Hi Christopher,
[Christopher_Bazley] Christopher_Bazley
https://discourse.llvm.org/u/christopher_bazley
August 4
Hi,
I love C programming (I’ve been doing it for 23 years) and I think
something like your idea would be a great addition to the language. I
was already thinking about this topic extensively since Friday,
including all the possible ways to introduce ‘null’/‘non-null’ type
qualifiers.
It seems to me that the only way of adding nullability information to
C’s type system that is sympathetic to the language’s syntax, and allows
the information to be added to programs gradually without breaking
compatibility, would be to create a pointer /target/ type qualifier like
‘const’. Putting the type qualifier on the pointer type (like
‘restrict’) is just wrong IMO.
Yes… more or less.
I don’t like the idea of something like restrict at all. At the same
time, it’s the only way that we can guarantee 100% backwards
compatibility. But I prefer not doing it, rather than doing it bad, so
I’d completely discard the idea of something like restrict (i.e., I
discard the idea of _Nonnull and [[gnu::nonnull]]).
My idea is simply to add a new pointer target type qualifier, analogous
to 'const’, except that the new qualifier would mean “the address of
this object may be a null pointer”. It would have the same syntax and
usage rules as 'const’ (unlike, say, C++ references):
int optional *x = NULL;
int *y = x; // compiler error
int optional *z = x; // okay
I agree.
This is more or less Clang’s _Nullable, if Clang wanted to enforce it
(it perfectly could).
But your idea of moving it down to the type instead of the pointer might
makes sense, to avoid the issue of dropping qualifiers in
lvalue-to-rvalue conversions.
A small problem is that it has issues with backwards compatibility:
[
// This would be the new definition of NULL:
#define NULL ((void optional *)0)
int *p = NULL; // Compiler error; Valid in current ISO C
int optional *q = NULL; // Valid
]
We’re making ‘p’ be a compile error, so code would have to be updated to
work with C3x; it wouldn’t work out-of-the-box. So, if you pick current
C code and try to compile it with a compiler that has this feature, it
would stop compiling. Of course, the compiler could issue just a
warning, and let compilation continue, and let the programmer adapt to
this feature slowly.
In the end, I think it’s a good move. I wasn’t at the time ‘const’ was
introduced, but I guess it was more or less the same pain, and we have a
net gain some years after, when everyone supports const.
However, there’s still something that needs to be thought out:
[
int foo(int optional *p, int a)
{
if (p != NULL)
return *p;
return a;
}
]
I want the code above to work. But of course, I’m not allowed to
dereference a pointer to optional, am I? How will the compiler let me
do that? I still needs to know the paths of code previous to the
dereference, to deduct that I checked for nullness.
[…]
I thought the new qualifier could be called ‘optional’ by analogy with
the Python type annotation of the same name. This avoids clashing with
the names of existing Clang annotations such as _Nullable or _Nonnull
which aren’t part of the language’s type system and seem only to be
understood by Clang-format. (Those annotations are also less strict and
more complex than what I envisaged.)
I don’t know the py optional keyword. Can you link to it? It makes
sense to me the name.
My choice of name also puts mental space between pointer type qualifiers
that are implicitly discarded on copy (such as ‘restrict’) and my
proposed pointer /target/ type qualifier, which cannot be discarded on
copy (like 'const’). In other words, it’s a statement about the object,
not the address of the object:
int optional q = 4;
int r = q; // okay
Yes, makes sense.
Since optional isn’t a reserved word, it would need to be defined as a
macro alias for an inbuilt name like _Optional` in a header file (e.g.
a header named <stdopt.h> by analogy with <stdbool.h>)
Also makes sense.
Existing code could easily be updated progressively:
- functions which consume pointers that can be null could be changed
with no effect on compatibility. For example, void free(void
optional *) could consume a pointer to an optional-qualified
type, or a pointer to an unqualified type, without casting.
- ‘Safe’ variants of functions which produce pointers could also be
written, e.g.
void optional *malloc(size_t);
would produce a pointer that can only be passed to functions which
accept optional-qualified pointer target types. Crucially, however, this
is not necessary! Assigning the return value of standard malloc() to
an object of any pointer-to-optional type would be sufficient to ensure
that it cannot be used with any interface (or local variable) which
doesn’t promise to handle a null value safely.
I don’t envisage that a language standard would mandate that compilers
statically check for unchecked dereferences of pointers to ‘optional’
types, but they would acquire the ability to do:
extern int optional *z;
if (z != NULL) ++z; // okay (based on usage)
++z; // optional compiler warning (based on usage)
No, as discussed above, it probably shouldn’t, as it doesn’t with const.
Cheers,
Alex