`inout` subscript parameters

Why are inout subscript parameters prohibited?

I just tried making a Collection subscript that post-increments the index, and was met with this error:

extension Collection {
  // error: 'inout' must not be used on subscript parameters
  subscript (postIncrement i: inout Index) -> Element {
    defer { formIndex(after: &i) }
    return self[i]
  }
}

What’s the rationale here?

4 Likes

It just introduces some implementation complexity that we haven't had the time to handle right.

2 Likes

For this specific use-case I'd use a custom post-increment operator:

array[index++]

I use that in my sources occasionally, so far had no problem with it.

However there are indeed cases where "inout" subscript parameter would be highly beneficial to have. Alternatively a function with setter could work equally well:

func foo() -> String {
    get {
        // normal function body here
    }
    set {
        print(newValue)
        // set is mutating unless mark nonmutating
    }
}

foo() = "hello"

Without any of those, there's this dirty workaround:

extension MutableCollection {
    subscript(postIncrement i: UnsafeMutablePointer<Index>) -> Element {
        get {
            defer { formIndex(after: &i.pointee) }
            return self[i.pointee]
        }
        set {
            defer { formIndex(after: &i.pointee) }
            self[i.pointee] = newValue
        }
    }
}

var array = ["a", "b", "c", "d"]
var index = 0
let x = withUnsafeMutablePointer(to: &index) { array[postIncrement: $0] }
print(x, index)
let y = withUnsafeMutablePointer(to: &index) { array[postIncrement: $0] }
print(y, index)
let z = withUnsafeMutablePointer(to: &index) { array[postIncrement: $0] }
print(z, index)

// setter:
_ = withUnsafeMutablePointer(to: &index) { array[postIncrement: $0] = "X" }
print(array, index) // ["a", "b", "c", "X"] 4

which is far from nice.

Somewhat cleaner is to use a binding subscript parameter:

extension MutableCollection {
    subscript(postIncrement i: Binding<Index>) -> Element {
        get {
            defer { formIndex(after: &i.wrappedValue) }
            return self[i.wrappedValue]
        }
        set {
            defer { formIndex(after: &i.wrappedValue) }
            self[i.wrappedValue] = newValue
        }
    }
}

var array = ["a", "b", "c", "d"]
var index = 0
var binding = Binding<Int>(get: {index}, set: {index = $0})
let x = array[postIncrement: binding]
print(x, binding.wrappedValue) // a 1
let y = array[postIncrement: binding]
print(y, binding.wrappedValue) // b 2
let z = array[postIncrement: binding]
print(z, binding.wrappedValue) // c 3

// setter:
array[postIncrement: binding] = "X"
print(array, binding.wrappedValue) // ["a", "b", "c", "X"] 4