Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Two different types of nil in Swift?

Tags:

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
^
like image 944
Michael Avatar asked Jun 10 '14 07:06

Michael


People also ask

What is nil type in Swift?

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.

What is nil Xcode?

Defines the id of a null class.

Does null exist in Swift?

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).

What is let in Swift?

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.


2 Answers

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> 
like image 187
Bryan Chen Avatar answered Sep 17 '22 01:09

Bryan Chen


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

like image 20
LombaX Avatar answered Sep 17 '22 01:09

LombaX