null and type safety
Michel Fortin
michel.fortin at michelf.com
Wed Nov 5 04:31:57 PST 2008
On 2008-11-05 03:18:50 -0500, Walter Bright <newshound1 at digitalmars.com> said:
> I don't see what you've gained here. The compiler certainly can do flow
> analysis in some cases to know that a pointer isn't null, but that
> isn't generalizable. If a function takes a pointer parameter, no flow
> analysis will tell you if it is null or not.
I'm not sure how you're reading things, but to me having two kinds of
pointers (nullable and non-nullable) is exactly what you need to enable
nullness flow analysis across function boundaries.
Basically, if you declare some pointer to be restricted to not-null in
a function signature, and then try to call the function by passing it a
possibly null pointer, the compiler can tell you that you need to check
for null at the call site before calling the function.
It then ensue that when given a non-nullable pointer you can call a
function requiring a non-nullable pointer directly without any check
for null, because you know the pointer you recieved can't be null.
Currently, you can acheive this with proper documentation of functions
saying whether arguments accept null and if return values can return
null, and write your code with those assumptions in mind. Most often
than not however there is no such documentation and you find yourself
checking for null a lot more than necessary. If this property about
pointers in function parameters and return values were known to the
compiler, the compiler could check for you that you're doing things
correctly, warn you whenever you're forgetting a null check, and
optimise away checks for null on these pointers.
I know the null-dereferencing problem can generally be caught easily at
runtime, but sometime your null comes from far away in the program
(someone set a global to null for instance) and you're left to wonder
who put a null value there in the first place. Non-nullable pointers
would help a lot in those cases because you no longer have to test
every code path and the error of giving a null value would be caught at
the source (with the compiler telling you to check against null), not
only where it's being dereferenced.
- - -
That said, I think this could be done using an template. Consider this:
struct NotNullPtr(Type) {
private Type* ptr;
this(Type* ptr) {
opAssign(ptr);
}
void opAssign(Type* ptr) {
// if this gets inlined and you have already checked for null, then
// hopefully the optimizer will remove this redundant check.
if (ptr)
this.ptr = ptr;
else
throw new Exception("Unacceptable null value.");
}
void opAssign(NotNullPtr other) {
this.ptr = other.ptr;
}
Type* opCast() {
return ptr;
}
ref Type opDeref() {
return &ptr;
}
alias opDeref opStar;
// ... implement the rest yourself
}
(not tested)
You could use this template everywhere you want to be sure a pointer
isn't null. It guarenties that its value will never be null, and will
throw an exception at the source where you attempt to put a null value
in it, not when you attempt to dereference it later, when it's too late
and your program has already been put in an incorrect state.
NotNullPtr!(int) globalThatShouldNotBeNull;
int* foo();
globalThatShouldBeNull = foo(); // will throw if you attempt to set it
to null.
void bar(NotNullPtr!(int) arg);
bar(globalThatShouldNotBeNull); // no need to check for null.
The greatest downside to this template is that since it isn't part of
the language, almost no one will use it in their function prototypes
and return types. That's not counting that its syntax is verbose and
not very appealing (although it's not much worse than boost::shared_ptr
or std::auto_ptr).
But still, if you have a global or member variable that must not be
null, it can be of use; and if you have a function where you want to
put the burden of checking for null on the caller, it can be of use.
--
Michel Fortin
michel.fortin at michelf.com
http://michelf.com/
More information about the Digitalmars-d
mailing list