I'm generally not against this feature, I'd put it into the "nice to have" box. However I'm strongly against the most commonly pitched designs which make it a direct subscript of the collection type. Subscripts are already not discoverable at all, by adding more direct overloads you won't improve the situation by any means. There is simply no way for a newcomer to come up with the idea to write array[safe: index]
(I used one of the mentioned spelling from above). I also understand the fact that we don't want to split the setter from getter and therefore need a subscript, but there is a slightly better way. Since we cannot name subscripts directly like functions we could add a more discoverable view over the Collection
.
struct SomeView<T> where T : Collection {
// Should use a reference instead for the setter.
private var collection: T
subscript (index: T.Index) -> T.Element? {
guard
collection.indices.contains(index)
else { return nil }
return collection[index]
}
}
extension SomeView where T : MutableCollection {
subscript (index: T.Index) -> T.Element? {
get {
guard
collection.indices.contains(index)
else { return nil }
return collection[index]
}
set {
guard
let value = newValue,
collection.indices.contains(index)
else { return }
collection[index] = value
}
}
}
extension Collection {
var some_view: SomeView<Self> {
get { return SomeView(collection: self) }
set {
/* do differently, but better */
self = newValue.collection
}
}
}
var array: [Int] = [1, 2, 3, 4]
array.some_view[3] // returns Int?
array.some_view[0] == 1 // true
array.some_view[10] == nil // true
array.some_view[10] = 10 // no-op
array.some_view[0] = 42 //
array.some_view[0] == 42 // true
print(array) // [42, 2, 3, 4]
I also don't see the need for an Int
constrained Index
, because this feature already adds another level of protection by wrapping a failed access into an Optional
. That said, if you have an index that matches your collection type it should be safe to use it.
If this pitch is also willing to tackle the mutable access, I would love to see some discussion in another thread about dual-typed subscripts where the getter type is different form the setter type. As shown in the example above the pitched design which makes use of a subscript adds a new ability to try to assign nil
values to a collection with potentially non-optional Element
type. This can be fixed if we don't use a subscript at all, but this would make it less convenient to use. I personally would like to see a dual-typed subscripts as a statically type safe solution for that problem.
Update: I just had a different idea which is probably more reasonable to tackle than a dual-typed subscript. Instead we should push failable subscript's!
subscript?(index: T.Index) -> T.Element
^
The setter will be statically forced to be T.Element
, while the returned value would be wrapped into an optional always resulting into T.Element?
similar to init?
.
Funny story, I already pitched that once in the same discussion, but forgot about it.