Null-checked reference types
Quirin Schroll
qs.il.paperinik at gmail.com
Tue Aug 6 14:55:46 UTC 2024
### Proposal for types
Add the following type suffixes to the language: `?` and `!`.
For every reference type (definition excludes slices, see below)
`T`, the meaning of `T!` is “non-nullable `T`” and the meaning of
`T?` is “nullable `T`”, and `T` without suffix means either `T?`
or `T!` depending on context. For every non-reference type, `T!`
is a synonym for `T`; there is `T?` added for an optional type
with the same values as `T` plus a dedicated `null` value.
Naturally, `T!` converts to `T?` implicitly, but for `T?` to
`T!`, an explicit cast is required and that cast is `@system`.
Multiple suffixes are allowed: `T?!` is `T!` and `T!?` is `T?`.
That is, later `!` or `?` override any previous ones.
Every lexical use of a reference type without `?` or `!` appended
is equivalent to one of them, depending on the module’s default.
The module’s default is either specified (`default null module
m;` or `default !null module m;`) or is the language’s default
(which depends on the Edition).
In class member functions, `this` has `!` type.
Any operation that requires a value of type `T?` to be non-null
is a compile-time error.
Add operators for null-respecting access: `?.`, `?(…)` (call if
not null), `?[…]` (index if not null), `?=` (assign if null).
For `if (auto x = expr)` and `if (T! x = expr)` if `expr` is of
type `T?`, `x` infers type `T!` and contrary to normal variable
definitions, in an `if` or `while` condition, `T?` implicitly
converts to `T!`.
To wrap the rest of the function in the *then* block of such an
`if` statement, add `if (auto x = expr) ... else …` to the
language. The `...` is part of the core syntax and is intended to
be read as “whatever follows next.” The `else` branch is
mandatory, and its `…` means any statement or a possibly empty
block. However, for an `else` block that would be `{ bool f =
false; assert(f); }`, add `assert(auto x !is expr)` to the
language. (Note that `assert(0)` as special semantics and isn’t
equivalent to a failed assertion.) Assert with declaration
enforces non-null for a possibly null value.
No data flow analysis is proposed. Null checking is local and
done by tracking `?` and `!` by the type system.
### Proposal for `ref`
The most difficult one is `ref`. `ref` parameters and variables
are assumed to be non-null, i.e. for `ref x`, `&x` should not be
`null`. To allow for null references, add `ref?`.
Non-null enforcement of `ref` should be done even in the current
edition to some degree. My bet is not a single D program ever
correctly expected and handled a `ref` returning function
returning a null reference. One would have to take the address of
the result and test that pointer for null. No-one does that,
except some people toying around with the edges of the language
intentionally used `ref` with null.
In the current Edition, because `T*` is `T*?`, a dereferenced
pointer is a possibly null reference. Binding one by `ref` would
be an error. For this special case, I propose to allow it
instead, as some programs would be full of errors (or deprecation
warnings) otherwise.
### Reference types
In this DIP Idea, reference types are:
- Pointer types
- Class / interface types
- Associative array types
- Function pointer types
- Delegate types
Slice types are not reference types in this logic because null
slices are equivalent to an empty slices for the most part.
More information about the dip.development
mailing list