[swift-evolution] Pre-proposal: Convert reference params to tuples

Philippe Hausler phausler at apple.com
Mon Jan 11 20:05:29 CST 2016


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.

> On Dec 22, 2015, at 11:50 AM, Charles Srstka via swift-evolution <swift-evolution at swift.org> wrote:
> 
> 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 <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.

>  
> 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 at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160111/058da5af/attachment.html>


More information about the swift-evolution mailing list