I have encountered some strange behaviour in the Swift language when working in the REPL (=Read-Eval-Print-Loop) where there seem to be two different types of nil
values with different behaviour at runtime:
For this, I define a function g
:
func g(x:String!) {
println("start!");
println((x == "foo") ? "foo" : "not");
}
Then I define two variables:
var x:String
var y:String!
When I call g(x)
, it works like Objective-C:
start!
not
When I call g(y)
, I got an error:
start!
fatal error: Can't unwrap Optional.None
Execution interrupted. Enter Swift code to recover and continue.
Enter LLDB commands to investigate (type :help for assistance.)
Note that this error is caught at runtime! The function is already started. You can see this because of the "start!" string in the output.
It seems, that in the first case, the function gets a value of nil, together with a note "this is not a nil value". In the second case, it seems to get a nil, with an attached note "this value is nil, please scream and don't just use it."
Why didn't I get an error in the first case, and an error in the second case? Shouldn't be the behaviour of g(x) and g(y) the same?
Is this expected behaviour? Am I missing something? Is this a bug in Swift? A bug in the specification? Or a bug in the implementation? Or just a bug in the REPL? Shouldn't it be impossible to access uninitialized variables?
The whole session transcript, for reference...
$ /Applications/Xcode6-Beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift
Welcome to Swift! Type :help for assistance.
1> func g(x:String!) {println("start!"); println((x=="foo") ? "foo" : "not");}
2> var x:String
x: String = {
core = {
_baseAddress = Builtin.RawPointer = 0x0000000000000000
_countAndFlags = 0
_owner = None
}
}
3> var y:String!
y: String! = nil
4> g(x)
start!
not
5> g(y)
start!
fatal error: Can't unwrap Optional.None
Execution interrupted. Enter Swift code to recover and continue.
Enter LLDB commands to investigate (type :help for assistance.)
6>
To reproduce the whole session, just type the following code into the terminal:
/Applications/Xcode6-Beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift
func g(x:String!) {println("start!"); println((x=="foo") ? "foo" : "not");}
var x:String
var y:String!
g(x)
g(y)
(the function is written in one line, because copy&paste functionality is broken in the REPL, at least on my machine. Writing it in one line works, though. That's another story...)
March 2015 Update: Apple seems to have fixed this issue. It is no longer possible to declare a normal variable without an initial value:
2> var x:String
repl.swift:2:1: error: variables currently must have an initial value when entered at the top level of the REPL
var x:String
^
In Swift, nil means the absence of a value. Sending a message to nil results in a fatal error. An optional encapsulates this concept. An optional either has a value or it doesn't. Optionals add safety to the language.
Defines the id of a null class.
However there is another data type in Swift called Optional, whose default value is a null value ( nil ). You can use optional when you want a variable or constant contain no value in it. An optional type may contain a value or absent a value (a null value).
Swift employs the keywords let and var for naming variables. The let keyword declares a constant, meaning that it cannot be re-assigned after it's been created (though its variable properties can be altered later). The var keyword declares a new variable, meaning that the value it holds can be changed at a later time.
REPL is doing some initialization for you automatically, even if you don't want it
I copied & pasteed your code into the playground and it throws this error to me:
error: variable 'x' used before being initialized
g(x)
^
<REPL>:22:5: note: variable defined here
var x:String
Which I think it is the correct result. The code should not compile.
When I type this into REPL
var a:String
it prints
a: String = {
core = {
_baseAddress = Builtin.RawPointer = 0x0000000000000000
_countAndFlags = 0
_owner = None
}
}
so REPL somehow initialize a
into a nil
String (which I am not sure is it legal to exist) and rest of the story are explained in other answers
Looks like REPL is doing some default initialization for every variable so you can't use uninitialized variable
Welcome to Swift! Type :help for assistance.
1> var a:String
a: String = {
core = {
_baseAddress = Builtin.RawPointer = 0x0000000000000000
_countAndFlags = 0
_owner = None
}
}
2> var b:Int
b: Int = 0
3> var c:Int[]
c: Int[] = size=0
4> var d:Dictionary<Int,Int>
d: Dictionary<Int, Int> = {}
more interesting that it still can initialize non-optional type with nil
5> import Foundation
6> var f:NSObject
f: NSObject = {}
7> var g:NSNumber
g: NSNumber = {
Foundation.NSValue = <parent is NULL>
}
8> print(g)
fatal error: Can't unwrap Optional.None
Execution interrupted. Enter Swift code to recover and continue.
So REPL turn access to uninitialized variable (which should be compile-time error) into runtime error
Welcome to Swift! Type :help for assistance.
1> class Test{ var val:Int; init(v:Int) {val=v} }
2> var t:Test
t: Test = {
val = <parent is NULL>
}
3> t.val
Execution interrupted. Enter Swift code to recover and continue.
Enter LLDB commands to investigate (type :help for assistance.)
4> t = Test(v:1)
5> t.val
$R2: Int = 1
6>
It is expected behavior.
var y:String!
(with the exclamation mark) is an "implicitly unwrapped optional", it means that it's an optional, but you don't need to unwrap it (add the !) when you want to access it. But, like an optional, you'll trigger a runtime exception when you try to access it, if it has no value:
From “The Swift Programming Language” - page 78
“If you try to access an implicitly unwrapped optional when it does not contain a value, you will trigger a runtime error. The result is exactly the same as if you place an exclamation mark after a normal optional that does not contain a value.”
So, the fact that g(y) crashes is correct. What seems strange is that g(x) doesn't crash, but it seems to be a behavior of REPL, that initializes x for you.
For non optional values, the concept of nil doesn't exists. So, x is not nil, it's something like "uninitialized". If you try to put this code in a real project
func g(testValue:String!) {
println("start!");
println((testValue == "foo") ? "foo" : "not");
}
var x:String
var y:String!
g(x)
g(y)
It won't compile, you'll receive:
Variable 'x' used before being initialized
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With