So here are two designs that support setters. I'm not convinced that setters are needed here as I haven't found a use-case that requires checked setting. That doesn't mean they don't exist. But if setters are part of the equation, then a method approach beats a subscripted approach.
The first is just awful:
/// Accesses the element indicated by `position`, returning `nil`
/// if it is not a valid position in `self` or `endIndex`.
///
/// ```swift
/// if let value = anArray[checked: idx] {
/// safely use value without trapping
/// }
/// ```
///
/// - parameter position: The index to be read or replaced
/// - returns: An optional, either the value for a in-bounds index or
/// nil for an out-of-bounds index
public subscript(checked position: Index) -> Element? {
get {
guard indices ~= position else { return nil }
return self[position]
}
}
/// Updates the element indicated by `position`.
///
/// Setters offer index checking, allowing in-place replacement for
/// valid indices. A special case is made for `endIndex`, which extends
/// the array by one element.
///
public subscript(setChecked position: Index) -> Element {
get {
fatalError("You cannot use a getter with `setChecked`")
}
set {
// Replace value in-place
guard indices ~= position else { return }
self[position] = newValue
}
}
}
extension ArraySlice {
/// Accesses the element indicated by `position`, returning `nil`
/// if it is not a valid position in `self` or `endIndex`.
///
/// - parameter position: The index to be read or replaced
/// - returns: An optional, either the value for a in-bounds index or
/// nil for an out-of-bounds index
public subscript(checked position: Index) -> Element? {
get {
guard indices ~= position else { return nil }
return self[position]
}
}
/// Updates the element indicated by `position`.
///
/// Setters offer index checking, allowing in-place replacement for
/// valid indices.
public subscript(setChecked position: Index) -> Element {
get {
fatalError("You cannot use a getter with `setChecked`")
}
set {
// Replace value in-place
guard indices ~= position else { return }
self[position] = newValue
}
}
}
var anArray = ["This", "is", "an", "array"]
var slice = anArray[1...3] // ["is", "an", "array"]
anArray[checked: 0] // "This"
slice[checked: 0] // nil
slice[checked: 1] // "is"
anArray[setChecked: 0] = "She" // ["She", "is", "an", "array"]
anArray[setChecked: anArray.endIndex] = "!" // ["She", "is", "an", "array", "!"]
let idx = anArray.index(anArray.endIndex, offsetBy: 1)
anArray[setChecked: idx] = "..." // ["She", "is", "an", "array", "!"]
slice[setChecked: 0] = "hello" // ["is", "an", "array"]
slice[setChecked: 1] = "isn't" // ["isn\'t", "an", "array"]
var optionalArray: [String?] = ["This", "is", "an", "array"]
optionalArray[setChecked: 0] = "She" // [Optional("She"), Optional("is"), Optional("an"), Optional("array")]
optionalArray[setChecked: 0] = nil // [nil, Optional("is"), Optional("an"), Optional("array")]
Here's the second design that does not use subscripting, supports checked assignment, and is much cleaner:
extension Array {
/// Accesses the element indicated by `position`, returning `nil`
/// if it is not a valid position in `self` or `endIndex`.
///
/// ```swift
/// if let value = anArray[checked: idx] {
/// safely use value without trapping
/// }
/// ```
///
/// - parameter position: The index to be read or replaced
/// - returns: An optional, either the value for a in-bounds index or
/// nil for an out-of-bounds index
public func element(at position: Index) -> Element? {
guard indices ~= position else { return nil }
return self[position]
}
/// Updates the element indicated by `position`.
///
/// Setters offer index checking, allowing in-place replacement for
/// valid indices. A special case is made for `endIndex`, which extends
/// the array by one element.
///
public mutating func setElement(at position: Index, to newValue: @autoclosure () -> Element) {
let value = newValue()
if position == endIndex {
self.append(value)
}
guard indices ~= position else { return }
self[position] = value
}
}
extension ArraySlice {
/// Accesses the element indicated by `position`, returning `nil`
/// if it is not a valid position in `self` or `endIndex`.
///
/// - parameter position: The index to be read or replaced
/// - returns: An optional, either the value for a in-bounds index or
/// nil for an out-of-bounds index
public func element(at position: Index) -> Element? {
guard indices ~= position else { return nil }
return self[position]
}
/// Updates the element indicated by `position`.
///
/// Setters offer index checking, allowing in-place replacement for
/// valid indices.
public mutating func setElement(at position: Index, to newValue: @autoclosure () -> Element) {
guard indices ~= position else { return }
self[position] = newValue()
}
}
var anArray = ["This", "is", "an", "array"]
var slice = anArray[1...3] // ["is", "an", "array"]
anArray.element(at: 0) // "This"
slice.element(at: 0) // nil
slice.element(at: 1) // "is"
anArray.setElement(at: 0, to: "She") // ["She", "is", "an", "array"]
anArray.setElement(at: anArray.endIndex, to: "!") // ["She", "is", "an", "array", "!"]
let idx = anArray.index(anArray.endIndex, offsetBy: 1)
anArray.setElement(at:idx, to: "...") // ["She", "is", "an", "array", "!"]
slice.setElement(at: 0, to: "Hello") // ["is", "an", "array"]
slice.setElement(at: 1, to: "isn't") // ["isn\'t", "an", "array"]
var optionalArray: [String?] = ["This", "is", "an", "array"]
optionalArray.setElement(at: 0, to: "She") // [Optional("She"), Optional("is"), Optional("an"), Optional("array")]
optionalArray.setElement(at: 0, to: nil) // [nil, Optional("is"), Optional("an"), Optional("array")]