Reputation: 6517
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
^
Upvotes: 13
Views: 6497
Reputation: 46598
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>
Upvotes: 14
Reputation: 17364
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
Upvotes: 4
Reputation: 12446
var x:String
has to have a value. May not be nil.
That is the problem. You should not use an uninitialized variable (it is not optional).
var y:String!
may be used unintitialized, value can be nil. Is is like writing
var y:String? // BUT please every time I use it put an exclamation mark after the y for me
So instead of y!
you can write y. But the value may be nil
. Using y
when the value is nil
-> crash.
Upvotes: 2
Reputation: 124997
Is this expected behaviour? Am I missing something? Is this a bug in Swift?
It looks like it's due to your prior activity in the REPL. Here's what I get when I paste your code into a playground:
Playground execution failed: error: code after 'return' will never be executedcode after 'return' will never be executed<REPL>:8:3: error: variable 'x' used before being initialized
g(x)
^
<REPL>:6:5: note: variable defined here
var x:String
Basically, it complains that you're using x
without first giving it a value. Looking back at the transcript of your session, however, you do give x
a value on line 2:
2> var x:String
x: String = {
core = {
_baseAddress = Builtin.RawPointer = 0x0000000000000000
_countAndFlags = 0
_owner = None
}
}
So it's no surprise that the compiler is okay with x
. The error that you do get is due to y
being declared as an implicitly unwrapped variable. You set it to nil
, try to access it, and get an runtime error that says that the value couldn't be unwrapped. The error there isn't surprising -- that's what's supposed to happen when you try to force unwrap a nil variable.
Upvotes: 3