www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Express "Class argument may not be null" ?

reply Johan Engelen <j j.nl> writes:
Hi all,
   How would you express the function interface intent that a 
reference to a class may not be null?
For a function "void foo(Klass)", calling "foo(null)" is valid. 
How do I express that that is invalid? (let's leave erroring with 
a compile error aside for now)

Something equivalent to C++'s pass by reference: "void 
foo(Klass&)".

(note: I mean D classes, for structs "ref" works)

Thanks,
   Johan
Aug 08 2017
next sibling parent reply ag0aep6g <anonymous example.com> writes:
On 08/08/2017 08:34 PM, Johan Engelen wrote:
    How would you express the function interface intent that a reference 
 to a class may not be null?
 For a function "void foo(Klass)", calling "foo(null)" is valid. How do I 
 express that that is invalid? (let's leave erroring with a compile error 
 aside for now)
 
 Something equivalent to C++'s pass by reference: "void foo(Klass&)".
A contract might be the best you can do: ---- void foo(Klass k) in { assert(k !is null); } body {} ---- Or throw an exception.
 (note: I mean D classes, for structs "ref" works)
But you can pass null in a ref parameter: ---- void f(ref int x) safe {} void main() safe { int* p = null; f(*p); } ----
Aug 08 2017
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 8/8/17 2:56 PM, ag0aep6g wrote:
 On 08/08/2017 08:34 PM, Johan Engelen wrote:
    How would you express the function interface intent that a 
 reference to a class may not be null?
 For a function "void foo(Klass)", calling "foo(null)" is valid. How do 
 I express that that is invalid? (let's leave erroring with a compile 
 error aside for now)

 Something equivalent to C++'s pass by reference: "void foo(Klass&)".
[snip]
 
 But you can pass null in a ref parameter:
 
 ----
 void f(ref int x)  safe {}
 void main()  safe
 {
      int* p = null;
      f(*p);
 }
 ----
Note that C++ also can do this, so I'm not sure the & is accomplishing the correct goal: void foo(Klass&); int main() { Klass *k = NULL; foo(*k); } However, the in contract does actually enforce the requirement. -Steve
Aug 08 2017
next sibling parent reply Johan Engelen <j j.nl> writes:
On Tuesday, 8 August 2017 at 19:38:19 UTC, Steven Schveighoffer 
wrote:
 Note that C++ also can do this, so I'm not sure the & is 
 accomplishing the correct goal:

 void foo(Klass&);

 int main()
 {
    Klass *k = NULL;
    foo(*k);
 }
In C++, it is clear that the _caller_ is doing the dereferencing, and the dereference is also explicit.
 However, the in contract does actually enforce the requirement.
And adds null pointer checks even when clearly not needed. - Johan
Aug 08 2017
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 8/8/17 3:59 PM, Johan Engelen wrote:
 On Tuesday, 8 August 2017 at 19:38:19 UTC, Steven Schveighoffer wrote:
 Note that C++ also can do this, so I'm not sure the & is accomplishing 
 the correct goal:

 void foo(Klass&);

 int main()
 {
    Klass *k = NULL;
    foo(*k);
 }
In C++, it is clear that the _caller_ is doing the dereferencing, and the dereference is also explicit.
In fact it's not doing any dereferencing. It's just under the hood passing a pointer.
 However, the in contract does actually enforce the requirement.
And adds null pointer checks even when clearly not needed.
Clearly not needed? I thought the point was to ensure the reference is not null? -Steve
Aug 09 2017
parent reply Johan <j j.nl> writes:
On Wednesday, 9 August 2017 at 12:47:49 UTC, Steven Schveighoffer 
wrote:
 On 8/8/17 3:59 PM, Johan Engelen wrote:
 
 In C++, it is clear that the _caller_ is doing the 
 dereferencing, and the dereference is also explicit.
In fact it's not doing any dereferencing. It's just under the hood passing a pointer.
Come on Steven, I thought you knew I am a compiler dev. The caller _is_ doing dereferencing. That's what the '*' means semantically and it matters. [1]
 However, the in contract does actually enforce the 
 requirement.
And adds null pointer checks even when clearly not needed.
Clearly not needed? I thought the point was to ensure the reference is not null?
I meant code like this: `k->something(); foo(*k);` or `auto k = new Klass(); foo(*k);`. Anyway, the point was to express the intent nicely. I wasn't expecting having to explain the merits of passing by reference. - Johan [1] Going off-topic. The alternative is dereferencing inside the callee (which is apparently the only option D semantically provides for classes). I showed the impact of this semantic difference in my DConf 2017 talk; here are links to examples: https://godbolt.org/g/rgvHTC (C++) https://godbolt.org/g/NCTeXo (D) It's a pity we are not able to pass classes "by reference" (per C++ parlance), for reasoning and readability, and performance. I still have to come up with a rationale for not having an explicit "*" for classes for when I am going to teach D.
Aug 09 2017
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 8/9/17 1:05 PM, Johan wrote:
 On Wednesday, 9 August 2017 at 12:47:49 UTC, Steven Schveighoffer wrote:
 On 8/8/17 3:59 PM, Johan Engelen wrote:
 In C++, it is clear that the _caller_ is doing the dereferencing, and 
 the dereference is also explicit.
In fact it's not doing any dereferencing. It's just under the hood passing a pointer.
Come on Steven, I thought you knew I am a compiler dev.
Of course I do! I'm not a compiler dev though :)
 The caller _is_ doing dereferencing. That's what the '*' means 
 semantically and it matters. [1]
As a layman user, I consider dereferencing to be when you actually use the data pointed at by the pointer (i.e. you've followed the reference). What you are doing when passing a dereferenced pointer through a reference is the same as just passing a pointer, it doesn't seem to me like there should be a difference.
 However, the in contract does actually enforce the requirement.
And adds null pointer checks even when clearly not needed.
Clearly not needed? I thought the point was to ensure the reference is not null?
I meant code like this: `k->something(); foo(*k);` or `auto k = new Klass(); foo(*k);`. Anyway, the point was to express the intent nicely. I wasn't expecting having to explain the merits of passing by reference.
Sorry, not a C++ developer. I just think in terms of how the code translates to the instructions to the computer. To me, pointers and references seem very similar.
 [1] Going off-topic.
 The alternative is dereferencing inside the callee (which is apparently 
 the only option D semantically provides for classes). I showed the 
 impact of this semantic difference in my DConf 2017 talk; here are links 
 to  examples:
 https://godbolt.org/g/rgvHTC  (C++)
 https://godbolt.org/g/NCTeXo  (D)
 It's a pity we are not able to pass classes "by reference" (per C++ 
 parlance), for reasoning and readability, and performance. I still have 
 to come up with a rationale for not having an explicit "*" for classes 
 for when I am going to teach D.
I didn't think this comes from the dereferencing, I thought it came from the ability to rebind. That is, inside the loop, the pointer potentially could have been set to something different, so it has to reload the data. What you really want perhaps is a class parameter that cannot be rebound? That would more accurately duplicate a ref parameter. -Steve
Aug 09 2017
prev sibling parent reply Andre Kostur <andre kostur.net> writes:
On 2017-08-08 12:38 PM, Steven Schveighoffer wrote:
 On 8/8/17 2:56 PM, ag0aep6g wrote:
 On 08/08/2017 08:34 PM, Johan Engelen wrote:
    How would you express the function interface intent that a 
 reference to a class may not be null?
 For a function "void foo(Klass)", calling "foo(null)" is valid. How 
 do I express that that is invalid? (let's leave erroring with a 
 compile error aside for now)

 Something equivalent to C++'s pass by reference: "void foo(Klass&)".
[snip]
 But you can pass null in a ref parameter:

 ----
 void f(ref int x)  safe {}
 void main()  safe
 {
      int* p = null;
      f(*p);
 }
 ----
Note that C++ also can do this, so I'm not sure the & is accomplishing the correct goal: void foo(Klass&); int main() { Klass *k = NULL; foo(*k); } However, the in contract does actually enforce the requirement.
To be fair: it cannot be done in C++ without first invoking Undefined Behaviour (such as dereferencing a nullptr).
Aug 08 2017
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 8/8/17 4:00 PM, Andre Kostur wrote:
 On 2017-08-08 12:38 PM, Steven Schveighoffer wrote:
 On 8/8/17 2:56 PM, ag0aep6g wrote:
 On 08/08/2017 08:34 PM, Johan Engelen wrote:
    How would you express the function interface intent that a 
 reference to a class may not be null?
 For a function "void foo(Klass)", calling "foo(null)" is valid. How 
 do I express that that is invalid? (let's leave erroring with a 
 compile error aside for now)

 Something equivalent to C++'s pass by reference: "void foo(Klass&)".
[snip]
 But you can pass null in a ref parameter:

 ----
 void f(ref int x)  safe {}
 void main()  safe
 {
      int* p = null;
      f(*p);
 }
 ----
Note that C++ also can do this, so I'm not sure the & is accomplishing the correct goal: void foo(Klass&); int main() { Klass *k = NULL; foo(*k); } However, the in contract does actually enforce the requirement.
To be fair: it cannot be done in C++ without first invoking Undefined Behaviour (such as dereferencing a nullptr).
If your "insurance" that null pointers aren't passed is the threat of undefined behavior, then it leaves a lot to be desired. It's possible, and does happen. The "just don't write bugs" approach doesn't scale. -Steve
Aug 09 2017
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 8/8/17 2:34 PM, Johan Engelen wrote:
 Hi all,
    How would you express the function interface intent that a reference 
 to a class may not be null?
 For a function "void foo(Klass)", calling "foo(null)" is valid. How do I 
 express that that is invalid? (let's leave erroring with a compile error 
 aside for now)
There isn't a way to do this in the type itself. One can always create a null class instance via: MyObj obj; There is no way to disallow this somehow in the definition of MyObj. With structs, you can disable this(), and it's still possible but harder to do so. I would say, however, that if you wanted to express the *intent*, even without a compile-time error, you could use a contract: void foo(Klass k) in {assert(k !is null);}; Since the contract is part of the signature, this should be symantically what you want. However, this has to be done on every function that would accept a Klass, there's no way to bake it into the type itself. -Steve
Aug 08 2017
parent Johan Engelen <j j.nl> writes:
On Tuesday, 8 August 2017 at 18:57:48 UTC, Steven Schveighoffer 
wrote:
 On 8/8/17 2:34 PM, Johan Engelen wrote:
 Hi all,
    How would you express the function interface intent that a 
 reference to a class may not be null?
 For a function "void foo(Klass)", calling "foo(null)" is 
 valid. How do I express that that is invalid? (let's leave 
 erroring with a compile error aside for now)
There isn't a way to do this in the type itself. One can always create a null class instance via: MyObj obj; There is no way to disallow this somehow in the definition of MyObj. With structs, you can disable this(), and it's still possible but harder to do so.
Ok thanks, so this could be a reason for not being allowed to express the non-null-ness. (I still haven't found peace with the absence of an explicit * for classes)
 I would say, however, that if you wanted to express the 
 *intent*, even without a compile-time error, you could use a 
 contract:

 void foo(Klass k) in {assert(k !is null);};
Thanks. I regret leaving compile-time errors out, because in that case adding it to the function documentation would suffice. (Btw: "Error: function foo in and out contracts require function body". But who uses .di files anyway. ;-) Cheers, Johan
Aug 08 2017
prev sibling parent Adam D. Ruppe <destructionator gmail.com> writes:
I was about to say "use NotNull" but there still isn't one in 
std.typecons. ugh.

But it is just a wrapper type that checks null in the contracts 
too, so you can do it at the function itself.
Aug 08 2017