Current sentiment on Nullable.get

Jonathan M Davis newsgroup.d at jmdavisprog.com
Sat Dec 15 02:14:15 UTC 2018


On Friday, December 14, 2018 5:05:51 PM MST Rubn via Digitalmars-d wrote:
> On Friday, 14 December 2018 at 23:08:30 UTC, Jonathan M Davis

> So for this naturally nullable type, would you say it has a value?
>
> int* ptr = null;

It has the value of null, which can either have a distinct meaning unto
itself, or it could mean that the pointer doesn't contain a value. Which it
is depends on what the code is doing. For example, IIRC, SQL treats a value
that is NULL and a value which doesn't exist as two separate states. Plenty
of other code would consider null to be an invalid value.

Without Nullable, if you wanted null to indicate that there is no value,
then T* represents that just fine, whereas if you wanted null to be a valid
value, then you'd need T** so that whether the outer pointer was null could
be used to indicate whether a value was present. With an Optional or
Nullable type, it's then possible to replace the T** with Nullable!(T*) in
the case where you consider null to be a valid value, and if you considered
null to be invalid, then either Nullable!T or T* would work, though T* would
require a heap allocation.

> > n = value;
> > return n.get;
> >
> > could then actually fail the assertion in get if the code was
> > instantiated with a type that was naturally nullable. The
> > current approach - regardless of the name of Nullable -
> > prevents that problem.
>
> When would you ever want to use get() if you know isNull is true
> (including for if isNull() returned true for pointers that have a
> null value) ?

The problem I'm pointing out is that if isNull cares about whether the value
the Nullable contains is null, then you can no longer rely on the fact that
a Nullable has been assigned a value to determine whether get is legal to
call (at least not in generic code). You would always have to call isNull
first, because the value it was assigned might have been null if that code
had been instantiated with a type that can be null. Currently, something
like

n = value;
foo(n.get);

is guaranteed to work. So, don't need to ever call isNull in any code where
you have assigned the Nullable a value, whereas if the value can affect
isNull, then calling get without calling isNull risks failing the assertion
in get in generic code, forcing you to check isNull even when you know that
you assigned it a value. The code could even be operating on value types in
most case, but it would still have to check isNull in case it was
instantiated with a pointer. And it would be easy to write code in such a
way that work perfectly fine with value types but then blew up in your face
later when someone used a pointer with it, whereas if Nullable treats
pointers the same as value types, then that's not a problem.

> > Having a Nullable treat pointers differently from value types
> > would completely defeat the purpose of having Nullable work
> > with pointers in the first place, and if you're not writing
> > generic code, a Nullable that doesn't have a separate bool
> > indicating whether it has a value or not is pointless for
> > pointers, because it would just be creating a wrapper that did
> > nothing that pointers don't do naturally.
>
> Are you really saying it's pointless after the you spent a
> paragraph writing up about how it's useful to have 1 type provide
> the same interface ?

I'm saying that if Nullable's isNull is affected by the value of the pointer
it contains, then it's pointless to use Nullable!(T*) in non-generic code,
because the Nullable!(T*) is acting exactly like a T*, whereas right now,
Nullable!(T*) acts more like T** but with the benefit of not having to
allocate a place on the heap to contain the T*.

And if Nullable's isNull is affected by the value of the pointer it
contains, then it doesn't work well in generic code, because the Nullable
will act differently depending on the type that it contains, forcing you to
either call isNull in places where it would be unnecessary for types which
aren't nullable, and possibly forcing you to special-case the code when
operating on nullable types so that the code works the same regardless of
which type it's given.

As such, it makes no sense for Nullable's isNull to be affected by the value
of what it contains. It would make more sense for Nullable to disallow
pointers and other nullable types than for it to treat them differently from
non-nullable types.

On the other hand, right now, because Nullable works the same regardless of
what type it containts, it works quite well in generic code, and because
Nullable!(T*) basically gives you T** without the extra heap allocation,
there are use cases where it makes sense to use Nullable!(T*) even though T*
is already nullable. So, ultimately, the way that Nullable currently works
makes sense, whereas having isNull be affected by the value contained by the
Nullable does not. It could certainly be argued that the naming is poor, but
the semantics are exactly what they should be.

> You keep saying this, but the very first version of Nullable
> could in fact contain pointers.
>
> https://github.com/dlang/phobos/commit/0c142994d9b5cb9f379eca28f3a625c7493
> 70e4a#diff-4e008aedb3026d4a84f58323e53bf017R873
>
> https://run.dlang.io/is/bu0hqt
>
> Maybe there was a Nullable type in D1, but I can't find one at
> least.

I recall it being the case that D2's Nullable did not work with pointers and
classes and that someone changed it so that it did so that it would work in
generic code. If that's not true, then I misremembered, and it's less
reasonable that it has the name that it does.

Either way, I'm quite certain that the main motivation behind introducing
Nullable was so that you could have value types be nullable without
allocating on the heap. If pointers and classes were not explicitly
prohibited, then it was presumably because the original author (Andrei IIRC)
did not think that prohibiting it was worthwhile. And while it may not have
been the purpose behind Nullable to use it with nullable types, it is true
that it's useful with them in situations where you need to know when a
variable has been given a value or not, and the code considers null to be a
valid value.

Another situation where it's popular to use Nullable with non-nullable types
is dynamic arrays, because some array operations treat null the same as
empty, making it so that some folks consider it to be too risky (or just
plain bad practice) to actually use null as a special value for dynamic
arrays, whereas Nullable!(int[]) does not have that problem, because it does
not care about what null means for dynamic arrays.

Regardless, having Nullable treat nullable types differently from value
types would make it so that it no longer worked well in generic code, and it
would make it pointless to use outside of generic code.

> Don't have to rename it, can just make it do what it actually is
> named after.

That would break existing code, and it would mean that Nullable had terrible
semantics due to the issues that I explained above.

- Jonathan M Davis





More information about the Digitalmars-d mailing list