8 ms·
> NULL is a value that is not a value. And that’s a problem. The problem isn't NULL, it's languages not enforcing the necessary checks for the "no data" condit
by pdkl95 8y ago
> NULL is a value that is not a value. And that’s a problem.
The problem isn't NULL, it's languages not enforcing the necessary checks for the "no data" condition. Option can still be NULL ("None" in rust), wrapping NULL in a struct doesn't provide any safety. The safety of Option wrapper types is from the other language features (like rust's "match") and a stricter compiler that forces the programmer to write the NULL check.
NULL would be fine if C required you to write this:
foo_t *maybe_get_foo(/*...*/) {
if (/*foo_is_available*/) {
return foo;
} else {
return NULL;
}
}
foo_t *f = maybe_get_foo();
if (!f) { /*...*/ } // REQUIRED or compile error
do_something(f->bar); // only allowed after NULL check
Obviously implementing that requirement would be difficult in C. Languages like Rust were designed with enforcement features (match + None, much stronger type/borrow checking), but lets your have "a value that is not a value".
- masklinn 8y ago> The problem isn't NULL, it's languages not enforcing the necessary checks for the "no data" condition. Talking about "NULL" pretty much implies that. When Tony Hoare talks about null references, it's about every reference being nullable in languages like Java or C#, not about the ability to conceptually wrap/opt non-nullable references in a nullability thingie.
- danesparza 8y agoPsst: I think the thingie you're referring to is called a 'monad': https://en.wikipedia.org/wiki/Monad_(functional_programming) https://en.wikipedia.org/wiki/Monad_(functional_programming)
- bfrydl 8y agoIt isn't. There exists a common monad that solves this problem but a wrapper type like Option or Maybe need not be a monad. For example, `Nullable<T>` in C# is not a monad.
- danesparza 8y agoAha! You're right. I misremembered this excellent blog series from Eric Lippert (a member of the c# design team): https://ericlippert.com/2013/02/25/monads-part-two/ https://ericlippert.com/2013/02/25/monads-part-two/
- masklinn 8y agoNo. The thingie may or may not be monadic, but I'm using thingie because it could be a "library" sum type, or it could be a magical-ish builtin, or it could be some other mechanism.
- r_c_a_d 8y agoOr add exceptions like C++ did, if (foo_is_available) return foo; else throw FooNotAvailable();
- r_c_a_d 8y agoNot sure why I am getting downvotes for this. The parent proposed a language feature to support an enforced code execution when a return value was not available: foo_t *maybe_get_foo(/*...*/) { if (/*foo_is_available*/) { return foo; } else { return NULL; } } foo_t *f = maybe_get_foo(); if (!f) { /*...*/ } // REQUIRED or compile error do_something(f->bar); // only allowed after NULL check And I was pointing out that C++ exceptions do exactly that. foo_t *maybe_get_foo(/*...*/) { if (/*foo_is_available*/) { return foo; } else { throw FooNotAvailable(); } } try { foo_t *f = maybe_get_foo(); do_something(f->bar); // f cannot be NULL } catch (const FooNotAvailable& ex) { /*...*/ } Sorry if that wasn't the answer you wanted because C++ is not fashionable. But it is a valid option for how to avoid the use of NULL.
- SamReidHughes 8y agoYou might instead argue that the problem is allowing checks for the null condition. If you make comparing a pointer with null crash, or just remove the keyword to make its use non-idiomatic (or only allow it as special initialization syntax), people won’t casually use null to mean something. It’ll serve its role as a pointer value that crashes cleanly if you dereference it.
- ummonk 8y agoYup, this is what happens when you use Typescript with strict null checking.
- chongli 8y agoNo, the problem with null is the inability to enforce, at the type level, that a particular value is not null. C simply does not have a type for "guaranteed valid pointer to x".
- kzrdude 8y agoC allows defining new types though, so it can be done.
- arcticbull 8y agoUnless I'm misreading OPs statement, it can't be done by defining a new type since C (unlike, say, Ada) doesn't support subrange types.
- chongli 8y agoCould you provide an example? Either source code or a link. I want to see how you might define a type for "guaranteed to not be null pointer to char".
- kzrdude 8y agoI'm thinking of just: struct char_nonnull_t { char *ptr; }; char *char_nonnull_as_pointer(struct char_nonnull_t cp) { return cp.ptr; } With the required extra accessor functions. Of course in C, we don't have generics, data hiding or other nice features, so we have to build a wall of conventions around our types instead. Types with invariants are totally possible in C. We just don't have a way to automatically enforce them. It's C, after all.
- mhh__ 8y agoIt can only really be done on a per codebase level, really. By which I mean, in my highly sensitive and correct program xyz I can (say) abstract all access to a type (A struct?) through a macro which would contain null checking/assert-ing. This could work perfectly well and even be static checked for (in principle). However, if I tried to package this type up in a library it all basically falls flat - or at least it would be very easily broken. Doing this like this is to static analysis(/The Type system) as foreplay is to sex.
- empath75 8y agoSince every type can be NULL that’s a lot of verbosity.
- jhomedall 8y ago> Option can still be NULL ("None" in rust) 'None' in Rust is part of the Option enum, not an equivalent to null. pub enum Option<T> { None, Some(T), }
- estebank 8y agoIt can be at the machine level. If T is NonZero, then sizeof(Option<T>) == sizeof(T).
- vmchale 8y ago> Option can still be NULL ("None" in rust), wrapping NULL in a struct doesn't provide any safety. The safety of Option wrapper types is from the other language features (like rust's "match") The advantage here (especially true in Haskell) is that you can use monadic error handling to make this far more pleasant.
- masklinn 8y agoThat's a secondary nicety (alongside the ability to make everything "nullable" not just the references/pointers subset), the primary advantage is that: 1. it clearly separates "nullable" and "non-nullable" providing better modelling tools 2. the compiler assists/mandates null-checking, providing better type-safety
- sidlls 8y agoEven Rust doesn't have the strictness in your comment. It's perfectly fine by the compiler to make use of `x.unwrap()`: if x is None (or Err, in the case of Result), you'll just get a panic at runtime. The features you note are superior to C's offering, but purely optional.
- dbaupp 8y agoThere's a fundamental difference between the Rust approach of the library providing a function for opting-in to potential crashes and the C/Java approach of not distinguishing that case at all. The programmer still is forced to write a null check, it's just a check that crashes the program.
- slavik81 8y agoI largely agree, but that would get very tedious because there's no way to create a pointer type that is guaranteed to be non-null in C. You'd end up having to do a lot of unnecessary checks because of that. People say optionals are the solution, but the way I see it it's the other way around. Pointers types that allow NULL are basically optionals, and the problem is that we use them everywhere, even for things that are not optional. What we are missing are pointer types for things that are not optional. And the pointer type with a guaranteed value needs to be as easy to use as the nullable pointer type, if not easier. Otherwise it won't always be used when it should be.
- pierrebai 8y agoNot very difficult. Just provide a not_null pointer attribute, just like const. Then require that all dereferenced pointers must have the not_null attribute. Problem solved. (Other note: C++ has a not_null pointer-like type: it's references. Unfortunately, C++ references cannot be reseated, which makes wholesale replacement of pointers not feasible. Plus, the language doesn't actually forces you to check pointers before assigning to a reference.)