tera
1
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
tera
3
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.
kavon
(Kavon Farvardin)
4
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
JuneBash
(June Bash)
5
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
bbrk24
6
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
tera
7
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