Nullable or Optional? Or something else?

Rainer Deyke rainerd at eldwood.com
Thu Sep 10 21:49:58 PDT 2009


Steven Schveighoffer wrote:
> On Thu, 10 Sep 2009 17:41:19 -0400, Rainer Deyke <rainerd at eldwood.com>
> wrote:
>> And I see no value at all, but a lot of danger.  Even ignoring, what
>> about this:
>>
>>   void f(ref int v) {
>>     // Do something...
>>   }
>>
>>   void g() {
>>     Optional!int x = 5;
>>     f(x);
>>   }
> 
> Doesn't compile.  Implicit casting doesn't mean implicit reference casting.

OK, this is just an annoyance, not actual danger.  It's still an
inconsistency.  If you had a way to get to the contained value (as an
lvalue), you could pass this value to 'f' as a reference.

>> Or this:
>>
>>   void report_error(int);
>>
>>   void f(T)(out T v) {
>>     if (error_condition) throw new SomeException();
>>     v = something;
>>   }
>>
>>   void g() {
>>     Optional!int v = someCalculation();
>>     if (!isNull(v)) {
>>       try {
>>         f(v);
>>       } catch (SomeException) {
>>         report_error(v); // Oops, invalid: 'v' turned into null.
>>       }
>>     }
>>   }
> 
> What is the point of this example?  Use ref if you don't want f to
> change the value of it's argument, not out.

No, 'out' is correct.  I want 'f' to return a value through its
argument.  If 'f' terminates through an exception before it calculates
the new value of 'v', I still want it to clobber the previous value of
'v' in order to prevent the error from being masked.

>> Note how in the second example, the semantics of the function 'f' subtly
>> change depending on whether the argument is an 'int' or and
>> 'Optional!int'.
> 
> Do they?

They absolutely do.  You pass in an 'int', you get one result, you pass
in an 'Optional!int', you get another result.

Maybe the use of exceptions is confusing you.  Here is another example:

  void defaultInitialize(T)(ref T) {
    T = T.init;
  }

Again, one result if you pass in an 'int', another result if you pass in
an 'Optional!T'.  Logically the same operation, but physically different
results.

>  I think the bug is that f's argument is out.  What if the
> argument is int, you get report_error(0), which tells you nothing.  in
> fact, you might as well just change the call to
> 
> report_error(typeof(v).init);

That's another good example, actually.

  report_error(typeof(v).init);

Legal when 'typeof(v)' is an int.  Illegal if 'typeof(v) is
'Optional!int', because 'Optional!int' is not and cannot be fully
equivalent to 'int' while also supporting null functionality.

When you just see that one line of code, the problem is obvious.  When
it's buried in several layers of template code, then it's a lot less
obvious.

>> Proxies, especially proxies that add functionality to their base type,
>> cannot act exactly like the type they are proxying.  Using a proxy
>> therefore requires the significant mental overhead of keeping track of
>> all the corner cases in which the proxy does not act like the type it is
>> proxying, as well as the hassle of working around those limitation
>> whenever they come up.
> 
> They can act like the base type up to a point.  You make it sound like a
> chore to use a wrapper type, but the truth is they are easy to use,
> there are not that many cases to worry about.

Up until you forget about one of those cases, at which you have a
program that mysteriously fails to compile at best and an incorrect
program at worst.


-- 
Rainer Deyke - rainerd at eldwood.com



More information about the Digitalmars-d mailing list