[swift-dev] `withUnsafePointer` mutates `self`

Dave Abrahams dabrahams at apple.com
Wed Dec 16 11:29:38 CST 2015


> On Dec 16, 2015, at 6:59 AM, Ryan Lovelett via swift-dev <swift-dev at swift.org> wrote:
> 
> On Wed, Dec 16, 2015, at 03:12 AM, Dave Abrahams via swift-dev wrote:
>> 
>>> On Dec 15, 2015, at 11:51 PM, Kevin Ballard <kevin at sb.org> wrote:
>>> 
>>> On Tue, Dec 15, 2015, at 11:25 PM, Dave Abrahams wrote:
>>>> 
>>>>> On Dec 15, 2015, at 6:46 PM, Kevin Ballard <kevin at sb.org> wrote:
>>>>> 
>>>>> On Tue, Dec 15, 2015, at 06:39 PM, Dave Abrahams wrote:
>>>>>> 
>>>>>>> On Dec 15, 2015, at 6:33 PM, Kevin Ballard via swift-dev <swift-dev at swift.org> wrote:
>>>>>>> 
>>>>>>> On Tue, Dec 15, 2015, at 03:03 PM, Joe Groff via swift-dev wrote:
>>>>>>>> 
>>>>>>>> Yeah, it seems to me like a reasonable refinement for 'withUnsafePointer' to take an immutable parameter. Since this is a stdlib API change, you should suggest that on swift-evolution.
>>>>>>> 
>>>>>>> A change like that is going to break any code that relies on the inout optimization (where it uses call-by-reference instead of copy-in copy-out when possible). Yes, such code is in violation of Swift semantics today, but it does work.
>>>>>> 
>>>>>> Two questions:
>>>>>> 
>>>>>> 1. Don’t we want a withUnsafeMutablePointer for the mutating cases (where the inout optimization can take effect) anyway?
>>>>> 
>>>>> I'm thinking here of cases like passing a context pointer to KVO. You're not actually mutating it, you just need a pointer that's the same every time you call the code.
>>>> 
>>>> Well, it is not possible to code a version of withUnsafePointer that makes that guarantee in Swift.
>>> 
>>> Yeah but we want to move in the direction of making that more reliable, not less.
>> 
>> I am not sure I agree with you.  I would defer to John McCall on this
>> one, but my understanding is that it's an explicit non-goal to make that
>> guarantee.
>> 
>>> I forget who but someone said in another thread that global variables can be reliably passed by-ref to functions that take pointers already (even though the Swift documentation does not guarantee this).
>>> 
>>> Come to think of it, what's the actual use-case for withUnsafePointer()?
>> 
>> I'm not sure we still have one that isn't covered by &x; that's my point.
>> 
>>> If a value is mutable, you can already use &x or withUnsafeMutablePointer(), and if it's immutable, you can't call withUnsafePointer() today anyway. The proposed change would just make withUnsafePointer() into the equivalent of `var x = value; callSomethingWith(&x)`. The only reason to really want a withUnsafePointer() function is if it can give you an UnsafePointer to an immutable value without copying it, but we can't do that. I'm inclined to say we should just get rid of withUnsafePointer() entirely, at least until such time as Swift has a way to pass immutable values by-ref.
>> 
>> I'm inclined to agree.  Proposal?
> 
> This line of thinking is hard for me to wrap my head around. I _get_
> that in my original example I could have easily changed the code to be:
> 
> func profileName(profile: Int32) -> String? {
>  var mutatingSelf = self
>  let result = av_get_profile_name(&mutatingSelf, profile)
>  return String.fromCString(result)
> }
> 
> Now the self reference is still constant, thus no `mutating` keyword on
> the func definition. From that stand point alone I suppose problem
> solved.

Depending on the semantics of av_get_profile_name the above may actually be unsafe, because the compiler is allowed to destroy both self and mutatingSelf before you use result.  You could make it safe by using withFixedLifetime, but that's sort of beside the point…

I was suggesting something more like this:

func profileName(profile: Int32) -> String? {
 return { (pSelf: UnsafePointer)->String in String.fromCString( av_get_profile_name(pSelf, profile) }(&self) }
}

However, it appears we don't do the &x => UnsafePointer mapping for immutable x (feature request?), so that won't work.  Also, it's obviously cumbersome, obtuse, and tricky to get right.

This is obviously better:

func profileName(profile: Int32) -> String? {
 return withUnsafePointer(self){ String.fromCString( av_get_profile_name($0, profile) }
}


> I also get that Swift uses some sort of
> copy-on-write/copy-on-modifcation for these sorts of cases therefore
> there is not a significant performance overhead in the `var mutatingSelf
> = self` because `av_get_profile_name` takes a `const AVCodec *` or
> `UnsafePointer<AVCodec>` and thus is guaranteed to _not_ modify
> mutatingSelf; thus saving the copy.

No, there's no CoW here in general, but "var mutatingSelf = self" is always O(1) in Swift because copies are always O(1) in Swift (*)

(*) except for all of our existential boxes, which are eagerly copied; that should be considered a bug; we need CoW for these.

> The thing that is hard for me to wrap my head around: how is any of that
> obvious to the developer? I was always under the impression that one of
> Swift's goals was to help a developer write clear and concise code. This
> line of thinking at least partially feels like the opposite of that.
> From my perspective, taking away `withUnsafePointer` we are actively
> walking in the other direction of clear. Though I concede this might be
> _slightly_ more concise. However, trading conciseness for clarity seems
> like a bad idea.

I agree.  There's a reason I introduced withUnsafePointer in the first place, and it was for cases like these. 

> A thought experiment that I went through when thinking about this
> proposal was assume that `withUnsafeMutablePointer` or
> `withUnsafePointer` had _never_ been in the Swift language. The proposed
> code above still would not feel idiomatic to me. The code above
> explicitly makes a mutable copy of self so that it can send it to a
> function that guarantees it will not mutate it. 😕
> 
> It feels like a hack/work-around for a limitation of the language.
> Effectively this syntax says that Swift, by design, is asymmetric when
> it comes to pointers. The language can _injest_ your C `const` params or
> non-`const` params idiomatically but will not provide an idiomatic
> method to send them back.

Not sure what you mean here.

> Feels wrong.
> 
> All that having been said. I get that `const` and non-`const` are just
> compiler abstractions but so are `var` and `let`. Furthermore, one or
> more of my assumptions might be completely wrong and I am by no means a
> language expert. Like I said at the start I don't want to make a fool of
> myself. I already feel like I'm wading into neck deep water and might be
> in over my head here shortly. My comments are meant to be from the
> perspective of someone who knows nothing about how the Swift compiler
> works; just how I'd _expect_ it to work as a consumer of the compiler.

You can relax, Ryan.  You input is appreciated and I think your arguments are valid :-)

Thanks,

-Dave





More information about the swift-dev mailing list