Michael
Michael

Reputation: 6517

Two different types of nil in Swift?

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

Answers (4)

Bryan Chen
Bryan Chen

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

LombaX
LombaX

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

Binarian
Binarian

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

Caleb
Caleb

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

Related Questions