swift-users Digest, Vol 6, Issue 28


(Ken Burgett) #1

Hi Quinn,

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?

···

On 2016-05-30 10:00, swift-users-request@swift.org wrote:

Send swift-users mailing list submissions to
  swift-users@swift.org

To subscribe or unsubscribe via the World Wide Web, visit
  https://lists.swift.org/mailman/listinfo/swift-users
or, via email, send a message with subject or body 'help' to
  swift-users-request@swift.org

You can reach the person managing the list at
  swift-users-owner@swift.org

When replying, please edit your Subject line so it is more specific
than "Re: Contents of swift-users digest..."

Today's Topics:

   1. Re: Simple text file I/O with Swift 3 (Quinn "The Eskimo!")

----------------------------------------------------------------------

Message: 1
Date: Mon, 30 May 2016 09:11:41 +0100
From: "Quinn \"The Eskimo!\"" <eskimo1@apple.com>
To: Swift Users List <swift-users@swift.org>
Subject: Re: [swift-users] Simple text file I/O with Swift 3
Message-ID: <D94A5001-BE92-44FB-ADB8-CE0D68E62323@apple.com>
Content-Type: text/plain; charset=us-ascii

On 28 May 2016, at 19:05, Ken Burgett via swift-users > <swift-users@swift.org> wrote:

print(buf)

The trick here is to replace the above line with:

    print(String(validatingUTF8: buf))

`fgets` sets up `buf` to hold a C string, so you have to convert it to
a Swift string. How do you do this depends on the encoding of the
bytes. If you expect the C string to be UTF-8, then
`String(validatingUTF8:)` is the way to go.

IMPORTANT: This conversion can fail, which is why the above will print
a bunch of optional strings, and you will have to decide what to your
program should do when it does.

                   * * *

btw This question came up recently. See the thread for other
suggestions about how to handle it.

<http://article.gmane.org/gmane.comp.lang.swift.user/1943>

Share and Enjoy
--
Quinn "The Eskimo!" <http://www.apple.com/developer/>
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

------------------------------

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

End of swift-users Digest, Vol 6, Issue 28
******************************************

--
Ken Burgett
Principal Software Engineer
Email: kenb@iotone.io
Office: 530.693.4449
Mobile: 831.332.6846
URL: www.iotone.co


(Brent Royal-Gordon) #2

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