RFC: Proposed rewrite of Unmanaged<T>

Also, while unambiguous, it’s a very low-level description of what’s happening, that doesn’t much help the user reading the documentation of a CF function to answer the “which one do I use?” question unless s/he’s already experienced with how to map what it says in that docs onto the use of ADDREF/TAKEREF.

I wonder if, instead of talking about retained/unretained, we should talk about whether the call “created" or “retrieved" the object it’s returning. If it created it, the object is +1; if it retrieved it, the object is +0.

I think I’d need more detail in order to evaluate the idea. Are you suggesting changing the way some CF functions are documented? Some concrete examples would certainly help.

CFAttributedString has actually been audited, but pretend it hasn't been...

  let attributedString = CFAttributedStringCreate(nil, anotherString,nil).takeCreatedObject()
  let str = CFAttributedStringGetString(attributedString).takeRetrievedObject()

I'm not a huge fan of the "take" here, but I think this general strategy of trying to say whether the Create Rule or the Get Rule applies is better than trying to make people understand when they should use "released" or not.

···

--
Brent Royal-Gordon
Architechies

Should the existing documentation be moved to the Swift.org website?

<https://developer.apple.com/library/ios/documentation/Swift/Reference/Swift_StandardLibrary_Functions/&gt;

-- Ben

···

On 22 Feb 2016, at 23:00, Janosch Hildebrand via swift-evolution <swift-evolution@swift.org> wrote:

So I want a decent overview over what free functions are available because
I probably should know about them before I need them. Being free functions
also signals that they are potentially special or different in some way so that
also makes them of interest.

Brent, thanks for working through this with me…

Also, while unambiguous, it’s a very low-level description of what’s happening, that doesn’t much help the user reading the documentation of a CF function to answer the “which one do I use?” question unless s/he’s already experienced with how to map what it says in that docs onto the use of ADDREF/TAKEREF.

I wonder if, instead of talking about retained/unretained, we should talk about whether the call “created" or “retrieved" the object it’s returning. If it created it, the object is +1; if it retrieved it, the object is +0.

I think I’d need more detail in order to evaluate the idea. Are you suggesting changing the way some CF functions are documented? Some concrete examples would certainly help.

CFAttributedString has actually been audited, but pretend it hasn't been...

  let attributedString = CFAttributedStringCreate(nil, anotherString,nil).takeCreatedObject()
  let str = CFAttributedStringGetString(attributedString).takeRetrievedObject()

I'm not a huge fan of the "take" here,

Then why did you use it, if you don't mind my asking? What is it supposed to mean in this context?

but I think this general strategy of trying to say whether the Create Rule or the Get Rule applies is better than trying to make people understand when they should use "released" or not.

Why is that better?

And how does "Retrieved" map onto "Get"?

Seems to me that if you know what the "Create Rule" is, you know it says "you're responsible for releasing the result." If you know a name contains "Create", you already have a good clue that the "Create Rule" applies. Isn't it the users of the functions that don't contain "Create" or "Get" in their names that need the most help?

-Dave

···

On Dec 19, 2015, at 2:02 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

CFAttributedString has actually been audited, but pretend it hasn't been...

  let attributedString = CFAttributedStringCreate(nil, anotherString,nil).takeCreatedObject()
  let str = CFAttributedStringGetString(attributedString).takeRetrievedObject()

I'm not a huge fan of the "take" here,

Then why did you use it, if you don't mind my asking? What is it supposed to mean in this context?

I suppose I'm struggling with the fact that there's clearly an action taking place here (at least in the created case), and yet merely saying `createdObject()` or `retrievedObject()` doesn't imply that. Those operations sound idempotent, but they're not.

(I kind of want to suggest that retrieving an object through these calls should destroy the reference so it can't be used again, but I don't think that fits with Swift's mutation model without turning `Unmanaged`/`UnsafeReference` into a reference type and adding lots of overhead.)

but I think this general strategy of trying to say whether the Create Rule or the Get Rule applies is better than trying to make people understand when they should use "released" or not.

Why is that better?

Mainly, because simply saying "release" or "released" is a bit ambiguous to me. Are you saying it *has been* released, or are you saying it *needs to be* released? I have the same problem with the current `takeRetainedValue()`/`takeUnretainedValue()` calls—I'm never sure which one I'm supposed to use. I'm hoping that, by stepping up a level and describing the semantic you want rather than the operation needed to achieve that semantic, this confusion can be cleared up.

I also like that this creates a matched pair of methods. Because they look sort of like each other, it's easier to understand that you should call one or the other, and to remember them.

And how does "Retrieved" map onto "Get"?

Not all that cleanly, I admit. "Gotten" would be better, but "get" is an irregular verb and I'm a little concerned about programmers who have English as a second language. (Plus, I subjectively think it's kind of ugly.)

(One possibility would be to have a single call with an enum parameter, like `bridge(.Create)` and `bridge(.Get)`. This would let you use the regular form of the verb.)

Isn't it the users of the functions that don't contain "Create" or "Get" in their names that need the most help?

I think of it more as "treat this like a Create function" or "treat this like a Get function".

···

--
Brent Royal-Gordon
Architechies

CFAttributedString has actually been audited, but pretend it hasn't been...

  let attributedString = CFAttributedStringCreate(nil, anotherString,nil).takeCreatedObject()
  let str = CFAttributedStringGetString(attributedString).takeRetrievedObject()

I'm not a huge fan of the "take" here,

Then why did you use it, if you don't mind my asking? What is it supposed to mean in this context?

I suppose I'm struggling with the fact that there's clearly an action taking place here (at least in the created case), and yet merely saying `createdObject()` or `retrievedObject()` doesn't imply that. Those operations sound idempotent, but they're not.

But you applied "take" to both of them? One of them is idempotent while the other is not.

(I kind of want to suggest that retrieving an object through these calls should destroy the reference so it can't be used again, but I don't think that fits with Swift's mutation model without turning `Unmanaged`/`UnsafeReference` into a reference type and adding lots of overhead.)

Yes, there's no way to reconcile that with the safety offered by the recommended usage patterns, since you can't mutate an rvalue.

but I think this general strategy of trying to say whether the Create Rule or the Get Rule applies is better than trying to make people understand when they should use "released" or not.

Why is that better?

Mainly, because simply saying "release" or "released" is a bit ambiguous to me.Are you saying it *has been* released, or are you saying it *needs to be* released?

But nobody proposed "released" as a method name. In what way is "release" ambiguous? It's an imperative verb.

I have the same problem with the current `takeRetainedValue()`/`takeUnretainedValue()` calls—I'm never sure which one I'm supposed to use. I'm hoping that, by stepping up a level and describing the semantic you want rather than the operation needed to achieve that semantic, this confusion can be cleared up.

I also like that this creates a matched pair of methods. Because they look sort of like each other, it's easier to understand that you should call one or the other, and to remember them.

The similarity of those names seems to me like a weakness of the current Unmanaged design: to me they are so similar it's hard to understand which one to call.

And how does "Retrieved" map onto "Get"?

Not all that cleanly, I admit. "Gotten" would be better, but "get" is an irregular verb and I'm a little concerned about programmers who have English as a second language. (Plus, I subjectively think it's kind of ugly.)

(One possibility would be to have a single call with an enum parameter, like `bridge(.Create)` and `bridge(.Get)`. This would let you use the regular form of the verb.)

There's no "bridging" going on here, though. This is simply "turn this unsafe thing into a safe thing in one of two ways"

Isn't it the users of the functions that don't contain "Create" or "Get" in their names that need the most help?

I think of it more as "treat this like a Create function" or "treat this like a Get function".

So far, my personal assessment of this direction is that it's no better than what I proposed, and has several weaknesses I'd like to avoid. In fact, it seems very similar to and roughly as understandable as the current Unmanaged design. I recognize that this is a highly subjective judgement, so if others disagree with me, I'd really like to hear about it. This is a tough design space and ultimately, what resonates best with the community is likely to be the best choice.

Thanks again,

-Dave

···

On Dec 19, 2015, at 2:59 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

Mainly, because simply saying "release" or "released" is a bit ambiguous to me.Are you saying it *has been* released, or are you saying it *needs to be* released?

But nobody proposed "released" as a method name. In what way is "release" ambiguous? It's an imperative verb.

I guess you're right that "release" is unambiguous, but as you mentioned, it's also strange to release a value and then use it.

I think what I'm trying to get at here is that I prefer to think of the operations on Unmanaged as "explain to ARC how it should handle this object", rather than "do some manual operations so that ARC will do the right thing". Maybe the current Unmanaged design has shown the limitations of that approach, though.

But you applied "take" to both of them? One of them is idempotent while the other is not.

The preferred way to use Unmanaged is that you immediately convert it to a managed reference without ever storing it or using it in any other way. That means you should immediately call either the retain-and-return operation or the don't-retain-and-return operation. Both of these should only ever be called once. You may instead choose to keep the reference Unmanaged and manually retain, release, and access it, but best practices discourage that.

Now, one of the preferred, do-only-once operations *happens* to be safe to apply more than once, but I view that as an implementation detail. Both of them *happen* to be implemented in the same way as manual operations (`manuallyRelease()` and `object`), but I view that as an implementation detail, too.

Honestly, I might be happier splitting an UnsafeReference type out of Unmanaged and putting the manual retain/release stuff into that:

  // Unmanaged is a high-level type for moving object references in and out of ARC's control.
  struct Unmanaged<T: class> {
    func created() -> T
    func gotten() -> T
    
    // Also would have stuff for passing, which I haven't even thought about yet
  }
  
  // UnsafeReference is a low-level type for manually managing the retain count of an object.
  struct UnsafeReference<T: class> {
    init(_ object: T)
    init(_ unmanaged: Unmanaged<T>)
    
    var object: T
    
    // Some or all of these might return T
    func retain()
    func release()
    func autorelease()
  }

This puts the discouraged manual operations off in their own type where they'll be available to those who know about them, but not sitting right there on every unaudited call.

(I kind of want to suggest that retrieving an object through these calls should destroy the reference so it can't be used again, but I don't think that fits with Swift's mutation model without turning `Unmanaged`/`UnsafeReference` into a reference type and adding lots of overhead.)

Yes, there's no way to reconcile that with the safety offered by the recommended usage patterns, since you can't mutate an rvalue.

I thought so. That's too bad. (I wonder if the compiler can emit warnings instead, though.)

(One possibility would be to have a single call with an enum parameter, like `bridge(.Create)` and `bridge(.Get)`. This would let you use the regular form of the verb.)

There's no "bridging" going on here, though. This is simply "turn this unsafe thing into a safe thing in one of two ways"

The "bridge" here comes from the Objective-C bridging casts, but I think there it's meant to refer to toll-free bridging, which is not what's happening in Swift.

If the type name remains `Unmanaged`, then perhaps `manage(_:)` would be better? (I don't like `managing` here because that again implies it's side-effect-free and safe to call more than once.)

So far, my personal assessment of this direction is that it's no better than what I proposed, and has several weaknesses I'd like to avoid. In fact, it seems very similar to and roughly as understandable as the current Unmanaged design. I recognize that this is a highly subjective judgement, so if others disagree with me, I'd really like to hear about it. This is a tough design space and ultimately, what resonates best with the community is likely to be the best choice.

I understand. I'm obviously struggling with this too, as you can see from how much I'm changing my design based on your replies, rather than defending the design as suggested before.

Ultimately, Unmanaged is an API for handling an abstraction failure. That's inherently going to be tricky and subjective.

···

--
Brent Royal-Gordon
Architechies

"takeAndRelease()"?

It seems so weird to me to call "release" on something and then use it - but I admit that seems less likely to confuse those not familiar with MRC. The parallel with documentation is nice.

I do agree with Brent that I found the previous symmetry (takeRetained and takeUnretained) easy to remember - even if not easy to keep straight which was which.

···

On Dec 19, 2015, at 6:14 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 19, 2015, at 2:59 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

CFAttributedString has actually been audited, but pretend it hasn't been...

  let attributedString = CFAttributedStringCreate(nil, anotherString,nil).takeCreatedObject()
  let str = CFAttributedStringGetString(attributedString).takeRetrievedObject()

I'm not a huge fan of the "take" here,

Then why did you use it, if you don't mind my asking? What is it supposed to mean in this context?

I suppose I'm struggling with the fact that there's clearly an action taking place here (at least in the created case), and yet merely saying `createdObject()` or `retrievedObject()` doesn't imply that. Those operations sound idempotent, but they're not.

But you applied "take" to both of them? One of them is idempotent while the other is not.

(I kind of want to suggest that retrieving an object through these calls should destroy the reference so it can't be used again, but I don't think that fits with Swift's mutation model without turning `Unmanaged`/`UnsafeReference` into a reference type and adding lots of overhead.)

Yes, there's no way to reconcile that with the safety offered by the recommended usage patterns, since you can't mutate an rvalue.

but I think this general strategy of trying to say whether the Create Rule or the Get Rule applies is better than trying to make people understand when they should use "released" or not.

Why is that better?

Mainly, because simply saying "release" or "released" is a bit ambiguous to me.Are you saying it *has been* released, or are you saying it *needs to be* released?

But nobody proposed "released" as a method name. In what way is "release" ambiguous? It's an imperative verb.

I have the same problem with the current `takeRetainedValue()`/`takeUnretainedValue()` calls—I'm never sure which one I'm supposed to use. I'm hoping that, by stepping up a level and describing the semantic you want rather than the operation needed to achieve that semantic, this confusion can be cleared up.

I also like that this creates a matched pair of methods. Because they look sort of like each other, it's easier to understand that you should call one or the other, and to remember them.

The similarity of those names seems to me like a weakness of the current Unmanaged design: to me they are so similar it's hard to understand which one to call.

And how does "Retrieved" map onto "Get"?

Not all that cleanly, I admit. "Gotten" would be better, but "get" is an irregular verb and I'm a little concerned about programmers who have English as a second language. (Plus, I subjectively think it's kind of ugly.)

(One possibility would be to have a single call with an enum parameter, like `bridge(.Create)` and `bridge(.Get)`. This would let you use the regular form of the verb.)

There's no "bridging" going on here, though. This is simply "turn this unsafe thing into a safe thing in one of two ways"

Isn't it the users of the functions that don't contain "Create" or "Get" in their names that need the most help?

I think of it more as "treat this like a Create function" or "treat this like a Get function".

So far, my personal assessment of this direction is that it's no better than what I proposed, and has several weaknesses I'd like to avoid. In fact, it seems very similar to and roughly as understandable as the current Unmanaged design. I recognize that this is a highly subjective judgement, so if others disagree with me, I'd really like to hear about it. This is a tough design space and ultimately, what resonates best with the community is likely to be the best choice.

Thanks again,

-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Mainly, because simply saying "release" or "released" is a bit ambiguous to me.Are you saying it *has been* released, or are you saying it *needs to be* released?

But nobody proposed "released" as a method name. In what way is "release" ambiguous? It's an imperative verb.

I guess you're right that "release" is unambiguous, but as you mentioned, it's also strange to release a value and then use it.

Yes. I think there are no really great choices here (at least not so far) so the question is whether that strangeness is enough of a problem to outweigh the points release() has in its favor. What do you think?

I think what I'm trying to get at here is that I prefer to think of the operations on Unmanaged as "explain to ARC how it should handle this object", rather than "do some manual operations so that ARC will do the right thing". Maybe the current Unmanaged design has shown the limitations of that approach, though.

Not at all; the Unmanaged design—at least in my best understanding of its intent—is firmly in the imperative/manual operations camp. I wanted to do something more declarative, but the "I want to manage the reference that I claim was passed to me at +1" operation is side-effectful. Are we really comfortable with hiding that fact?

But you applied "take" to both of them? One of them is idempotent while the other is not.

The preferred way to use Unmanaged is that you immediately convert it to a managed reference without ever storing it or using it in any other way. That means you should immediately call either the retain-and-return operation or the don't-retain-and-return operation. Both of these should only ever be called once. You may instead choose to keep the reference Unmanaged and manually retain, release, and access it, but best practices discourage that.

As I said in my original post, I'm ambivalent about the importance of highlighting the distinctions of safety and idempotence between these methods, but even if they're named similarly I don't see any merit in starting with "take." One thing I really dislike about is that the receiver, the UnsafeReference, isn't "taking" anything. The *caller* might be said to be taking something from the UnsafeReference, but only in the "returned at +1" case.

How I see it: along with the UnsafeReference the called CF function either notionally
a) gives (possibly-shared) ownership of the object directly to the caller, or
b) gives the caller a token that allows him to get (shared) ownership of the object

In case a), the caller needs to ask the UnsafeReference to transfer (or "release") that ownership into a strong reference, and. In case b), the caller needs to explicitly get (shared) ownership.

If this description doesn't sound right to you, please try to correct it; that may help me understand your perspective better.

Now, one of the preferred, do-only-once operations *happens* to be safe to apply more than once, but I view that as an implementation detail. Both of them *happen* to be implemented in the same way as manual operations (`manuallyRelease()` and `object`), but I view that as an implementation detail, too.

Hm, well, I don't view `object` as a "manual operation" and there's value in having a smaller API surface area. I don't think I want a separate `manuallyRelease` method if there is another method that has the same semantics. One of the greatest weaknesses of the current Unmanaged is that its interface is too broad and hard to grasp.

Honestly, I might be happier splitting an UnsafeReference type out of Unmanaged and putting the manual retain/release stuff into that:

As noted in my original post, I really don't want to keep the name "Unmanaged" for anything. If anything, it's "ManuallyManaged." And I am very wary of API surface area creep here, whether it's in one type or two.

  // Unmanaged is a high-level type for moving object references in and out of ARC's control.
  struct Unmanaged<T: class> {
    func created() -> T
    func gotten() -> T
    
    // Also would have stuff for passing, which I haven't even thought about yet
  }
  
  // UnsafeReference is a low-level type for manually managing the retain count of an object.
  struct UnsafeReference<T: class> {
    init(_ object: T)
    init(_ unmanaged: Unmanaged<T>)
    
    var object: T
    
    // Some or all of these might return T
    func retain()
    func release()
    func autorelease()
  }

This puts the discouraged manual operations off in their own type where they'll be available to those who know about them, but not sitting right there on every unaudited call.

(I kind of want to suggest that retrieving an object through these calls should destroy the reference so it can't be used again, but I don't think that fits with Swift's mutation model without turning `Unmanaged`/`UnsafeReference` into a reference type and adding lots of overhead.)

Yes, there's no way to reconcile that with the safety offered by the recommended usage patterns, since you can't mutate an rvalue.

I thought so. That's too bad. (I wonder if the compiler can emit warnings instead, though.)

I don't know what you have in mind here.

(One possibility would be to have a single call with an enum parameter, like `bridge(.Create)` and `bridge(.Get)`. This would let you use the regular form of the verb.)

There's no "bridging" going on here, though. This is simply "turn this unsafe thing into a safe thing in one of two ways"

The "bridge" here comes from the Objective-C bridging casts, but I think there it's meant to refer to toll-free bridging, which is not what's happening in Swift.

If the type name remains `Unmanaged`, then perhaps `manage(_:)` would be better? (I don't like `managing` here because that again implies it's side-effect-free and safe to call more than once.)

Well again, we're not asking the receiver, the UnsafeReference, to manage anything. And don't forget, we have two operations and need two names, especially if you want them to feel similar.

So far, my personal assessment of this direction is that it's no better than what I proposed, and has several weaknesses I'd like to avoid. In fact, it seems very similar to and roughly as understandable as the current Unmanaged design. I recognize that this is a highly subjective judgement, so if others disagree with me, I'd really like to hear about it. This is a tough design space and ultimately, what resonates best with the community is likely to be the best choice.

I understand. I'm obviously struggling with this too, as you can see from how much I'm changing my design based on your replies, rather than defending the design as suggested before.

Ultimately, Unmanaged is an API for handling an abstraction failure. That's inherently going to be tricky and subjective.

Yup.

-Dave

···

On Dec 19, 2015, at 4:22 PM, Brent Royal-Gordon <brent@architechies.com> wrote:

For the record, I have previously talked with Dave about this in person. The conclusion I came to was that "[foo release]" and "CFRelease(foo)" are saying "I release [my hold on] 'foo'", but std::unique_ptr::release <http://en.cppreference.com/w/cpp/memory/unique_ptr/release&gt; is saying "please release your referent to me". The directionality being different is what created so much cognitive dissonance for me.

I'm still one of those who really doesn't like plain 'release' as a name. Putting "release" in the name is fine (to Greg's point about CFBridgingRelease), but 'release' on its own has way too much baggage for me. (To the point where I have to double-check mentally that it's what I really want when I'm working with std::unique_ptr.) Maybe that's historical, though—a pure Swift programmer has never called CFRelease.

To Brent's point about putting "Create" somewhere in there: the other use of Unmanaged is "safely" getting managed references through 'void *' "context pointers". In this case you're balancing a retain you performed. (That said, I'm also against the general pattern of passing references through context pointers, because it's very hard to ensure that they get cleaned up, if retained, or kept alive, if unretained.)

Oh, and there's one last use of Unmanaged: fields of structs. These are rare but they do exist.

Jordan

P.S. There is, in fact, a CFBridgingRetain, for going from ObjC ARC to CF manual ref-counting. I'm not sure why Greg didn't mention it.

···

On Dec 19, 2015, at 19:43 , Dave Abrahams via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 19, 2015, at 4:22 PM, Brent Royal-Gordon <brent@architechies.com <mailto:brent@architechies.com>> wrote:

Mainly, because simply saying "release" or "released" is a bit ambiguous to me.Are you saying it *has been* released, or are you saying it *needs to be* released?

But nobody proposed "released" as a method name. In what way is "release" ambiguous? It's an imperative verb.

I guess you're right that "release" is unambiguous, but as you mentioned, it's also strange to release a value and then use it.

Yes. I think there are no really great choices here (at least not so far) so the question is whether that strangeness is enough of a problem to outweigh the points release() has in its favor. What do you think?

I think what I'm trying to get at here is that I prefer to think of the operations on Unmanaged as "explain to ARC how it should handle this object", rather than "do some manual operations so that ARC will do the right thing". Maybe the current Unmanaged design has shown the limitations of that approach, though.

Not at all; the Unmanaged design—at least in my best understanding of its intent—is firmly in the imperative/manual operations camp. I wanted to do something more declarative, but the "I want to manage the reference that I claim was passed to me at +1" operation is side-effectful. Are we really comfortable with hiding that fact?

But you applied "take" to both of them? One of them is idempotent while the other is not.

The preferred way to use Unmanaged is that you immediately convert it to a managed reference without ever storing it or using it in any other way. That means you should immediately call either the retain-and-return operation or the don't-retain-and-return operation. Both of these should only ever be called once. You may instead choose to keep the reference Unmanaged and manually retain, release, and access it, but best practices discourage that.

As I said in my original post, I'm ambivalent about the importance of highlighting the distinctions of safety and idempotence between these methods, but even if they're named similarly I don't see any merit in starting with "take." One thing I really dislike about is that the receiver, the UnsafeReference, isn't "taking" anything. The *caller* might be said to be taking something from the UnsafeReference, but only in the "returned at +1" case.

How I see it: along with the UnsafeReference the called CF function either notionally
a) gives (possibly-shared) ownership of the object directly to the caller, or
b) gives the caller a token that allows him to get (shared) ownership of the object

In case a), the caller needs to ask the UnsafeReference to transfer (or "release") that ownership into a strong reference, and. In case b), the caller needs to explicitly get (shared) ownership.

If this description doesn't sound right to you, please try to correct it; that may help me understand your perspective better.

Hi Dave,
Thanks for sharing the proposal. I finally had a chance to catch up with
the discussion.

Generally I like the proposal and it would serve my needs well.

I share others' concern that completely manual retain/release calls should
still be allowed in some way. This doesn't have to be via UnsafeReference,
but I don't think it makes sense to replace Unmanaged with UnsafeReference
as proposed without some solution to this.

Regarding naming:

problems with Unmanaged that we wanted to fix:
It was poorly-named (the reference is managed by somebody, we just aren't

representing that management in the type system).

I don't really agree with this. The management isn't "not represented" —
the Unmanaged type explicitly says, at least to me, "this particular
reference to the object does not manage its lifetime". So I don't think
UnsafeReference is particularly an improvement here, though I'm not against
it.

- The release() name makes sense to me; it behaves like
removeFirst/removeAtIndex. I'd also suggest something like
consumeReference(). And for manual retains, if that's in scope for this
API, you could use addReference().

Regarding COpaquePointer:

- Just to clarify, it looks like the proposed API would produce usage
patterns like:

    someCFunction(context: COpaquePointer(UnsafeReference(retaining: self)))

    func myCallback(context: COpaquePointer) {
        let object = UnsafeReference<Foo>(bitPattern: context).object
    }

    func myCleanup(context: COpaquePointer) {
        UnsafeReference<Foo>(bitPattern: context).release()
    }

- I'm curious why you chose a COpaquePointer initializer, rather than
toOpaque() as the current Unmanaged API provides? On my
UnsafePointer+Unmanaged proposal, you commented
<Proposal to change Unmanaged API to use UnsafePointer by jtbandes · Pull Request #44 · apple/swift-evolution · GitHub;
"we can’t give just UnsafePointer<Void> an init taking an UnsafeReference".
But (a) it sounds like this is an eventual goal (I started working on it
but it's a bit over my head currently); (b) using toOpaque() instead solves
this problem:

    func toOpaque() -> UnsafePointer<Void> {
        return unsafeBitCast(_storage, UnsafePointer<Void>.self)
    }
    ...
    someCFunction(context: UnsafeReference(retaining: self).toOpaque())

My motivation here is mostly that C void* APIs are currently bridged as
UnsafePointer<Void>, so APIs that produce COpaquePointer are an obstacle to
using them. If the trend is away from UnsafePointer and toward better
"Opaque" semantics, that's fine with me; I'd just like the API to be easy
to use in the meantime.

In the same comment you also said "pointers to Void and incomplete types
are not in any sense “unsafe” (once you restrict the interface as
appropriate for incomplete types), and so maybe we want OpaquePointer<T>
for these". It seems to me, though, that anything which can operate on
arbitrary memory addresses is indeed Unsafe.

Regarding documentation comments:

- It might be worth mentioning that init(retaining:) and
init(withoutRetaining:) are most likely to be used before conversion to an
opaque pointer, rather than immediately calling .object or .release() which
wouldn't be a very useful pattern.

- A weakness I see is that CF/other C APIs won't have comments saying which
"state" the returned UnsafeReference is in. The UnsafeReference doc
comments are very clear about which operations may be used when, but the
user is forced to mentally translate "…responsible for releasing…" into
"this reference is in the *retained* state" — a possible source of
confusion, unless all the CF doc comments can be reworded when they're
imported into Swift to be more explicit.

- Very minor: your doc comment on "public var object" has a stray/double `.
Similarly, the top-level comment has a stray/double ".

Jacob Bandes-Storch

···

On Sat, Dec 19, 2015 at 7:43 PM, Dave Abrahams via swift-evolution < swift-evolution@swift.org> wrote:

> On Dec 19, 2015, at 4:22 PM, Brent Royal-Gordon <brent@architechies.com> > wrote:
>
>>> Mainly, because simply saying "release" or "released" is a bit
ambiguous to me.Are you saying it *has been* released, or are you saying it
*needs to be* released?
>>
>> But nobody proposed "released" as a method name. In what way is
"release" ambiguous? It's an imperative verb.
>
> I guess you're right that "release" is unambiguous, but as you
mentioned, it's also strange to release a value and then use it.

Yes. I think there are no really great choices here (at least not so far)
so the question is whether that strangeness is enough of a problem to
outweigh the points release() has in its favor. What do you think?

> I think what I'm trying to get at here is that I prefer to think of the
operations on Unmanaged as "explain to ARC how it should handle this
object", rather than "do some manual operations so that ARC will do the
right thing". Maybe the current Unmanaged design has shown the limitations
of that approach, though.

Not at all; the Unmanaged design—at least in my best understanding of its
intent—is firmly in the imperative/manual operations camp. I wanted to do
something more declarative, but the "I want to manage the reference that I
claim was passed to me at +1" operation is side-effectful. Are we really
comfortable with hiding that fact?

>> But you applied "take" to both of them? One of them is idempotent
while the other is not.
>
> The preferred way to use Unmanaged is that you immediately convert it to
a managed reference without ever storing it or using it in any other way.
That means you should immediately call either the retain-and-return
operation or the don't-retain-and-return operation. Both of these should
only ever be called once. You may instead choose to keep the reference
Unmanaged and manually retain, release, and access it, but best practices
discourage that.

As I said in my original post, I'm ambivalent about the importance of
highlighting the distinctions of safety and idempotence between these
methods, but even if they're named similarly I don't see any merit in
starting with "take." One thing I really dislike about is that the
receiver, the UnsafeReference, isn't "taking" anything. The *caller* might
be said to be taking something from the UnsafeReference, but only in the
"returned at +1" case.

How I see it: along with the UnsafeReference the called CF function either
notionally
a) gives (possibly-shared) ownership of the object directly to the caller,
or
b) gives the caller a token that allows him to get (shared) ownership of
the object

In case a), the caller needs to ask the UnsafeReference to transfer (or
"release") that ownership into a strong reference, and. In case b), the
caller needs to explicitly get (shared) ownership.

If this description doesn't sound right to you, please try to correct it;
that may help me understand your perspective better.

> Now, one of the preferred, do-only-once operations *happens* to be safe
to apply more than once, but I view that as an implementation detail. Both
of them *happen* to be implemented in the same way as manual operations
(`manuallyRelease()` and `object`), but I view that as an implementation
detail, too.

Hm, well, I don't view `object` as a "manual operation" and there's value
in having a smaller API surface area. I don't think I want a separate
`manuallyRelease` method if there is another method that has the same
semantics. One of the greatest weaknesses of the current Unmanaged is that
its interface is too broad and hard to grasp.

> Honestly, I might be happier splitting an UnsafeReference type out of
Unmanaged and putting the manual retain/release stuff into that:

As noted in my original post, I really don't want to keep the name
"Unmanaged" for anything. If anything, it's "ManuallyManaged." And I am
very wary of API surface area creep here, whether it's in one type or two.

> // Unmanaged is a high-level type for moving object references in
and out of ARC's control.
> struct Unmanaged<T: class> {
> func created() -> T
> func gotten() -> T
>
> // Also would have stuff for passing, which I haven't even
thought about yet
> }
>
> // UnsafeReference is a low-level type for manually managing the
retain count of an object.
> struct UnsafeReference<T: class> {
> init(_ object: T)
> init(_ unmanaged: Unmanaged<T>)
>
> var object: T
>
> // Some or all of these might return T
> func retain()
> func release()
> func autorelease()
> }
>
> This puts the discouraged manual operations off in their own type where
they'll be available to those who know about them, but not sitting right
there on every unaudited call.
>
>>> (I kind of want to suggest that retrieving an object through these
calls should destroy the reference so it can't be used again, but I don't
think that fits with Swift's mutation model without turning
`Unmanaged`/`UnsafeReference` into a reference type and adding lots of
overhead.)
>>
>> Yes, there's no way to reconcile that with the safety offered by the
recommended usage patterns, since you can't mutate an rvalue.
>
> I thought so. That's too bad. (I wonder if the compiler can emit
warnings instead, though.)

I don't know what you have in mind here.

>
>>> (One possibility would be to have a single call with an enum
parameter, like `bridge(.Create)` and `bridge(.Get)`. This would let you
use the regular form of the verb.)
>>
>> There's no "bridging" going on here, though. This is simply "turn this
unsafe thing into a safe thing in one of two ways"
>
> The "bridge" here comes from the Objective-C bridging casts, but I think
there it's meant to refer to toll-free bridging, which is not what's
happening in Swift.
>
> If the type name remains `Unmanaged`, then perhaps `manage(_:)` would be
better? (I don't like `managing` here because that again implies it's
side-effect-free and safe to call more than once.)

Well again, we're not asking the receiver, the UnsafeReference, to manage
anything. And don't forget, we have two operations and need two names,
especially if you want them to feel similar.

>> So far, my personal assessment of this direction is that it's no better
than what I proposed, and has several weaknesses I'd like to avoid. In
fact, it seems very similar to and roughly as understandable as the current
Unmanaged design. I recognize that this is a highly subjective judgement,
so if others disagree with me, I'd really like to hear about it. This is a
tough design space and ultimately, what resonates best with the community
is likely to be the best choice.
>
> I understand. I'm obviously struggling with this too, as you can see
from how much I'm changing my design based on your replies, rather than
defending the design as suggested before.
>
> Ultimately, Unmanaged is an API for handling an abstraction failure.
That's inherently going to be tricky and subjective.

Yup.

-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

Floating an idea here—not sure if it’s even in the right ballpark, and I’m
certainly not tied to the specific wording, but what about something along
the lines of:

.transferByReleasing()
.transferWithoutReleasing() // or perhaps just .transfer()

Or the slightly-more-verbose:

.transferObjectByReleasingReference()
.transferObjectWithoutReleasingReference() // or .transferObject()

Nevin

···

On Sat, Dec 19, 2015 at 11:37 PM, Jacob Bandes-Storch via swift-evolution < swift-evolution@swift.org> wrote:

Hi Dave,
Thanks for sharing the proposal. I finally had a chance to catch up with
the discussion.

Generally I like the proposal and it would serve my needs well.

I share others' concern that completely manual retain/release calls should
still be allowed in some way. This doesn't have to be via UnsafeReference,
but I don't think it makes sense to replace Unmanaged with UnsafeReference
as proposed without some solution to this.

Regarding naming:

> problems with Unmanaged that we wanted to fix:
> It was poorly-named (the reference is managed by somebody, we just
aren't representing that management in the type system).

I don't really agree with this. The management isn't "not represented" —
the Unmanaged type explicitly says, at least to me, "this particular
reference to the object does not manage its lifetime". So I don't think
UnsafeReference is particularly an improvement here, though I'm not against
it.

- The release() name makes sense to me; it behaves like
removeFirst/removeAtIndex. I'd also suggest something like
consumeReference(). And for manual retains, if that's in scope for this
API, you could use addReference().

Regarding COpaquePointer:

- Just to clarify, it looks like the proposed API would produce usage
patterns like:

    someCFunction(context: COpaquePointer(UnsafeReference(retaining:
self)))

    func myCallback(context: COpaquePointer) {
        let object = UnsafeReference<Foo>(bitPattern: context).object
    }

    func myCleanup(context: COpaquePointer) {
        UnsafeReference<Foo>(bitPattern: context).release()
    }

- I'm curious why you chose a COpaquePointer initializer, rather than
toOpaque() as the current Unmanaged API provides? On my
UnsafePointer+Unmanaged proposal, you commented
<Proposal to change Unmanaged API to use UnsafePointer by jtbandes · Pull Request #44 · apple/swift-evolution · GitHub;
"we can’t give just UnsafePointer<Void> an init taking an UnsafeReference".
But (a) it sounds like this is an eventual goal (I started working on it
but it's a bit over my head currently); (b) using toOpaque() instead solves
this problem:

    func toOpaque() -> UnsafePointer<Void> {
        return unsafeBitCast(_storage, UnsafePointer<Void>.self)
    }
    ...
    someCFunction(context: UnsafeReference(retaining: self).toOpaque())

My motivation here is mostly that C void* APIs are currently bridged as
UnsafePointer<Void>, so APIs that produce COpaquePointer are an obstacle to
using them. If the trend is away from UnsafePointer and toward better
"Opaque" semantics, that's fine with me; I'd just like the API to be easy
to use in the meantime.

In the same comment you also said "pointers to Void and incomplete types
are not in any sense “unsafe” (once you restrict the interface as
appropriate for incomplete types), and so maybe we want OpaquePointer<T>
for these". It seems to me, though, that anything which can operate on
arbitrary memory addresses is indeed Unsafe.

Regarding documentation comments:

- It might be worth mentioning that init(retaining:) and
init(withoutRetaining:) are most likely to be used before conversion to an
opaque pointer, rather than immediately calling .object or .release() which
wouldn't be a very useful pattern.

- A weakness I see is that CF/other C APIs won't have comments saying
which "state" the returned UnsafeReference is in. The UnsafeReference doc
comments are very clear about which operations may be used when, but the
user is forced to mentally translate "…responsible for releasing…" into
"this reference is in the *retained* state" — a possible source of
confusion, unless all the CF doc comments can be reworded when they're
imported into Swift to be more explicit.

- Very minor: your doc comment on "public var object" has a stray/double
`. Similarly, the top-level comment has a stray/double ".

Jacob Bandes-Storch

On Sat, Dec 19, 2015 at 7:43 PM, Dave Abrahams via swift-evolution < > swift-evolution@swift.org> wrote:

> On Dec 19, 2015, at 4:22 PM, Brent Royal-Gordon <brent@architechies.com> >> wrote:
>
>>> Mainly, because simply saying "release" or "released" is a bit
ambiguous to me.Are you saying it *has been* released, or are you saying it
*needs to be* released?
>>
>> But nobody proposed "released" as a method name. In what way is
"release" ambiguous? It's an imperative verb.
>
> I guess you're right that "release" is unambiguous, but as you
mentioned, it's also strange to release a value and then use it.

Yes. I think there are no really great choices here (at least not so
far) so the question is whether that strangeness is enough of a problem to
outweigh the points release() has in its favor. What do you think?

> I think what I'm trying to get at here is that I prefer to think of the
operations on Unmanaged as "explain to ARC how it should handle this
object", rather than "do some manual operations so that ARC will do the
right thing". Maybe the current Unmanaged design has shown the limitations
of that approach, though.

Not at all; the Unmanaged design—at least in my best understanding of its
intent—is firmly in the imperative/manual operations camp. I wanted to do
something more declarative, but the "I want to manage the reference that I
claim was passed to me at +1" operation is side-effectful. Are we really
comfortable with hiding that fact?

>> But you applied "take" to both of them? One of them is idempotent
while the other is not.
>
> The preferred way to use Unmanaged is that you immediately convert it
to a managed reference without ever storing it or using it in any other
way. That means you should immediately call either the retain-and-return
operation or the don't-retain-and-return operation. Both of these should
only ever be called once. You may instead choose to keep the reference
Unmanaged and manually retain, release, and access it, but best practices
discourage that.

As I said in my original post, I'm ambivalent about the importance of
highlighting the distinctions of safety and idempotence between these
methods, but even if they're named similarly I don't see any merit in
starting with "take." One thing I really dislike about is that the
receiver, the UnsafeReference, isn't "taking" anything. The *caller* might
be said to be taking something from the UnsafeReference, but only in the
"returned at +1" case.

How I see it: along with the UnsafeReference the called CF function
either notionally
a) gives (possibly-shared) ownership of the object directly to the
caller, or
b) gives the caller a token that allows him to get (shared) ownership of
the object

In case a), the caller needs to ask the UnsafeReference to transfer (or
"release") that ownership into a strong reference, and. In case b), the
caller needs to explicitly get (shared) ownership.

If this description doesn't sound right to you, please try to correct it;
that may help me understand your perspective better.

> Now, one of the preferred, do-only-once operations *happens* to be safe
to apply more than once, but I view that as an implementation detail. Both
of them *happen* to be implemented in the same way as manual operations
(`manuallyRelease()` and `object`), but I view that as an implementation
detail, too.

Hm, well, I don't view `object` as a "manual operation" and there's value
in having a smaller API surface area. I don't think I want a separate
`manuallyRelease` method if there is another method that has the same
semantics. One of the greatest weaknesses of the current Unmanaged is that
its interface is too broad and hard to grasp.

> Honestly, I might be happier splitting an UnsafeReference type out of
Unmanaged and putting the manual retain/release stuff into that:

As noted in my original post, I really don't want to keep the name
"Unmanaged" for anything. If anything, it's "ManuallyManaged." And I am
very wary of API surface area creep here, whether it's in one type or two.

> // Unmanaged is a high-level type for moving object references in
and out of ARC's control.
> struct Unmanaged<T: class> {
> func created() -> T
> func gotten() -> T
>
> // Also would have stuff for passing, which I haven't
even thought about yet
> }
>
> // UnsafeReference is a low-level type for manually managing the
retain count of an object.
> struct UnsafeReference<T: class> {
> init(_ object: T)
> init(_ unmanaged: Unmanaged<T>)
>
> var object: T
>
> // Some or all of these might return T
> func retain()
> func release()
> func autorelease()
> }
>
> This puts the discouraged manual operations off in their own type where
they'll be available to those who know about them, but not sitting right
there on every unaudited call.
>
>>> (I kind of want to suggest that retrieving an object through these
calls should destroy the reference so it can't be used again, but I don't
think that fits with Swift's mutation model without turning
`Unmanaged`/`UnsafeReference` into a reference type and adding lots of
overhead.)
>>
>> Yes, there's no way to reconcile that with the safety offered by the
recommended usage patterns, since you can't mutate an rvalue.
>
> I thought so. That's too bad. (I wonder if the compiler can emit
warnings instead, though.)

I don't know what you have in mind here.

>
>>> (One possibility would be to have a single call with an enum
parameter, like `bridge(.Create)` and `bridge(.Get)`. This would let you
use the regular form of the verb.)
>>
>> There's no "bridging" going on here, though. This is simply "turn
this unsafe thing into a safe thing in one of two ways"
>
> The "bridge" here comes from the Objective-C bridging casts, but I
think there it's meant to refer to toll-free bridging, which is not what's
happening in Swift.
>
> If the type name remains `Unmanaged`, then perhaps `manage(_:)` would
be better? (I don't like `managing` here because that again implies it's
side-effect-free and safe to call more than once.)

Well again, we're not asking the receiver, the UnsafeReference, to manage
anything. And don't forget, we have two operations and need two names,
especially if you want them to feel similar.

>> So far, my personal assessment of this direction is that it's no
better than what I proposed, and has several weaknesses I'd like to avoid.
In fact, it seems very similar to and roughly as understandable as the
current Unmanaged design. I recognize that this is a highly subjective
judgement, so if others disagree with me, I'd really like to hear about
it. This is a tough design space and ultimately, what resonates best with
the community is likely to be the best choice.
>
> I understand. I'm obviously struggling with this too, as you can see
from how much I'm changing my design based on your replies, rather than
defending the design as suggested before.
>
> Ultimately, Unmanaged is an API for handling an abstraction failure.
That's inherently going to be tricky and subjective.

Yup.

-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

There's still the "release" issue (std::unique_ptr::release versus -[NSObject release]), but "transfer" seems like a good word to me. What about "transferByRetaining" and "transferWithoutRetaining"?

Félix

···

Le 20 déc. 2015 à 00:01:22, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org> a écrit :

Floating an idea here—not sure if it’s even in the right ballpark, and I’m certainly not tied to the specific wording, but what about something along the lines of:

.transferByReleasing()
.transferWithoutReleasing() // or perhaps just .transfer()

Or the slightly-more-verbose:

.transferObjectByReleasingReference()
.transferObjectWithoutReleasingReference() // or .transferObject()

Nevin

On Sat, Dec 19, 2015 at 11:37 PM, Jacob Bandes-Storch via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:
Hi Dave,
Thanks for sharing the proposal. I finally had a chance to catch up with the discussion.

Generally I like the proposal and it would serve my needs well.

I share others' concern that completely manual retain/release calls should still be allowed in some way. This doesn't have to be via UnsafeReference, but I don't think it makes sense to replace Unmanaged with UnsafeReference as proposed without some solution to this.

Regarding naming:

> problems with Unmanaged that we wanted to fix:
> It was poorly-named (the reference is managed by somebody, we just aren't representing that management in the type system).

I don't really agree with this. The management isn't "not represented" — the Unmanaged type explicitly says, at least to me, "this particular reference to the object does not manage its lifetime". So I don't think UnsafeReference is particularly an improvement here, though I'm not against it.

- The release() name makes sense to me; it behaves like removeFirst/removeAtIndex. I'd also suggest something like consumeReference(). And for manual retains, if that's in scope for this API, you could use addReference().

Regarding COpaquePointer:

- Just to clarify, it looks like the proposed API would produce usage patterns like:

    someCFunction(context: COpaquePointer(UnsafeReference(retaining: self)))

    func myCallback(context: COpaquePointer) {
        let object = UnsafeReference<Foo>(bitPattern: context).object
    }

    func myCleanup(context: COpaquePointer) {
        UnsafeReference<Foo>(bitPattern: context).release()
    }

- I'm curious why you chose a COpaquePointer initializer, rather than toOpaque() as the current Unmanaged API provides? On my UnsafePointer+Unmanaged proposal, you commented <Proposal to change Unmanaged API to use UnsafePointer by jtbandes · Pull Request #44 · apple/swift-evolution · GitHub; "we can’t give just UnsafePointer<Void> an init taking an UnsafeReference". But (a) it sounds like this is an eventual goal (I started working on it but it's a bit over my head currently); (b) using toOpaque() instead solves this problem:

    func toOpaque() -> UnsafePointer<Void> {
        return unsafeBitCast(_storage, UnsafePointer<Void>.self)
    }
    ...
    someCFunction(context: UnsafeReference(retaining: self).toOpaque())

My motivation here is mostly that C void* APIs are currently bridged as UnsafePointer<Void>, so APIs that produce COpaquePointer are an obstacle to using them. If the trend is away from UnsafePointer and toward better "Opaque" semantics, that's fine with me; I'd just like the API to be easy to use in the meantime.

In the same comment you also said "pointers to Void and incomplete types are not in any sense “unsafe” (once you restrict the interface as appropriate for incomplete types), and so maybe we want OpaquePointer<T> for these". It seems to me, though, that anything which can operate on arbitrary memory addresses is indeed Unsafe.

Regarding documentation comments:

- It might be worth mentioning that init(retaining:) and init(withoutRetaining:) are most likely to be used before conversion to an opaque pointer, rather than immediately calling .object or .release() which wouldn't be a very useful pattern.

- A weakness I see is that CF/other C APIs won't have comments saying which "state" the returned UnsafeReference is in. The UnsafeReference doc comments are very clear about which operations may be used when, but the user is forced to mentally translate "…responsible for releasing…" into "this reference is in the *retained* state" — a possible source of confusion, unless all the CF doc comments can be reworded when they're imported into Swift to be more explicit.

- Very minor: your doc comment on "public var object" has a stray/double `. Similarly, the top-level comment has a stray/double ".

Jacob Bandes-Storch

On Sat, Dec 19, 2015 at 7:43 PM, Dave Abrahams via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> wrote:

> On Dec 19, 2015, at 4:22 PM, Brent Royal-Gordon <brent@architechies.com <mailto:brent@architechies.com>> wrote:
>
>>> Mainly, because simply saying "release" or "released" is a bit ambiguous to me.Are you saying it *has been* released, or are you saying it *needs to be* released?
>>
>> But nobody proposed "released" as a method name. In what way is "release" ambiguous? It's an imperative verb.
>
> I guess you're right that "release" is unambiguous, but as you mentioned, it's also strange to release a value and then use it.

Yes. I think there are no really great choices here (at least not so far) so the question is whether that strangeness is enough of a problem to outweigh the points release() has in its favor. What do you think?

> I think what I'm trying to get at here is that I prefer to think of the operations on Unmanaged as "explain to ARC how it should handle this object", rather than "do some manual operations so that ARC will do the right thing". Maybe the current Unmanaged design has shown the limitations of that approach, though.

Not at all; the Unmanaged design—at least in my best understanding of its intent—is firmly in the imperative/manual operations camp. I wanted to do something more declarative, but the "I want to manage the reference that I claim was passed to me at +1" operation is side-effectful. Are we really comfortable with hiding that fact?

>> But you applied "take" to both of them? One of them is idempotent while the other is not.
>
> The preferred way to use Unmanaged is that you immediately convert it to a managed reference without ever storing it or using it in any other way. That means you should immediately call either the retain-and-return operation or the don't-retain-and-return operation. Both of these should only ever be called once. You may instead choose to keep the reference Unmanaged and manually retain, release, and access it, but best practices discourage that.

As I said in my original post, I'm ambivalent about the importance of highlighting the distinctions of safety and idempotence between these methods, but even if they're named similarly I don't see any merit in starting with "take." One thing I really dislike about is that the receiver, the UnsafeReference, isn't "taking" anything. The *caller* might be said to be taking something from the UnsafeReference, but only in the "returned at +1" case.

How I see it: along with the UnsafeReference the called CF function either notionally
a) gives (possibly-shared) ownership of the object directly to the caller, or
b) gives the caller a token that allows him to get (shared) ownership of the object

In case a), the caller needs to ask the UnsafeReference to transfer (or "release") that ownership into a strong reference, and. In case b), the caller needs to explicitly get (shared) ownership.

If this description doesn't sound right to you, please try to correct it; that may help me understand your perspective better.

> Now, one of the preferred, do-only-once operations *happens* to be safe to apply more than once, but I view that as an implementation detail. Both of them *happen* to be implemented in the same way as manual operations (`manuallyRelease()` and `object`), but I view that as an implementation detail, too.

Hm, well, I don't view `object` as a "manual operation" and there's value in having a smaller API surface area. I don't think I want a separate `manuallyRelease` method if there is another method that has the same semantics. One of the greatest weaknesses of the current Unmanaged is that its interface is too broad and hard to grasp.

> Honestly, I might be happier splitting an UnsafeReference type out of Unmanaged and putting the manual retain/release stuff into that:

As noted in my original post, I really don't want to keep the name "Unmanaged" for anything. If anything, it's "ManuallyManaged." And I am very wary of API surface area creep here, whether it's in one type or two.

> // Unmanaged is a high-level type for moving object references in and out of ARC's control.
> struct Unmanaged<T: class> {
> func created() -> T
> func gotten() -> T
>
> // Also would have stuff for passing, which I haven't even thought about yet
> }
>
> // UnsafeReference is a low-level type for manually managing the retain count of an object.
> struct UnsafeReference<T: class> {
> init(_ object: T)
> init(_ unmanaged: Unmanaged<T>)
>
> var object: T
>
> // Some or all of these might return T
> func retain()
> func release()
> func autorelease()
> }
>
> This puts the discouraged manual operations off in their own type where they'll be available to those who know about them, but not sitting right there on every unaudited call.
>
>>> (I kind of want to suggest that retrieving an object through these calls should destroy the reference so it can't be used again, but I don't think that fits with Swift's mutation model without turning `Unmanaged`/`UnsafeReference` into a reference type and adding lots of overhead.)
>>
>> Yes, there's no way to reconcile that with the safety offered by the recommended usage patterns, since you can't mutate an rvalue.
>
> I thought so. That's too bad. (I wonder if the compiler can emit warnings instead, though.)

I don't know what you have in mind here.

>
>>> (One possibility would be to have a single call with an enum parameter, like `bridge(.Create)` and `bridge(.Get)`. This would let you use the regular form of the verb.)
>>
>> There's no "bridging" going on here, though. This is simply "turn this unsafe thing into a safe thing in one of two ways"
>
> The "bridge" here comes from the Objective-C bridging casts, but I think there it's meant to refer to toll-free bridging, which is not what's happening in Swift.
>
> If the type name remains `Unmanaged`, then perhaps `manage(_:)` would be better? (I don't like `managing` here because that again implies it's side-effect-free and safe to call more than once.)

Well again, we're not asking the receiver, the UnsafeReference, to manage anything. And don't forget, we have two operations and need two names, especially if you want them to feel similar.

>> So far, my personal assessment of this direction is that it's no better than what I proposed, and has several weaknesses I'd like to avoid. In fact, it seems very similar to and roughly as understandable as the current Unmanaged design. I recognize that this is a highly subjective judgement, so if others disagree with me, I'd really like to hear about it. This is a tough design space and ultimately, what resonates best with the community is likely to be the best choice.
>
> I understand. I'm obviously struggling with this too, as you can see from how much I'm changing my design based on your replies, rather than defending the design as suggested before.
>
> Ultimately, Unmanaged is an API for handling an abstraction failure. That's inherently going to be tricky and subjective.

Yup.

-Dave

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org <mailto:swift-evolution@swift.org>
https://lists.swift.org/mailman/listinfo/swift-evolution

I like "transfer" but I think this would be mostly helpful to people familiar with manual ref. counting in Obj-C.
It's probably just as confusing to others and the visual similarity could be confusing as well (like with `Unmanaged`).

But I also wouldn't be opposed to these if they were selected...

···

On 20 Dec 2015, at 06:56, Félix Cloutier via swift-evolution <swift-evolution@swift.org> wrote:

There's still the "release" issue (std::unique_ptr::release versus -[NSObject release]), but "transfer" seems like a good word to me. What about "transferByRetaining" and "transferWithoutRetaining"?

Félix

Le 20 déc. 2015 à 00:01:22, Nevin Brackett-Rozinsky via swift-evolution <swift-evolution@swift.org <mailto:swift-evolution@swift.org>> a écrit :

Floating an idea here—not sure if it’s even in the right ballpark, and I’m certainly not tied to the specific wording, but what about something along the lines of:

.transferByReleasing()
.transferWithoutReleasing() // or perhaps just .transfer()

Or the slightly-more-verbose:

.transferObjectByReleasingReference()
.transferObjectWithoutReleasingReference() // or .transferObject()

Nevin

- Janosch