Hi, I have only just discovered this thread and it looks very interesting. Was a type qualifier added to Clang or submitted for possible standardization in C23?
In a recent version of Xcode, Apple introduced an extension to
C/C++/Objective-C that expresses the nullability of pointers in the type
system via new nullability qualifiers . Nullability qualifiers express
nullability as part of the declaration of strchr [2]:
__nullable char *strchr(__nonnull const char *s, int c);
I think this is the correct place to put the qualifier, for reasons explained below.
Coincidentally, I was thinking along very similar lines on Friday evening. I was considering the practicality of implementing something like Python’s Optional[]
type-checking in C. I didn’t consider name-mangling or C++. (Apologies for the didactic style – it helped me get my thoughts coherent):
Type qualifiers are restrictions (or promises). A restriction on a pointer target type can be added implicitly but must be explicitly cast away; a restriction on any other type (including a pointer itself) is lost when the value is copied by assignment. This will be important later.
-
const
(‘not-writable’) is a usage restriction that the value of an object may not be modified. The compiler refuses to compile code which violates this.
-
volatile
(‘not-cacheable’) is a usage restriction that the value of an object may not be cached in registers. The compiler automatically generates code which complies.
-
restrict
(‘not-aliased’) is a usage restriction that the value of an object may not be accessed except via the specified pointer. This is actually an anti-restriction because it frees the compiler to generate code which assumes no aliasing. It would have been more consistent if compilers assumed by default that pointers do not alias (just as they assume that most objects are not volatile
). A fictional alternative alias
qualifier could force generation of safe code.
volatile
and const
qualifiers can be applied to every object type, whereas restrict
only applies to pointers (which is a bit ugly). Because restrict
does not apply to the pointer target type, it does not need to be explicitly cast away and therefore is not type-safe. Unfortunately a nullable
or notnull
qualifier would fall into the same category as restrict
(e.g. int *notnull x
would be a promise to the compiler that the target of pointer x
exists, but could also be implicitly converted to int *y
).
-
int *nullable
could be a usage restriction that a pointer may not be dereferenced or used for arithmetic without first checking it is not null. The compiler cannot implement such checking itself because it affects control flow (or else would have UB). Static analysis tools could check for execution paths on which such checks are absent, but this checking could already be done because any pointer in C can be null. Effectively every object reference in C is already Optional[...]
by default.
-
int *notnull
could be a restriction that the value of a pointer is not null (like a C++ reference). This looks like a more plausible guarantee because it doesn’t require any runtime checking. When generating code for pointers with this qualifier, the compiler could assume that they will never equal null (like it assumes restrict
pointers will never alias). For this qualifier to improve type-safety, the compiler must check that ordinary pointer types (including (void*)0
) are not assigned to a pointer declared as notnull
. That would break the existing pattern for type qualifiers, which is that an object of unqualified type may be assigned to an object of the equivalent qualified type. restrict
allows this implicit conversion, which is one reason it is dangerous.
-
int optional *
could be a pointer target type qualifier indicating that an object may not exist (e.g. int optional *y;
makes sense, but int optional z;
does not). Taking the address of an optional
object would generate a pointer-to-optional
, and this qualifier would need to be explicitly cast away (as for a pointer-to-const
). This qualifier could be added to some existing interfaces (e.g. free(void optional *)
wouldn’t require the pointer target to be optional
any more than strcmp(const char *, const char *)
requires its pointer targets to be const
), but the effect of adding optional
to the pointer target returned by malloc
(which is the main source of null pointers) would be to prevent a huge amount of existing code from compiling. The only way to introduce it in a backward-compatible way would be to add ‘safe’ variants of malloc
, calloc
, fopen
and many other standard functions.
-
int nonopt *
could be a pointer target type qualifier indicating that an object must exist (e.g. int nonopt *y;
makes sense, but int nonopt z;
does not). However, this notion isn’t compatible with the fact that C adds pointer target type qualifiers implicitly on assignment. In practice, therefore, int nonopt *y
would mean that y
could be a pointer that may not be null (like any existing pointer declaration), just as int const *x
means that x
could be the address of an object which may not be modified. This information is meaningless.
Of all the proposed extensions, optional
seems to be the only one which would make code more type-safe, and therefore the only one of potential interest to me. notnull
could improve code generation, at the cost of introducing hard-to-debug errors (like restrict
).
Obviously, optional
isn’t a reserved keyword, so I envisage a header file analogous to <stdbool.h>
in which supporting compilers #define optional _Optional
. Compilers that don’t support optional
could instead simply #define optional
as nothing.