[swift-evolution] Pre-proposal: Convert reference params to tuples
Charles Srstka
cocoadev at charlessoft.com
Tue Dec 22 13:50:25 CST 2015
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;
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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151222/396641c8/attachment-0001.html>
More information about the swift-evolution
mailing list