Types
Unions and optionals
Closed unions T | U, the optional T?, narrowing with != None and is, and the ?? and ?. operators.
since 0.1.0-alpha.1linuxwindowswasm
Description
A union type int | string holds a value of exactly one of the listed types. A value of type T can be assigned to any union that contains T.
T? is shorthand for T | None. None is the type of the single value that means "absent". There is no null. An optional field starts as None, and a general union starts with the default value of its first member.
You cannot call a method or read a member through a union until the compiler knows which alternative you hold. Test the value, and inside the tested branch the type is narrowed:
Narrowing an optional and a union
string describe(int | string v) {
if (v is int) {
return "int ${v + 1}";
} else {
return "string ${v.toUpper()}";
}
}
int lengthOf(string? s) {
if (s != None && s.length() > 0) {
return s.length();
}
return 0;
}
console.writeln(describe(41));
console.writeln(describe("hi"));
console.writeln(lengthOf("abc"));
console.writeln(lengthOf(None));
int 42
string HI
3
0
Rules
The types.
int | stringis a closed, tagged union. Each stored value knows which alternative it holds.T?meansT | None.Nonehas no payload and never compares equal to a present value, so an absent value, an empty value and a filled value stay distinguishable: withint? count = None,count == 0isfalse.- A field of an optional type defaults to
None. A union field or variable of another kind defaults to the default of its first member. - A numeric optional such as
float?keepsNoneand converts a present value. Seelang.numeric-conversions.
Narrowing.
- Calling a member of a union value, or reading one, is a compile error until it is narrowed: "narrow the union before calling members".
x != None,x == Noneandx is Tnarrowx. The narrowing applies in the branches ofifandelse, in the body of awhileloop, in the arms of a conditional expressionc ? a : b, and across&&(s != None && s.length() > 0). A path such asreq.hostnarrows like a variable.!negates what the test established: afterif (!(s == None))the value is a presentstring.- Assigning to the narrowed variable or path, or to something it is reached through, drops the narrowing.
- Narrowing does not carry past an early return. After
if (s == None) { return 0; }the value is stillstring?on the next line. Write the use inside the enclosingif (s != None) { ... }block, or in theelsebranch.
Defaults and chaining.
a ?? byieldsawhen it is present andbotherwise. Its type is the union withNoneremoved, andbmust match it:int n = s ?? 5;is an error whensis astring?.a?.m()anda?.bstop and giveNonewhenaisNone; the arguments are not evaluated. The result has typeR | None.
Examples
Defaults and optional chaining:
?? and ?. on optional values
class Req {
string? host;
int | string id = 5;
}
Req r = Req();
console.writeln(r.host == None);
console.writeln(r.id is int);
string? name = None;
console.writeln(name ?? "anonymous");
r.host = "example.org";
console.writeln(r.host?.toUpper());
name = "ada";
console.writeln(name?.length());
string? nothing = None;
console.writeln(nothing?.length() == None);
true
true
anonymous
EXAMPLE.ORG
3
true
Narrowing a path in a loop:
Narrowing in a while loop
class Node {
int value = 0;
Node? next;
}
Node tail = Node();
tail.value = 3;
Node head = Node();
head.value = 1;
head.next = tail;
Node? cur = head;
int total = 0;
while (cur != None) {
total += cur.value;
cur = cur.next;
}
console.writeln(total);
4
Notes
?? and ?. work on an optional without narrowing it first.
See also
- Type expressions — How a type is written: names, qualified names, generic instantiations, unions, optionals and function types.