Null safety and memory, 1 of 5
Nullable types
Say where a value can be missing with T?, and let the compiler check every use of it.
What you’ll build
An organization chart where each person may have a manager, and a lookup of nicknames that may not be set. Both have values that can be missing, and the program handles each case without ever reading a field of nothing.
T? holds Null, and T doesn’t
A type ending in ? can hold Null, and the same type without the ? can’t. A String always
holds a String; a String? holds a String or Null. A field of a T? type starts as Null.
Local name: String = Null is an error, so a value that can’t be missing never is. The one
exception is the raw pointers used with C libraries, which can be Null like C’s.
a?.b reads b when a holds a value, and is Null when a is Null; a?.M() calls M
only when there’s a value. a ?? b gives a’s value, or b when a is Null. Together they
read a chain that may stop anywhere, with a fallback at the end.
Import std.core
Class Person
ReadOnly Field Name: String
Field Manager: Person?
New(name: String, manager: Person? = Null)
Name = name
Manager = manager
End New
End Class
Local grace = Person("Grace")
Local ada = Person("Ada", grace)
Print(ada.Manager?.Name ?? "nobody")
Print(grace.Manager?.Name ?? "nobody")
Print(ada.Manager?.Manager?.Name ?? "no one above")Grace nobody no one above
A T? can’t go in a string as it is, since Null has no text: "{ada.Manager?.Name}" is an
error that says to give a value for Null with ??.
Checking narrows the type
After If x != Null, the compiler knows x holds a value, and treats it as a T until the code
could change it. An early Return, &&, and || narrow the same way.
Import std.core
Import std.text
Class Person
ReadOnly Field Name: String
Field Manager: Person?
New(name: String, manager: Person? = Null)
Name = name
Manager = manager
End New
End Class
Function ManagerOf(person: Person): String
Local manager = person.Manager
If manager == Null Then Return "{person.Name} has no manager"
Return "{person.Name} reports to {manager.Name}, {manager.Name.Length} letters"
End Function
Local grace = Person("Grace")
Print(ManagerOf(Person("Ada", grace)))
Print(ManagerOf(grace))Ada reports to Grace, 5 letters Grace has no manager
A field isn’t narrowed, since any call could change it, so checking the field itself doesn’t help, and fb says what to do instead:
Import std.core
Class Person
ReadOnly Field Name: String
Field Manager: Person?
New(name: String)
Name = name
End New
End Class
Function ManagerOf(person: Person): String
If person.Manager == Null Then Return "{person.Name} has no manager"
Return "{person.Name} reports to {person.Manager.Name}"
End Function
Print(ManagerOf(Person("Grace"))) Compiling chart.fb
chart.fb:14:39: error: 'person.Manager' may be Null; check a Local copy of it, or use '?.'
|
14 | Return "{person.Name} reports to {person.Manager.Name}"
| ^^^^^^^^^^^^^^
error: couldn't build chart.fb because of 1 errorCopying the field into a Local, as the ManagerOf before this one does, is the fix.
Expect and OrElse
Every T? has two Methods. Expect(message) gives the value, and panics with the message when
there’s none, for a value the program knows must be there. OrElse(make) gives the value, or
calls make for one, only when it’s needed.
Import std.core
Import std.collections
Local nicknames = {"Ada": "Countess", "Alan": "Prof"}
Local ada = nicknames.Get("Ada")
Print(ada.Expect("Ada has a nickname"))
Local grace = nicknames.Get("Grace")
Print(grace.OrElse(Function() => "no nickname"))Countess no nickname
Expect on a Null stops the program with its message and the line of the call. Use it only
where a missing value would be a bug:
Import std.core
Import std.collections
Local nicknames = {"Ada": "Countess", "Alan": "Prof"}
Print(nicknames.Get("Grace").Expect("every user has a nickname"))panic: every user has a nickname at nicknames.fb:5:7
at top-level code (nicknames.fb:5)What you learned
- A type ending in
?can holdNull, the same type without it can’t, and a field of aT?starts asNull. ?.reads through a value that may beNull, and??gives a fallback.- A check like
If x != Nullnarrowsxto its type without the?. - A field isn’t narrowed; copy one into a
Localand check that. Expectgives the value or panics, andOrElsemakes a fallback only when it’s needed.
The rules are in the language reference.
Report an issue with this page or with FutureBlitz on GitHub.