A few weeks back, I saw a piece of Swift code being tweeted by @objcio on Twitter. 46% of people think the code has compiler error, and I am one of them. 😅
Here’s the code.
func foo() throws -> Int { 0 }
func foo() throws -> Int? { 1 }
let a = (try? foo())
let b = (try? foo())!
let result = (a == b)
// What’s the value of result? (Swift 5)
// 1. true
// 2. false
// 3. Compiler Error
Here’s the voting result of that tweet.
Surprisingly, out of 850 people who voted, 46.6% of them think the code snippet above have compiler error. However, that is not the correct answer. The correct answer is…
“true”
This makes me wonder why the majority of people think the code have error and why the answer is “true”.
After spending some time doing research and playing around the code in Xcode playground, here’s my findings.
The False Alarm
Missing Return In a Function
I think most people will think that the following 2 lines of code might get compiler error.
func foo() throws -> Int { 0 }
func foo() throws -> Int? { 1 }
One might think that the return
keyword are missing in both functions and the compiler should show error of “Missing return in a function” .
In fact, these 2 lines of code are totally valid due to the inclusion of “Implicit returns from single-expression functions” proposal in Swift 5.1. The proposal suggested that if a closure contains just a single expression, the return
keyword can be omitted.
Therefore, the code above is identical to the code below.
func foo() throws -> Int {
return 0
}
func foo() throws -> Int? {
return 1
}
Invalid Redeclaration
Another possible reason why people think compiler error will occur is because both functions have the same name foo()
. This might cause “Invalid redeclaration of ‘foo()'” error.
However, this is also not true because function overloading is supported by Swift. As long as functions with the same name have different parameters, different argument labels or different return types, no compiler error will trigger.
After knowing what causes the false alarm, next up, let’s get into the interesting part — figuring out why the answer is “true”.
Type Inference in Swift
In order to get the correct answer, we must first know what is type inference in Swift.
Type inference is a Swift language feature where compiler will figure out the appropriate type of your constant or variable if you do not specify it during declaration.
Code snippet below demonstrate type inference in Swift.
let value1 = "some string"
// value1 is inferred to be of type String
let value2 = 123
// value2 is inferred to be of type Int
With that in mind, let’s simplify the tweet’s code into 3 lines so that it is easier to analyse.
func foo() throws -> Int { 0 }
func foo() throws -> Int? { 1 }
let a = (try? foo())
Now, copy the above code into Xcode playground, then change try?
to try
. You should see that “Ambiguous use of ‘foo()'” compiler error is shown.
The reason why the error happened is because the compiler failed to use type inference to identify the type for a
.
When using try
(without ?
operator) on a throws
function, if no error occurred, we will receive the function’s return type. Since foo()
have 2 equally possible return types — Int
and Int?
, the compiler has no way to decide which type a
should inferred to.
Now, let’s add back ?
to try
. You will notice that the compiler error has gone away. This is because when using try?
on a throws
function, we will always get back an optional type.
The compiler is now able to use type inference and select the closest possible return type for foo()
, which is Int?
. Therefore, a
will have type of Int?
and its value will be 1
.
We can further prove the correctness of our understanding by changing Int?
to some other non-optional data type. Type inference should fail this time due to both foo()
have 2 equally possible return types. (Note that this time we are using try?
but still get the ambiguous error.)
With the tricky part out of the way, we can now continue working towards the final answer.
Getting the Final Answer
To recap, this is the code we are working on.
func foo() throws -> Int { 0 }
func foo() throws -> Int? { 1 }
let a = (try? foo())
let b = (try? foo())!
let result = (a == b)
// What’s the value of result? (Swift 5)
// 1. true
// 2. false
// 3. Compiler Error
We already know that a
is optional(1)
, and b
is just force unwrapping of a
, which is 1
.
(a == b)
is equivalent to optional(1) == 1
, which is true
. Therefore, the value of result
is true
.
Wrapping Up
I would like to thanks @tammofreese for sharing this interesting piece of code. Anyone who is interested can checkout the original tweet by @objcio.
If you like this article, checkout the following article that showcase another piece of interesting Swift code.
Further Reading
- Making Sense of Swift ? and ! Operators in Optional, Downcasting and Initialization
- Error Handling in Swift
- What’s new in Swift 5.1
If you like this article, feel free to share it with your friends and leave me a comment. You can follow me on Twitter for more article related to iOS development.
Thanks for reading! 👨🏼💻
👋🏻 Hey!
While you’re still here, why not check out some of my favorite Mac tools on Setapp? They will definitely help improve your day-to-day productivity. Additionally, doing so will also help support my work.
- ✨ Bartender: Superpower your menu bar and take full control over your menu bar items.
- ✨ CleanShot X: The best screen capture app I’ve ever used.
- ✨ PixelSnap: Measure on-screen elements with ease and precision.
- ✨ iStat Menus: Track CPU, GPU, sensors, and more, all in one convenient tool.