← Back to context

Comment by rudedogg

4 years ago

Funny, the ergonomics of the Swift string API are so bad that I've started learning a lower level language for parsing, etc.

Here's my favorite WTF: https://christiantietze.de/posts/2020/01/string-index-offset...

You can fault the docs but what's the problem with the API? Why should you be surprised that accessing a collection with 'nil' index is a runtime error? What else could it be?

A simple fix in the doc seems to solve the confusion:

"Returns an index that is the specified distance from the given index, unless that distance is beyond a given limiting index [in which case it returns nil]".

It does say "returns an index ... unless ..". So yeah, a bit too terse. But with is the issue with the API?

    //  /!\  Warning! Do not use in production!   /!\
    let s = "Swift"
    if let i = s.index(s.startIndex, offsetBy: 5, limitedBy: s.endIndex) {
       print(s[i])
    }

"What does it print? Nothing, you hope?"

Seriously? 'print(s[nil])' should print nothing? How about 'let c = s[nil]'. Should that just silently pass? That is the runtime error, btw. It won't even get to print(). (Valid to question the entirely non-informative error, however.)

  • If s.index() returned nil, the "if" would test false and the s[i] would not be reached.

    The problem is that it returns non-nil, but the _limit_ is broken in this case: using s.endIndex as a limit means you can get non-nil but bogus indices returned. And yes, this is the fault of the docs for using a broken last arg to the API, but there's no really clean way to use this API as designed, afaict. At least not if you want to limit to "end of string" as opposed to "some index into the string that I already know to be valid".

    • The index is not bogus, the API is working as designed. The example provided shows the use of the index, which I understand can be confusing because the index returned may not always be valid for this, but the index is decidedly valid. FWIW, since String is a BiderectionalCollection, this code works for what you are probably trying to do:

        let s = "Swift"
        if !s.isEmpty,
            let index = s.index(s.startIndex, offsetBy: 5, limitedBy: s.index(before: s.endIndex)) {
          print(s[index])
        }
      

      I am sure the equivalent code in other languages, barring Python, is going to be similarly verbose.

  • The problem is that `s.index` with an `offset` equal to `limitedBy` returns non-nil index, rather than nil, but that index is invalid (out of bounds) and causes the program to blow up...

        let s = "Swift"
        
        print(s.index(s.startIndex, offsetBy: 4, limitedBy: s.endIndex))
        print(s.index(s.startIndex, offsetBy: 5, limitedBy: s.endIndex))
        print(s.index(s.startIndex, offsetBy: 6, limitedBy: s.endIndex))
    

    outputs:

        Optional(Swift.String.Index(_rawBits: 262401))
        Optional(Swift.String.Index(_rawBits: 327681)) <-  this is unexpected
        nil

    • The index is perfectly valid; it's just that not every index may be used for subscripting. This is exactly how C++ does it too.