10

I wrote following extension in Swift 2.3:

extension CollectionType {
    /// Returns the element at the specified index iff it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Generator.Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

However, it turns out that Swift 3.0 does not have contains() function. Instead, it offers me following syntax for this method:

indices.contains(where: { (<#Self.Indices.Iterator.Element#>) -> Bool in
    <# code ??? what should it do??? #>
})

The problem is that I don't know what should it contain inside the block. Any help with migrating it, please?

Hamish
  • 78,605
  • 19
  • 187
  • 280
Nat
  • 12,032
  • 9
  • 56
  • 103

1 Answers1

28

Swift 4 Update

In Swift 4, thanks to the ability to have where clauses on associated types, Collection now enforces that Indices's Element type is the same type as the Collection's Index.

This therefore means that we can just say:

extension Collection {

    /// Returns the element at the specified index iff it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

Swift 3

The Sequence protocol in Swift 3 still has a contains(_:) method, which accepts an element of the sequence if the sequence is of Equatable elements:

extension Sequence where Iterator.Element : Equatable {
    // ...
    public func contains(_ element: Self.Iterator.Element) -> Bool
    // ...
}

The problem you're encountering is due to the change in the type of Collection's indices property requirement. In Swift 2, it was of type Range<Self.Index> – however in Swift 3, it is of type Indices (an associated type of the Collection protocol):

/// A type that can represent the indices that are valid for subscripting the
/// collection, in ascending order.
associatedtype Indices : IndexableBase, Sequence = DefaultIndices<Self>

As there's currently no way in Swift for the Collection protocol itself to express that Indices's Iterator.Element is of type Index (this will however be possible in a future version of Swift), there's no way for the compiler to know that you can pass something of type Index into contains(_:). This is because it's currently fully possible for a type to conform to Collection and implement Indices with whatever element type it wants.

Therefore the solution is to simply constrain your extension to ensure that Indices does have elements of type Index, allowing you to pass index into contains(_:):

extension Collection where Indices.Iterator.Element == Index {

    /// Returns the element at the specified index iff it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Iterator.Element? {
        return indices.contains(index) ? self[index] : nil
    }
}
Hamish
  • 78,605
  • 19
  • 187
  • 280
  • 3
    Here http://stackoverflow.com/a/30593673/1187415 is a solution which does not require any constraints. – Martin R Oct 30 '16 at 17:52
  • @MartinR Ah yes, I may have overthunk the solution in this case ;) Although once [SE-0142: Permit where clauses to constrain associated types](https://github.com/apple/swift-evolution/blob/master/proposals/0142-associated-types-constraints.md) is implemented, I would hope that the stdlib team will add the constraint to the associated type itself, so the extension won't require it. – Hamish Oct 30 '16 at 18:07
  • 3
    A possibly corner-case-only-prompt, but a prompt nonetheless: there are cases where the simplified `O(1)` "safe" subscript version in the linked Q&A is still not entirely safe, whereas the `O(n)` version above, is: for `Collection` with `Indices` that are not contiguous. E.g. for `Set` instances, _if_ we were to access a set element by index (`SetIndex`), we can run into runtime exceptions for indices that are `>= startIndex` end `< endIndex`, in which case the safe subscript fails (see e.g. [this contrived example](https://gist.github.com/dfrib/85398f9d3d5bfc9757905b499d79e26f)). – dfrib Oct 30 '16 at 18:37
  • 1
    @dfri A very good point, thanks for mentioning it! Although the solution above isn't necessarily O(n) – if the conforming type's `Indices` have an O(1) implementation of `contains(_:)` and the concrete type of the conforming type is known (not type erased), the O(1) implementation should be used (IIRC). – Hamish Oct 30 '16 at 18:53
  • 1
    You're right! I was pondering if the extension in the linked answer should be applied to e.g. `RandomAccessCollection` rather than `Collection` (or some other more appropriate protocol), in which case (I believe) it would be safe, but then it's possibly to narrow instead. – dfrib Oct 30 '16 at 18:59
  • 1
    @dfri Yeah, I don't *believe* there's a protocol out there that guarantees contiguous indices – although you'd have to have a *really* wacky implementation of `RandomAccessCollection` to warrant non-contiguous indices (I suppose one really (I mean *really*) contrived example could be a specialised collection for storing two interleaved collections– a 'just the elements of collection A' *slice* of such a collection could be a `RandomAccessCollection`, but would have non-contiguous indices, as it would be skipping an index upon advancing). – Hamish Oct 30 '16 at 19:15
  • @Hamish Is contains method faster than using a guard to get the index offsetBy n limitedBy endIndex and returning nil or is it the same?. `extension Collection { subscript(safe offset: Int) -> Element? { guard let index = self.index(startIndex, offsetBy: offset, limitedBy: endIndex) else { return nil } return self[index] } }` – Leo Dabus Sep 01 '18 at 13:08
  • Btw why `~=` operator (I think I read that Apple says it is preferred over contains) cant be used in this case `return indices ~= index ? self[index] : nil` – Leo Dabus Sep 01 '18 at 13:09
  • 1
    @LeoDabus I wouldn't expect there to be any real performance differences between the two – at least if there was, I would consider that to be a bug of the conforming collection rather than a flaw of the subscript's implementation. (Slight tangent: Arguably, this extension should only be written on `RandomAccessCollection` to ensure the check is always done in O(1) time to fit with the semantics of a subscript). Note btw that you'd also need to add the condition `index != endIndex` to your guard, as `limitingBy:` is an inclusive upper bound. – Hamish Sep 01 '18 at 13:52
  • 1
    @LeoDabus Regarding why you can't use `~=`, it's because there's no overload of it that deals with collections. There is an overload however that deals with `RangeExpression`, which is why you can say things like `0 ... 1 ~= 0`. (Though as a point of preference, I always prefer `contains` over `~=`). – Hamish Sep 01 '18 at 13:53
  • @Hamish Thank you. So the only way would be something like `func ~= (lhs: C, rhs: C.Element) -> Bool where C.Element: Equatable { return lhs.contains(rhs) }` – Leo Dabus Sep 01 '18 at 14:05
  • 1
    @LeoDabus Yup – and that would also let you do things like `switch 5 { case [1, 2, 5]: ... }` – Hamish Sep 01 '18 at 14:06
  • @Hamish What about `extension Collection where Element: Equatable { static func ~= (lhs: Self, rhs: Element) -> Bool { return lhs.contains(rhs) } }` which one do you prefer? I prefer the second one – Leo Dabus Sep 01 '18 at 15:23
  • 1
    @LeoDabus Yup, I prefer the one in the extension – Hamish Sep 01 '18 at 15:25
  • Hi there, can you take a look [Do protocols have an effect on the retain count?](https://stackoverflow.com/questions/56227192/do-protocols-have-an-effect-on-the-retain-count) – mfaani May 20 '19 at 19:43