Absence
The redefinition: no
null, no implicit zero-value. Three concepts that other languages confuse stay separate: absence, default and null.
The idea
Section titled “The idea”“What if there is no value?” is one of the oldest questions in programming, and the most common answer, the
null/nil/NULL, is admittedly the “billion-dollar mistake”: a value that inhabits
any type and blows up on access. Go softens it with the zero-value (every type has an implicit
default), but it trades one problem for another.
How Makoto redefines it
Section titled “How Makoto redefines it”Three concepts, three distinct answers, with no overlap:
- Absence =
Optional[T], with the builtin valuenone. The type says it may be missing. You unpack it withmatchor with the honest API (is_present/or_panic/or). A pointer is guaranteed valid; “there may be no pointer” isOptional[*T]. - Default = explicit RHS. Every binding is born with a value that you write (
x := 0,var name := "").0and""only appear when you type them. - Null = does not exist. There is no “this is nothing” sentinel inhabiting the types.
And the case that seems to contradict but confirms: the Value.Null of JSON/binary (encoding ch.). It
is a token of the format (the null literal that JSON has), not the language’s null. It is a variant
present and normal in an enum; it maps to Optional.none only at the serialization boundary. Three
concepts, and not even the format token breaks the rule.
The reasoning
Section titled “The reasoning”The separation attacks a specific ambiguity of the zero-value: “is it 0 because someone asked for 0, or because
nobody initialized it?” In Go, var x int is 0 and var p *T is nil without you asking,
and then there is no way to distinguish “missing” from “is the default”. Makoto closes the door: since every binding has an RHS,
a 0 is always intentional, and absence is always an explicit Optional. The entire class of bugs
“I forgot to initialize” disappears because there is nothing to forget; you do not write an empty variable.
This fits into the pointer model (ch. 02): the non-nullable pointer is only possible because absence
lives in a separate type (Optional[*T]). And it fits into the error model (ch. 05): an allocation that can fail does
not return a silent null pointer; it returns a Result with an error. Absence, default and failure are three
things, and each one has its typed form.
Why not the alternatives
Section titled “Why not the alternatives”- Implicit
null/nil(C/Java/Go-pointers). It inhabits every type, blows up on access, and does not appear in the signature.Optional[T]puts the possibility in the type and forces the unpacking. - Zero-value (Go). It solves “always has a value” but creates the ambiguity “0 or missing?”. The mandatory RHS gives “always has a value” without the ambiguity, because the value is always chosen.
- Coercion by omission (an omitted field becomes an invisible default, C++). It reopens the “value that nobody asked for”. In Makoto, omitting is an error; you write what you want.
nil, nilas a return (Go). “Is it nil because it does not matter, or because it failed?” Ambiguous.Result[(...), E]separates: success isOk, failure isErr; one does not disguise itself as the other.
The concrete pain
Section titled “The concrete pain”The NullPointerException/segfault that rises from a field nobody initialized. The Go struct you
think is filled and is full of silent zeros. The repeated if (x === null || x === undefined)
in JavaScript, where two different “nothings” do the same thing. Each one is the confusion between
absence, default and null charging the price.
The mental model
Section titled “The mental model”Absence is typed; default is written; null does not exist. If something can be missing, the type says so (
Optional[T]). If it has an initial value, you write it. There is no third magic case.
The accepted friction
Section titled “The accepted friction”The price is verbosity in the RHS: you initialize everything, and sometimes you write a row of x := 0; y := 0. And Optional has to be unpacked at every use, instead of being read as if it were the value. More
boilerplate, in exchange for guaranteed safety at compile time: absence never catches you
by surprise, because it is always written in the type.
Next: 07 · Types