[swift-users] swift-users Digest, Vol 6, Issue 28
Brent Royal-Gordon
brent at architechies.com
Mon May 30 20:57:31 CDT 2016
> Thanks for the tip on print(String(validatingUTF8: buf)), that does reproduce my input text, line for line, EXCEPT for wrapping every line in "Optional(line-of-text-with-terminator)", for example "Optional("import Glibc\n")".
>
> So, how does wrapping a line of UTF8 text in another character string "Optional()" help me print the text? Is Optional() some kind of function? If so, how is it intended to be used?
The inclusion of "Optional(…)" in the printout is meant to tell you that what you got wasn't a plain `String`, but an `Optional<String>`.
`Optional` is a type Swift uses to express that a certain value might be `nil`. Essentially, an `Optional<String>` either contains a `String`, or it doesn't contain a `String`, in which case it is `nil`. To print the `Optional<String>`, you must first extract the `String` from it in some way, which is called "unwrapping" it.
(Note: Although I've written the type `Optional<String>` out longhand, Swift has a very commonly used shorthand form: `String?`. This is what you'll see in most API listings.)
In this case, the `String(validatingUTF8:)` constructor returns an `Optional<String>` instead of just a `String` because, if the bytes you pass it are not valid UTF-8, it returns `nil` instead of returning an invalid string. If you want to use the `String`, you need to decide how to unwrap it, and thus how to handle the `nil` case where the UTF-8 you passed in was invalid.
Getting down to concrete code changes, what you need to do here is first separate the conversion to a `String` from the `print()` line:
while fgets(&buf, Int32(BUFSIZE), file_handle) != nil
{
let possibleString = String(validatingUTF8: buf)
print(possibleString)
}
And then use one of Swift's features for unwrapping Optional types. There are three simple ways you can choose from:
1. You can use the postfix `!` operator to extract the value from `possibleString`, failing an assertion (i.e. crashing) if `possibleString` happens to be `nil`. That sounds a little bit ridiculous, but if you have complete control of the input data and you know it should *never* contain invalid UTF-8, crashing is probably better than adding a code path to your program that you haven't really thought about because it "can't happen".
while fgets(&buf, Int32(BUFSIZE), file_handle) != nil
{
let possibleString = String(validatingUTF8: buf)
print(possibleString!)
}
2. You can use the infix `??` operator to substitute a default value for the string. For instance, you might decide to print "[invalid]" for invalid text:
while fgets(&buf, Int32(BUFSIZE), file_handle) != nil
{
let possibleString = String(validatingUTF8: buf)
print(possibleString ?? "[invalid]")
}
3. You can use the `if let` construct to test for whether `possibleString` has a value and, if so, use it. For instance, if you just want to skip invalid lines:
while fgets(&buf, Int32(BUFSIZE), file_handle) != nil
{
let possibleString = String(validatingUTF8: buf)
if let string = possibleString {
print(string)
}
}
Or if you wanted to print a message to `stderr` and exit with a nonzero return code:
while fgets(&buf, Int32(BUFSIZE), file_handle) != nil
{
let possibleString = String(validatingUTF8: buf)
if let string = possibleString {
print(string)
}
else {
fprintf(stderr, "Invalid UTF-8 byte sequence")
exit(1)
}
}
This second behavior, incidentally, might be better written as a `guard let` statement. `guard` is sort of the reverse of `if`—it has only an `else` block. The `else` block is also *required* to exit the scope it's in, for instance by calling `exit`, using `return`, failing an assertion, or (since we're in a loop) using `continue` or `break`.
while fgets(&buf, Int32(BUFSIZE), file_handle) != nil
{
let possibleString = String(validatingUTF8: buf)
guard let string = possibleString else {
fprintf(stderr, "Invalid UTF-8 byte sequence")
exit(1)
}
print(string)
}
You can see how the `guard` statement lets you leave the "happy path" unindented, whereas the equivalent `if` forced you to indent it. If you had several different conditions—the line must be valid, the line must not be empty, the line must contain only digits, etc.—then using `guard` for each of these conditions might make your code significantly more readable than it would be as a series of nested `if`s.
--
Brent Royal-Gordon
Architechies
More information about the swift-users
mailing list