Short-Circuit Operators
In most compiled languages, the &&
and ||
operators take boolean operands and always return a boolean result. Most scripting languages instead tend to evaluate the truthiness of the left argument, and then either return it (whatever its type may be), or evaluate and return the right operand.
We have chosen the latter behaviour, because it is more expressive. Consider this program:
fn if(argv: _[], exists!i: int)
argv.len > i && argv[i]
fn main(argv: string[]) {
let name = argv.if(exists: 1).trimmed || {
println("Usage: greet [name]")
return 1
}
println("Hello, " name "!")
return 0
}
Note how:
returnsargv.if
only ifargv[i]
, and an empty slice otherwise;argv.len >i- we're using
as an ad-hoc pattern matching utensil inargv.if
;main - because empty strings are falsy this program will only greet the user if the provided name, once
from leading and trailing whitespace, is a non empty.trimmed
Caveats
Static typing imposes some limitations on how these operators can be used in practice.
-
In a boolean context (e.g. the condition of an
statement), the operators accept any combination of types.if -
Otherwise,
||
requires that both operands are of the same type, while&&
allows operands of mismatched types so long as the right-hand type can be zero initialized.
Thus, outside of a boolean context:
0 || "Dog" // is an error
2 || "Dog" // is an error
"" || "Dog" // is "Dog"
"Cat" || "Dog" // is "Cat"
0 && "Dog" // is ""
2 && "Dog" // is "Dog"
"" && "Dog" // is ""
"Cat" && "Dog" // is "Dog"
Truthy and Falsy Values
Currently, all value types must be zero-initializable, and any zero-filled value must evaluate to
when coerced to false
. This means that a sequence of zero bytes of sufficient length is a valid value of any type.
bool
Importantly, currently:
- All primitives are falsy if
and truthy otherwise.0 - A dynamic array or string is truthy if it is not empty.
- A struct is truthy if any of its
fields are truthy.true