Immutable value as inout argument

Why do we have this limitation:

func mutates(_ x: UnsafeMutablePointer<Int>) { ... }
func doesntMutate(_ x: UnsafePointer<Int>) { ... }

func bar(x: Int, y: Int) {
    mutates(&x)      // 🛑 Cannot pass immutable value as inout argument:
                     // 'x' is a 'let' constant 😀👍
    var x = x
    mutates(&x)      // ✅ 😀👍

    doesntMutate(&y) // 🛑 same error?! 😒👎
    var y = y        // feels odd I need this 😒👎
    doesntMutate(&y) // ✅
}

I would understand the error if "doesntMutate" was taking "UnsafeMutablePointer" but here it takes "UnsafePointer" and it is not possible to modify the passed argument anyway. It feels that I have to make a copy for no good reason, which might be trivial for Int, but for String / Array / etc there's a not so trivial cost even taking into account the COW optimisation (and some value types won't have COW optimisation!).

1 Like

The & operator is not a pointer-to operator, it's an inout operator, and separately there's an inout-to-pointer conversion that converts an inout parameter to a mutable pointer. There's a valid argument to be made that Swift should add this capability, but you can accomplish what you want using the built-in withUnsafePointer function.

withUnsafePointer(to: y) { pointerToY in
    doesntMutate(pointerToY)
}

This should work.

8 Likes

Yes, ideally this is done by Swift itself behind the scenes, just imagine doesntMutate takes several pointer parameters:

withUnsafePointer(to: a) { a in
    withUnsafePointer(to: b) { b in
        withUnsafePointer(to: c) { c in
            doesntMutate(a, b, c)
        }
    }
}

Found this old thread.

With SE-377 we could mark the to parameter of withUnsafePointer with borrowing, but borrowing is already the implicit ownership for that parameter. If internally withUnsafePointer does something that requires a copy of its parameter then that's unfortunate.

As an optimization the compiler may sometimes pass a borrowing argument by-value when the type is copyable and simple, like Int. But because withUnsafePointer<T> is generic over its to parameter, it's always going to pass an address; the borrowing ownership just prevents the caller from having to retain/copy the value before the address is formed.

I do not think it is a good idea to allow implicit casts of &var to an unsafe type like UnsafeMutablePointer.

1 Like

One could imagine a new function once variadic generics are finished that makes this a little more ergonomic.

withUnsafePointers(to: a, b, c) { aPtr, bPtr, cPtr in
  doesntMutate(aPtr, bPtr, cPtr)
}

Or even shorter:

withUnsafePointers(to: a, b, c, doesntMutate(_:_:_:))
1 Like

Just thinking out loud here, if they're all the same type this should already be doable:

func withUnsafePointer<T, Result>(to args: T..., body: (UnsafePointer<T>) throws -> Result) rethrows -> Result {
  return try args.withUnsafeBufferPointer { buf in
    return try body(buf.baseAddress!)
  }
}

Usage:

withUnsafePointer(to: a, b, c) { ptr in
  doesntMutate(ptr, ptr + 1, ptr + 2)
}

Granted, pointer arithmetic doesn't look as nice as multiple arguments.

1 Like

These workarounds are nice of course but they can't beat the simplicity of the more straightforward and concise:

func bar(x: Int, y: Int) {
    doesntMutate(&x, &y)
}

(or whatever the actual syntax we will land with, be it doesntMutate(x, y) or doesntMutate(^x, ^y), or etc.

1 Like

rust is aware of both mutable and immutable references, so passing let references is possible as immutable references, not sure why Swift is not yet on the same level of references discernment , however simply blocking immutable inout seems more like a design pitfall rather than a deliberate constraint

fn hold_my_vec<T>(_: &Vec<T>) {}

fn main() {
    let v = vec![2, 3, 5, 7];

    hold_my_vec(&v); // immutable borrow reference
}

UPDATE
can pass immutable references with borrowing

func foo(_: borrowing Foo)