www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - This syntax regarding null checking baffles me

reply 12345swordy <alexanderheistermann gmail.com> writes:
if (c !is null) Why?????

Would it be simpler to type

if (c is not null)

on a related note. Why are not we allowed to do this?

if (c != null)



-Alex
Jan 06
next sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Thursday, 7 January 2021 at 04:57:55 UTC, 12345swordy wrote:
 if (c !is null) Why?????

 Would it be simpler to type

 if (c is not null)

 on a related note. Why are not we allowed to do this?

 if (c != null)
it is allowed


 -Alex
"is" checks the address whereas "==" and "!=" eventually takes the path of operator overloading, i.e opEquals.
Jan 06
parent reply 12345swordy <alexanderheistermann gmail.com> writes:
On Thursday, 7 January 2021 at 05:14:44 UTC, Basile B. wrote:
 On Thursday, 7 January 2021 at 04:57:55 UTC, 12345swordy wrote:
 if (c !is null) Why?????

 Would it be simpler to type

 if (c is not null)

 on a related note. Why are not we allowed to do this?

 if (c != null)
it is allowed
"For class objects, the == and != operators are intended to compare the contents of the objects, however an appropriate opEquals override must be defined for this to work. The default opEquals provided by the root Object class is equivalent to the is operator. Comparing against null is invalid, as null has no contents. Use the is and !is operators instead." Not allowed for classes apparently. -Alex
Jan 07
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/7/21 9:13 AM, 12345swordy wrote:
 On Thursday, 7 January 2021 at 05:14:44 UTC, Basile B. wrote:
 On Thursday, 7 January 2021 at 04:57:55 UTC, 12345swordy wrote:
 if (c !is null) Why?????

 Would it be simpler to type

 if (c is not null)

 on a related note. Why are not we allowed to do this?

 if (c != null)
it is allowed
"For class objects, the == and != operators are intended to compare the contents of the objects, however an appropriate opEquals override must be defined for this to work. The default opEquals provided by the root Object class is equivalent to the is operator. Comparing against null is invalid, as null has no contents. Use the is and !is operators instead." Not allowed for classes apparently.
Historically (before this restriction was added), when you compared 2 class objects, the code: obj != expr translated directly to: !obj.opEquals(expr) Which, if obj was null, resulted in a segmentation fault. Thus the case of: obj != null is both error prone and ironic: if(obj != null) { /* use obj */ } This crashes if obj actually is null. Therefore, the syntax `obj !is null` is required. You could find somewhere on these forums where I advocated for this, and Walter finally agreed. Since then, the code for equality now translates to: object.opEquals(obj1, obj2) which will not crash, even if the object that isn't null improperly handles a comparison with null. However, it's still more "correct" to say `is null` or `!is null`, as technically there's no other comparison that makes sense. So we *could* relax the restriction, but I think the existing expression is clearer. -Steve
Jan 07
prev sibling next sibling parent reply Dukc <ajieskola gmail.com> writes:
On Thursday, 7 January 2021 at 04:57:55 UTC, 12345swordy wrote:
 if (c !is null) Why?????

 Would it be simpler to type

 if (c is not null)
Saves 3 character per use, I guess. And keeps one operator in one keyword. Matter of taste, and whoever designed this obviously had to pick something.
Jan 07
parent reply Max Haughton <maxhaton gmail.com> writes:
On Thursday, 7 January 2021 at 15:37:44 UTC, Dukc wrote:
 On Thursday, 7 January 2021 at 04:57:55 UTC, 12345swordy wrote:
 if (c !is null) Why?????

 Would it be simpler to type

 if (c is not null)
Saves 3 character per use, I guess. And keeps one operator in one keyword. Matter of taste, and whoever designed this obviously had to pick something.
I don't think not is reserved anywhere else so it would mean adding a new terminal to the grammar too, and d already has a lot.
Jan 07
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Thursday, January 7, 2021 8:55:52 AM MST Max Haughton via Digitalmars-d 
wrote:
 On Thursday, 7 January 2021 at 15:37:44 UTC, Dukc wrote:
 On Thursday, 7 January 2021 at 04:57:55 UTC, 12345swordy wrote:
 if (c !is null) Why?????

 Would it be simpler to type

 if (c is not null)
Saves 3 character per use, I guess. And keeps one operator in one keyword. Matter of taste, and whoever designed this obviously had to pick something.
I don't think not is reserved anywhere else so it would mean adding a new terminal to the grammar too, and d already has a lot.
That and using ! is more consistent with what the C family of languages typically does. C-derived languages don't typically try to make sentences like "is not" would do. Having is and !is is also more consistent with == and !=. "is not" wouldn't really fit the rest of the language. Ultimately, how you feel about it probably comes down primarily to taste and what you're used to, but I'm sure that Walter would have wanted to avoid adding an extra keyword just for this. So, I would have expected him to reject "is not" on that basis, but my guess is that he never even considered it. Once he'd decided on is, !is was probably simply the obvious choice given the other operators that D has and Walter's C/C++ background. - Jonathan M Davis
Jan 07
parent reply kdevel <kdevel vogtner.de> writes:
On Thursday, 7 January 2021 at 22:42:40 UTC, Jonathan M Davis 
wrote:

[...]

 That and using ! is more consistent with what the C family of 
 languages typically does. C-derived languages don't typically 
 try to make sentences like "is not" would do. Having is and !is 
 is also more consistent with == and !=. "is not" wouldn't 
 really fit the rest of the language.
Idiomatic C is of course [1] char *p = ... if (p) ... This form seems to be applicable for pointers in D, too. And also for class variables class C { ... C c; I wonder if there is a difference between if (c) and if (c ! is null). [1] https://stackoverflow.com/questions/3825668/checking-for-null-pointer-in-c-c/3825704#3825704
Jan 09
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
On Saturday, January 9, 2021 2:02:33 PM MST kdevel via Digitalmars-d wrote:
 On Thursday, 7 January 2021 at 22:42:40 UTC, Jonathan M Davis
 wrote:

 [...]

 That and using ! is more consistent with what the C family of
 languages typically does. C-derived languages don't typically
 try to make sentences like "is not" would do. Having is and !is
 is also more consistent with == and !=. "is not" wouldn't
 really fit the rest of the language.
Idiomatic C is of course [1] char *p = ... if (p) ... This form seems to be applicable for pointers in D, too. And also for class variables class C { ... C c; I wonder if there is a difference between if (c) and if (c ! is null). [1] https://stackoverflow.com/questions/3825668/checking-for-null-pointer-in-c-c
IIRC, if the class overrides opCast for bool, then if(c) will cast the object to bool and use the result for the if condition, whereas if(c !is null) always checks whether the reference is null. - Jonathan M Davis
Jan 10
next sibling parent reply kdevel <kdevel vogtner.de> writes:
On Sunday, 10 January 2021 at 10:42:48 UTC, Jonathan M Davis 
wrote:

[...]

 IIRC, if the class overrides opCast for bool, then

     if(c)

 will cast the object to bool and use the result for the if 
 condition,
Well, no: ```null.d import std.stdio: writeln; class C { bool opCast () { return true; } } void bar (C c) { if (c) writeln (3); else writeln (2); } void foo (ref C c) { if (c) writeln (5); else writeln (4); } void main () { C c; // null ref if (c) writeln (1); else writeln (0); bar (c); foo (c); auto x = cast (bool) c; // crash as expected } ``` $ ./null 0 2 4 Segmentation fault BTW: Correctly using variables of class or in case below of AA type takes some getting used to. A few day ago I ran into a bug with a helper function "merge" in a context like this: ```ini.d import std.stdio; void merge(T) (T a, T b) { foreach (k, v; b) { if (k in a) throw new Exception ("key <" ~ k ~ "> already set"); a[k] = v; } } void main () { string[string] data; data = ["a": "A"]; // <---- comment me out! (*) auto other_data = ["x": "X"]; data.merge (other_data); writeln (data); } ``` As expected one gets: $ dmd ini.d && ./ini ["a":"A", "x":"X"] Now it happened that in the course of development there was a code path in which data was not initialized (*). Guess what the output was! $ dmd ini.d && ./ini [] A void merge(T) (ref T a, T b) is required to cover the uninitialzed case. That was a rather surprising experience.
Jan 10
parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 10 January 2021 at 14:40:04 UTC, kdevel wrote:
 On Sunday, 10 January 2021 at 10:42:48 UTC, Jonathan M Davis 
 wrote:

 [...]

 IIRC, if the class overrides opCast for bool, then

     if(c)

 will cast the object to bool and use the result for the if 
 condition,
Well, no: ```null.d import std.stdio: writeln; class C { bool opCast () { return true; } }
Your opCast has the wrong signature. It should be: bool opCast(T : bool)()
Jan 10
parent reply kdevel <kdevel vogtner.de> writes:
On Sunday, 10 January 2021 at 16:44:29 UTC, Paul Backus wrote:

[...]

 Your opCast has the wrong signature.
"Wrong" in what respect?
 It should be:

     bool opCast(T : bool)()
"My" opCast works as expected: ```null2.d import std.stdio: writeln; class C { int i; bool opCast () { __PRETTY_FUNCTION__.writeln; return true; } } void bar (C c) { if (c) writeln (3); else writeln (2); } void foo (ref C c) { if (c) writeln (5); else writeln (4); } void main () { C c = new C; writeln (c); if (c) writeln (1); else writeln (0); bar (c); foo (c); cast (bool) c; // print bool null2.C.opCast() c = null; cast (bool) c; // Segmentation fault } ``` $ dmd null2 && ./null2 null2.C 1 3 5 bool null2.C.opCast() Segmentation fault
Jan 10
parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 10 January 2021 at 17:28:42 UTC, kdevel wrote:
 On Sunday, 10 January 2021 at 16:44:29 UTC, Paul Backus wrote:

 [...]

 Your opCast has the wrong signature.
"Wrong" in what respect?
It does not match the signature required by the language spec: https://dlang.org/spec/operatoroverloading.html#cast
Jan 10
parent reply kdevel <kdevel vogtner.de> writes:
On Sunday, 10 January 2021 at 19:21:47 UTC, Paul Backus wrote:
 On Sunday, 10 January 2021 at 17:28:42 UTC, kdevel wrote:
 On Sunday, 10 January 2021 at 16:44:29 UTC, Paul Backus wrote:

 [...]

 Your opCast has the wrong signature.
"Wrong" in what respect?
It does not match the signature required by the language spec: https://dlang.org/spec/operatoroverloading.html#cast
Sure, but it is called anyway: ```null3.d import std.stdio: writeln; class C { int i; bool opCast () { __PRETTY_FUNCTION__.writeln; return false; } } void main () { C c = new C; cast (bool) c; // print bool null3.C.opCast() c = null; cast (bool) c; // Segmentation fault } ``` $ dmd null3 && ./null3 bool null3.C.opCast() Segmentation fault As ag0aep6g pointed out if (c) does not invoke the opCast member. Neither one of the signatures bool opCast () T opCast (T: bool) (). This "conversion to bool" [1] of the class variable seems to be not programmatically accessible. [1] "Class references are converted to bool by checking to see if the class reference is null or not." from § 20.2.1
Jan 10
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 1/10/21 4:52 PM, kdevel wrote:
 On Sunday, 10 January 2021 at 19:21:47 UTC, Paul Backus wrote:
 On Sunday, 10 January 2021 at 17:28:42 UTC, kdevel wrote:
 On Sunday, 10 January 2021 at 16:44:29 UTC, Paul Backus wrote:

 [...]

 Your opCast has the wrong signature.
"Wrong" in what respect?
It does not match the signature required by the language spec: https://dlang.org/spec/operatoroverloading.html#cast
Sure, but it is called anyway: ```null3.d import std.stdio: writeln; class C {    int i;    bool opCast ()    {       __PRETTY_FUNCTION__.writeln;       return false;    } } void main () {    C c = new C;    cast (bool) c; // print bool null3.C.opCast()    c = null;    cast (bool) c; // Segmentation fault } ``` $ dmd null3 && ./null3 bool null3.C.opCast() Segmentation fault As ag0aep6g pointed out    if (c) does not invoke the opCast member. Neither one of the signatures    bool opCast ()    T opCast (T: bool) (). This "conversion to bool" [1] of the class variable seems to be not programmatically accessible. [1] "Class references are converted to bool by checking to see if the class reference is null or not." from § 20.2.1
That's a relic from D1: https://digitalmars.com/d/1.0/operatoroverloading.html#Unary IMO, that functionality should not be used, but I doubt we will ever remove it. (I tested, and using this form works for if(s) for a struct) Note also that assert(c) will check the invariant of the object if it's not null. D has a few hidden behaviors for truthiness. -Steve
Jan 11
prev sibling parent ag0aep6g <anonymous example.com> writes:
On Sunday, 10 January 2021 at 10:42:48 UTC, Jonathan M Davis 
wrote:
 IIRC, if the class overrides opCast for bool, then

     if(c)

 will cast the object to bool and use the result for the if 
 condition, whereas

     if(c !is null)

 always checks whether the reference is null.
You're misremembering. Or maybe it was changed since you last checked. A rewrite from `if (c)` to `if (c.opCast!bool)` "only happens, however, for instances of structs. Class references are converted to bool by checking to see if the class reference is null or not." https://dlang.org/spec/operatoroverloading.html#boolean_operators
Jan 10
prev sibling parent Imperatorn <johan_forsberg_86 hotmail.com> writes:
On Thursday, 7 January 2021 at 04:57:55 UTC, 12345swordy wrote:
 if (c !is null) Why?????

 Would it be simpler to type

 if (c is not null)

 on a related note. Why are not we allowed to do this?

 if (c != null)



 -Alex
One thing you could do is to make an "extension method", exploit ufcs and do like c.isNull instead. Maybe those already exists somewhere. operators. Also, strings for example have string.IsNullOrEmpty and IsNullOrWhiteSpace, those are handy.
Jan 11