Search code examples
iosswiftmacosunsafemutablepointer

UnsafeMutablePointer to an Array element


var a = [1,2,3]
let ptr1 = UnsafeMutablePointer<Int>(&a[0]) //works fine

let index = 0
let ptr2 = UnsafeMutablePointer<Int>(&a[index]) //compiler throws error

error: cannot invoke initializer for type UnsafeMutablePointer<Int> with an argument list of type (inout Int)

Why the latter one doesn't compile? Is there anything I am missing here?


I wanted to do like a below snippet.

class Holder {
    var numbers: [Int] = [1,2,3,4]
    var modifier: Modifier

    init(index: Int) {
       self.modifier = Modifier(UnsafeMutablePointer(&self.numbers) + index)
    }

}

class Modifer {
  var ptr: UnsafeMutablePointer<Int>

  init(_ ptr: UnsafeMutablePointer<Int>) {
     self.ptr = ptr
  }

  func change(to: Int) {
     self.ptr.pointee = to
    // expected the change to be reflected in numbers array
    // but as Rob Napier said it became invalid and throws EXC_BAD_ACCESS
  }
}

How can I achieve the above expected result?


Solution

  • Note that neither of these is a valid way to create an UnsafeMutablePointer. Swift is free to deallocate a immediately after the last time it is referenced, so by the time you use these pointers, they may be invalid. The tool you want is a.withUnsafeMutableBufferPointer.

    That said, the correct syntax here is:

    let ptr2 = UnsafeMutablePointer(&a) + index
    

    Looking at your updated code, there's no way for this to make sense on Array. I think you're assuming that Arrays are reference types. They're value types. There's no way to change a piece of numbers. Any change to it replaces the entire array with a completely different array. Swift has some clever copy-on-write tricks to make this efficient, and in practice it may not actually replace the entire array, but you should program as though it did. You should consider the following line:

    array[1] = 2
    

    to be equivalent to:

    array = <a new array that is identical, but element 1 has been replaced by 2>
    

    This means that pointers into Array are meaningless outside of very controlled situations (such as inside a withUnsafeMutableBufferPointer block).

    What you want is something more like this:

    class Holder {
        var numbers: [Int] = [1,2,3,4]
        private(set) var modifier: ((Int) -> ())! // ! is an artifact of capturing self in init
    
        init(index: Int) {
            self.modifier = { [weak self] in self?.numbers[index] = $0 }
        }
    }
    
    let holder = Holder(index: 2)
    holder.numbers  // [1, 2, 3, 4]
    holder.modifier(0)
    holder.numbers  // [1, 2, 0, 4]