Pre-proposal: Convert reference params to tuples


(Charles Srstka) #1

This is a re-post of a proposal I previously submitted to Radar. This seems like a more appropriate place for it, though, and since Apple seems to be planning a heuristic mechanism to rewrite Objective-C APIs to make them more idiomatic to Swift (https://github.com/apple/swift-evolution/blob/master/proposals/0005-objective-c-name-translation.md), I thought I’d bounce this by the list.

Motivation:
Swift's system of getting rid of NSError ** parameters and turning them into something easier to deal with is great. However, NSError is not the only possible type of reference parameter that can appear in C and Objective-C APIs. Some extremely common APIs in the frameworks require passing C-style pointers, including the popular -[NSURL getResourceValue:forKey:error:]. These reference pointers are currently exposed as ugly UnsafeMutablePointer or AutoreleasingUnsafeMutablePointer constructs that seem quite out of place in a Swift public API.

Proposed Solution:

If an Objective-C method takes a reference parameter, and that parameter is marked "out" in the declaration, this guarantees that the parameter is purely for returning a value, and that its initial value will be ignored. Thus, we can eliminate the parameter entirely and move it to the method's return value. If the Objective-C method already has a return value, we can accommodate both return values by having the method return a tuple. So, something like this (assuming nonnull):

- (NSString *)foo:(out NSString **)bar;

becomes something like this:

func foo() -> (String, String)

The Obj-C return value, if there is one, would always be the first element in the tuple, accessible via .0. For methods using the common Obj-C naming conventions for methods that return values by reference, the other elements in the tuple could be named. In this case, "get<name>" would remain the name of the method, but additional by-reference parameters and their names could be removed from the method name completely and moved to the return tuple. In both cases the argument label could be used to determine the name, like so:

- (void)getFoo:(out NSString **)foo bar:(out NSString **)bar;
- (NSString *)fooWithBar:(NSString *)bar baz:(out NSString **)baz;
- (NSString *)fooAndReturnBar:(out NSString **)bar;

become:

func getFoo() -> (foo: String, bar: String)
func fooWithBar(bar: String) -> (String, baz: String)
func foo() -> (String, bar: String)

Methods that have void returns (or which have Boolean returns and an error parameter, which Swift will turn into a void return) don't even need a tuple:

- (void)foo:(out NSString **)bar;

becomes

func foo() -> String

Furthermore, something like -[NSURL getResourceValue:forKey:error:] becomes this:

func getResourceValueForKey() throws -> AnyObject?

so that instead of this rather Byzantine-looking construction:

var sizeObj: AnyObject? = nil
  
try url.getResourceValue(&sizeObj, forKey: NSURLFileSizeKey)
  
if size = sizeObj as? NSNumber {
   // do something with size
}

you could just do this:

if let size = try url.getResourceValueForKey(NSURLFileSizeKey) as? NSNumber {
    // do something with size
}

So much cleaner, and generally more "swifty"!

The beauty of it all is that we don't even have to invent a new keyword for this, since Obj-C already has an "out" keyword (which was originally there for use with Distributed Objects, but I see no reason we couldn't repurpose it here). Many APIs, such as -[NSURL getResourceValue:forKey:error:] mentioned above, already use it. We could even wrap the call in an autoreleasepool to get rid of the autorelease on the returned-by-reference values, if the performance trade-off is deemed to be worth it.

One possible objection could be raised regarding methods that can take NULL as the reference parameter, and skip doing the work to generate that value in this case; one could argue that the proposed change could make such methods inefficient in cases where you don't want one of the values. However, assuming the parameter is nullable, we could account for this as well by assigning one of the return values to _, like this:

let (foo, bar: _) = someMethod()

or:

let (foo, _) = someMethod()

and, seeing that a particular return value is not needed, Swift could pass NULL for the undesired reference parameter. (If the pointer were non-nullable, Swift would send it an actual pointer and simply ignore the result).

Impact on Existing Code:

The impact is similar to the existing, accepted Objective-C translation proposal. It will break lots of existing use of Objective-C code, but since that is happening soon anyway, this seems like an appropriate time to consider other things such as this.

Further Discussion:

The proposal above discussed mainly Objective-C code; however, the logical next step may be to extend it to C as well. This would dramatically simplify the CoreFoundation interface as well, since that API relies extremely heavily on reference parameters to return things, with the actual return type often simply being a Boolean or an OSStatus. This would probably be more complicated, however, since you wouldn’t necessarily have the advantage of the naming conventions that exist in Objective-C for this type of API.

Charles


(Tino) #2

Imho it sounds good, but I rarely encounter situations where I would benefit from the proposed change… and afair "out" is an Objective-C addition, so there would be no improvement for plain C.
So I would surely find use for the automatic conversion, but if I would have to write it on my own, I'd rather take the lazy solution and stick with the status quo and write wrappers.


(Philippe Hausler) #3

We have put in a few experimental APIs into the swift-corelibs-foundation that try to remove some of these ugly spots.

I really thing that almost all cases of AutoreleasingUnsafeMutablePointer are either problematic or potentially incorrect (not to mention potentially unsafe). Most of these cases are really just a case of wanting an out parameter to the method or a fast array of elements. The use case of the fast array of elements is of dubious usage in swift (unless you are implementing Foundation itself), and there seem to be better accessors anyhow e.g. NSArray.getObjects(_:range:) perhaps should be subarrayWithRange or slice as a better usage case. Some of the NSCalendar APIs that had AUMPs have been replaced with experiments that are not only potentially vastly better Swift APIs but also potentially better ObjC APIs (win/win in my book).

The remainder seem to be cases of an out parameter. Granted ObjC does have the annotation of out (which is from distributed objects iirc) but if we had some sort of demarkation of this intent the APIs could be exposed as inout without mucking with potentially pitfall cases of misinterpreting the meaning of the API names.

If there are other APIs that are potentially pitfalls of this I would definitely be interested to see how they could be improved.

This is a re-post of a proposal I previously submitted to Radar. This seems like a more appropriate place for it, though, and since Apple seems to be planning a heuristic mechanism to rewrite Objective-C APIs to make them more idiomatic to Swift (https://github.com/apple/swift-evolution/blob/master/proposals/0005-objective-c-name-translation.md), I thought I’d bounce this by the list.

Motivation:
Swift's system of getting rid of NSError ** parameters and turning them into something easier to deal with is great. However, NSError is not the only possible type of reference parameter that can appear in C and Objective-C APIs. Some extremely common APIs in the frameworks require passing C-style pointers, including the popular -[NSURL getResourceValue:forKey:error:]. These reference pointers are currently exposed as ugly UnsafeMutablePointer or AutoreleasingUnsafeMutablePointer constructs that seem quite out of place in a Swift public API.

Proposed Solution:

If an Objective-C method takes a reference parameter, and that parameter is marked "out" in the declaration, this guarantees that the parameter is purely for returning a value, and that its initial value will be ignored. Thus, we can eliminate the parameter entirely and move it to the method's return value. If the Objective-C method already has a return value, we can accommodate both return values by having the method return a tuple. So, something like this (assuming nonnull):

- (NSString *)foo:(out NSString **)bar;

Cases like this are kinda cagey because you have to keep track of which string is which; is it the foo result string or the bar result string at .0 versus .1? I find myself always favoring struct or inout for clarity/safety.

···

On Dec 22, 2015, at 11:50 AM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:

becomes something like this:

func foo() -> (String, String)

The Obj-C return value, if there is one, would always be the first element in the tuple, accessible via .0. For methods using the common Obj-C naming conventions for methods that return values by reference, the other elements in the tuple could be named. In this case, "get<name>" would remain the name of the method, but additional by-reference parameters and their names could be removed from the method name completely and moved to the return tuple. In both cases the argument label could be used to determine the name, like so:

- (void)getFoo:(out NSString **)foo bar:(out NSString **)bar;
- (NSString *)fooWithBar:(NSString *)bar baz:(out NSString **)baz;
- (NSString *)fooAndReturnBar:(out NSString **)bar;

become:

func getFoo() -> (foo: String, bar: String)
func fooWithBar(bar: String) -> (String, baz: String)
func foo() -> (String, bar: String)

Methods that have void returns (or which have Boolean returns and an error parameter, which Swift will turn into a void return) don't even need a tuple:

- (void)foo:(out NSString **)bar;

becomes

func foo() -> String

Furthermore, something like -[NSURL getResourceValue:forKey:error:] becomes this:

func getResourceValueForKey() throws -> AnyObject?

so that instead of this rather Byzantine-looking construction:

var sizeObj: AnyObject? = nil
  
try url.getResourceValue(&sizeObj, forKey: NSURLFileSizeKey)
  
if size = sizeObj as? NSNumber {
   // do something with size
}

you could just do this:

if let size = try url.getResourceValueForKey(NSURLFileSizeKey) as? NSNumber {
    // do something with size
}

So much cleaner, and generally more "swifty"!

The beauty of it all is that we don't even have to invent a new keyword for this, since Obj-C already has an "out" keyword (which was originally there for use with Distributed Objects, but I see no reason we couldn't repurpose it here). Many APIs, such as -[NSURL getResourceValue:forKey:error:] mentioned above, already use it. We could even wrap the call in an autoreleasepool to get rid of the autorelease on the returned-by-reference values, if the performance trade-off is deemed to be worth it.

One possible objection could be raised regarding methods that can take NULL as the reference parameter, and skip doing the work to generate that value in this case; one could argue that the proposed change could make such methods inefficient in cases where you don't want one of the values. However, assuming the parameter is nullable, we could account for this as well by assigning one of the return values to _, like this:

let (foo, bar: _) = someMethod()

or:

let (foo, _) = someMethod()

and, seeing that a particular return value is not needed, Swift could pass NULL for the undesired reference parameter. (If the pointer were non-nullable, Swift would send it an actual pointer and simply ignore the result).

Impact on Existing Code:

The impact is similar to the existing, accepted Objective-C translation proposal. It will break lots of existing use of Objective-C code, but since that is happening soon anyway, this seems like an appropriate time to consider other things such as this.

Further Discussion:

The proposal above discussed mainly Objective-C code; however, the logical next step may be to extend it to C as well. This would dramatically simplify the CoreFoundation interface as well, since that API relies extremely heavily on reference parameters to return things, with the actual return type often simply being a Boolean or an OSStatus. This would probably be more complicated, however, since you wouldn’t necessarily have the advantage of the naming conventions that exist in Objective-C for this type of API.

Charles

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


(D. Felipe Torres) #4

+ 1 on the automatic translation. Let's get Swifty.

···

On Wed, Dec 23, 2015 at 12:11 PM, Tino Heth via swift-evolution < swift-evolution@swift.org> wrote:

Imho it sounds good, but I rarely encounter situations where I would
benefit from the proposed change… and afair "out" is an Objective-C
addition, so there would be no improvement for plain C.
So I would surely find use for the automatic conversion, but if I would
have to write it on my own, I'd rather take the lazy solution and stick
with the status quo and write wrappers.
_______________________________________________
swift-evolution mailing list
swift-evolution@swift.org
https://lists.swift.org/mailman/listinfo/swift-evolution

--
++++++++++++++++++++++++++
Diego Torres.
Phone (Mobile Germany): +49 157 30070985
Phone (Landline Chile): +56 2 29790978
Web: dtorres.me


(Charles Srstka) #5

Imho it sounds good, but I rarely encounter situations where I would benefit from the proposed change…

It does happen in the Objective-C frameworks; the NSURL example I provided is an aggravatingly common one, but there are plenty others (particularly anything that begins with “get”). In C, of course, it’s all over the place.

It would be handy for third-party code, as well. I’m currently converting a class hierarchy from Obj-C to Swift that returns multiple values, but since this code still has to be interoperable with Obj-C (for now), I can’t move it to returning a tuple yet, and all these UnsafeMutablePointers are driving me *crazy*.

and afair "out" is an Objective-C addition, so there would be no improvement for plain C.

While that’s true, Apple could use the same sort of solution that they have done in many other places; just use #ifdef checks to #define some constant like NS_OUT to ‘out’ for Objective-C, and to an empty string otherwise.

Charles

···

On Dec 23, 2015, at 5:11 AM, Tino Heth <2th@gmx.de> wrote:


(Charles Srstka) #6

In the case where the original API distinguishes between the two (-fooAndReturnBar:), you can return a named (or partially-named) tuple, like in one my later examples. In this case, the original API doesn’t have this information either, so we can’t translate it into something better (although I don’t believe this pattern is very common, so that helps).

In the more common case where you have something like fooWithBar: you can add a “bar:” label on the second parameter.

Charles

···

On Jan 11, 2016, at 8:05 PM, Philippe Hausler <phausler@apple.com> wrote:

Cases like this are kinda cagey because you have to keep track of which string is which; is it the foo result string or the bar result string at .0 versus .1? I find myself always favoring struct or inout for clarity/safety.


(Kenny Leung) #7

How would this work on the stack? I imagine returning a value in a tuple would make it a completely different location on the stack than where the original parameter would be where the ObjC side would be expecting it. This would break the calling convention.

-Kenny

···

On Dec 23, 2015, at 12:12 PM, Charles Srstka via swift-evolution <swift-evolution@swift.org> wrote:

On Dec 23, 2015, at 5:11 AM, Tino Heth <2th@gmx.de> wrote:

Imho it sounds good, but I rarely encounter situations where I would benefit from the proposed change…

It does happen in the Objective-C frameworks; the NSURL example I provided is an aggravatingly common one, but there are plenty others (particularly anything that begins with “get”). In C, of course, it’s all over the place.

It would be handy for third-party code, as well. I’m currently converting a class hierarchy from Obj-C to Swift that returns multiple values, but since this code still has to be interoperable with Obj-C (for now), I can’t move it to returning a tuple yet, and all these UnsafeMutablePointers are driving me *crazy*.

and afair "out" is an Objective-C addition, so there would be no improvement for plain C.

While that’s true, Apple could use the same sort of solution that they have done in many other places; just use #ifdef checks to #define some constant like NS_OUT to ‘out’ for Objective-C, and to an empty string otherwise.

Charles

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


(Brent Royal-Gordon) #8

I’m currently converting a class hierarchy from Obj-C to Swift that returns multiple values, but since this code still has to be interoperable with Obj-C (for now), I can’t move it to returning a tuple yet, and all these UnsafeMutablePointers are driving me *crazy*.

As an implementation strategy, you might want to consider writing the Swift interface you want to support, and then declaring Objective-C-compatible cover methods matching the old interface. You can even get the Obj-C ones out of the way in autocomplete by giving them a common prefix:

  @nonobjc func getFooAndBar() -> (Int, Int) {
    // Insert logic here
  }

  @objc(getFoo:andBar:) func _objc_getFoo(foo: UnsafeMutablePointer<Int>, andBar bar: UnsafeMutablePointer<Int>) {
    (foo.memory, bar.memory) = getFooAndBar()
  }

Once your code is all ported and using the Swifty interface, you can remove these cover methods. Or if they need to stay around, you could move them into extensions so they'll stay out of your way.

···

--
Brent Royal-Gordon
Architechies


(Charles Srstka) #9

My thinking is that the Swift compiler would create a temporary variable, then pass a pointer to that to the Objective-C method. After the method is complete, the temporary variable would be copied into the tuple, and converted as needed.

You pretty much have to do it this way, because of the disparities between the types on the Swift side and the Objective-C side: Bool vs. BOOL, String, vs. NSString, etc. To ensure a good user experience, these things all need to be translated to their Swift counterparts before returning them.

Charles

···

On Jan 9, 2016, at 7:52 PM, Kenny Leung via swift-evolution <swift-evolution@swift.org> wrote:

How would this work on the stack? I imagine returning a value in a tuple would make it a completely different location on the stack than where the original parameter would be where the ObjC side would be expecting it. This would break the calling convention.

-Kenny