One final post on upconversion of non-optional values to optional. As we saw previously, if you pass a non-optional value to a function argument that expects an optional, it will get automatically converted into an optional by the compiler.
Ken Ferry points out something that goes one step further. If a function takes an argument of a function that returns an optional, and you pass into it a function that does not return an optional, the function will automatically be converted to return an optional.
That is:
func foo()->Int { return 1 } func bar(fun: ()->Int?) { println(fun()) } // Even though foo returns an Int, // you can pass it to bar. // Inside bar it will return an Int?, // so this prints "Optional(1)" bar(foo)
What’s more, you can even store or return the converted function:
// alter bar to return the passed-in function func bar(fun: ()->Int?)->()->Int? { return fun } var i = 0 let f = { ++i } f() // returns 1 f() // returns 2 let g = bar(f) g() // returns {Some 3} g() // returns {Some 4} f() // returns 5
This feature makes sense when you think about it in the context of other implicit conversions – if you can pass non-optional values in to functions and have them implicitly converted to optionals, the logical next step is to be able to do the same for the return value of functions you pass in as parameters.
This only works on the return value of the function, though. The following won’t compile:
func foo(i: Int)->Int { return i } func bar(fun: (Int?)->Int?) { println(fun(1)) } // won't compile - no implicit conversion // of foo's argument to an Int? bar(foo)
This is because foo
is expecting a non-nil argument – it wouldn’t know how handle nil
if it received one. However, what would compile is if foo
could handle an optional, but bar
was expecting a function with a non-optional argument:
func foo(i: Int?)->Int { return i ?? 0 } func bar(fun: (Int)->Int?) { println(fun(1)) } // will compile - implicit conversion // of foo's argument to an Int bar(foo)
This works because any call by bar
of foo
could convert the non-optional parameter it passes in to an optional implicitly. (thanks to @westacular and @jvasileff for pointing this out)
Just like you can think of passing non-optionals into optional arguments as the compiler automatically wrapping your values in an optional, you can think of the compiler silently wrapping your non-optional-returning function in a closure that calls your function and returns its value wrapped in an optional:
func foo()->Int { return 1 } func bar(fun: ()->Int?) { println(fun()) } // to do an explicit equivalent // of the compiler's conversion: bar( { Optional(foo()) } )
In practice, the compiler probably does something a bit more low-level. If you wrote the above code, and then stepped through it with the debugger, and you put a breakpoint inside foo
, you’d see an extra entry in the stack trace between foo
and bar
showing the in-between closure. But if you leave the compiler to do it implicitly, you’ll see no such entry.
If you’re interested in digging a bit further into exactly where the compiler is doing the conversion, you can use a feature of the Swift compiler to dump the syntax tree. Here is a dump from the first piece of code in this article:1
% xcrun swiftc -dump-ast main.swift (source_file (func_decl "foo()" type='() -> Int' access=internal (body_params (pattern_tuple type='()')) (result (type_ident (component id='Int' bind=type))) (brace_stmt (return_stmt (call_expr implicit type='Int' location=main.swift:2:12 range=[main.swift:2:12 - line:2:12] (dot_syntax_call_expr type='(Int2048) -> Int' location=main.swift:2:12 range=[main.swift:2:12 - line:2:12] (declref_expr implicit type='Int.Type -> (Int2048) -> Int' location=main.swift:2:12 range=[main.swift:2:12 - line:2:12] decl=Swift.(file).Int._convertFromBuiltinIntegerLiteral specialized=no) (type_expr implicit type='Int.Type' location=main.swift:2:12 range=[main.swift:2:12 - line:2:12] typerepr='<<IMPLICIT>>')) (integer_literal_expr type='Int2048' location=main.swift:2:12 range=[main.swift:2:12 - line:2:12] value=1))))) (func_decl "bar(_:)" type='(() -> Int?) -> ()' access=internal (body_params (pattern_tuple type='(fun: () -> Int?)' (pattern_typed type='() -> Int?' (pattern_named type='() -> Int?' 'fun') (type_function (type_tuple) )))) (brace_stmt (call_expr type='()' location=main.swift:6:5 range=[main.swift:6:5 - line:6:18] (declref_expr type='(Int?) -> ()' location=main.swift:6:5 range=[main.swift:6:5 - line:6:5] decl=Swift.(file).println [with T=Int?] specialized=no) (paren_expr type='(Int?)' location=main.swift:6:13 range=[main.swift:6:12 - line:6:18] (call_expr type='Int?' location=main.swift:6:13 range=[main.swift:6:13 - line:6:17] (declref_expr type='() -> Int?' location=main.swift:6:13 range=[main.swift:6:13 - line:6:13] decl=main.(file).func decl.fun@main.swift:5:10 specialized=no) (tuple_expr type='()' location=main.swift:6:16 range=[main.swift:6:16 - line:6:17])))))) (top_level_code_decl (brace_stmt (call_expr type='()' location=main.swift:9:1 range=[main.swift:9:1 - line:9:8] (declref_expr type='(() -> Int?) -> ()' location=main.swift:9:1 range=[main.swift:9:1 - line:9:1] decl=main.(file).bar@main.swift:5:6 specialized=no) (paren_expr type='(() -> Int?)' location=main.swift:9:5 range=[main.swift:9:4 - line:9:8] (function_conversion_expr implicit type='() -> Int?' location=main.swift:9:5 range=[main.swift:9:5 - line:9:5] (declref_expr type='() -> Int' location=main.swift:9:5 range=[main.swift:9:5 - line:9:5] decl=main.(file).foo@main.swift:1:6 specialized=no))))))
This is fairly simple to tie back to the original code. We have three sections under source_file
: two func_decl
s, one for foo
and one for bar
, followed by a top_level_code_decl
showing the call to bar
passing in foo
. And we see that the compiler inserts a function_conversion_expr implicit type='() -
>Int?'
(line 36).
Incidentally, if you run -dump-ast
on a call that requires a conversion from regular type like Int
to an optional, you’d see inject_into_optional implicit type='Int?'
. And for an implicit conversion to an Any
type, you’d see erasure_expr implicit type='Any'
.
- swiftc dumps to stderr. If you want to pipe longer examples through less, and like me you need to trial-and-error where the ampersand goes every damn time despite how many thousand times you might have typed it before, it’s 2>&1 ↩
[…] Airspeed Velocity: […]
[…] we’ve seen previously, Swift will happily auto-wrap your non-optional value inside an optional temporarily if it helps […]