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