Null values
Represent a missing value and check for it safely.
On this page
A ? before a type, as in ?String or ?User, allows the value null. The compiler makes you check such a value before you use it.
Check for null#
fn greet(name: ?String) String {
if isset(name) {
return "Hello, " + name // name is a String here
}
return "Hello, guest"
}
fn main() {
println(greet("Ada")) // Hello, Ada
println(greet(null)) // Hello, guest
}
isset(x) is true when x is not null, and inside the branch x has its non-null type. !isset(x) checks the opposite.
Return early#
After a check that leaves the function, the rest of it sees the value as non-null:
fn initial(name: ?String) String {
if !isset(name) : return "?"
return name.range(0, 1)
}
fn main() {
println(initial("Zed")) // Z
println(initial(null)) // ?
}
The same works with break, continue, throw and panic.
Fall back to a default#
fn main() {
let nickname: ?String = null
println(nickname ?? "Anonymous") // Anonymous
}
a ?? b gives a when it is set and b otherwise. It binds tighter than comparisons: count ?? 0 == 3 means (count ?? 0) == 3.
Leave when null#
?! unwraps a value or runs what follows it, which must leave the current flow:
fn first_word(text: ?String) String {
let value = text ?! return ""
return value.split(" ").get(0) !? ""
}
fn main() {
println(first_word("hello world")) // hello
let required: ?String = "ok"
let checked = required ?! panic("missing value")
println(checked) // ok
}
Read through a chain#
?. reads a member of a value that may be null. If any step is null, the whole chain is null:
class Address { city: String }
class User { address: ?Address (null) }
fn find_user(id: int) ?User {
if id == 1 : return User { address: Address { city: "London" } }
return null
}
fn main() {
println(find_user(1)?.address?.city ?? "unknown") // London
println(find_user(2)?.address?.city ?? "unknown") // unknown
}
The result is nullable, so it usually ends in ?? or ?!. A method call without a result, such as user?.save(), is skipped when the value is null. You cannot assign through ?..
Null versus errors#
??and?!handle a missing value.!?and!handle an error from a call.
A function that cannot find something can return ?T when absence is normal, or throw an error when the caller should handle it explicitly. Map lookups show both: map["key"] gives null (or zero) for a missing key, while map.get("key") throws. See Maps.
