Issue 23998 - @mustuse should require opCast(T:bool) is checked
Summary: @mustuse should require opCast(T:bool) is checked
Status: NEW
Alias: None
Product: D
Classification: Unclassified
Component: dmd (show other issues)
Version: D2
Hardware: All All
: P1 enhancement
Assignee: No Owner
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2023-06-18 10:29 UTC by Richard (Rikki) Andrew Cattermole
Modified: 2023-06-18 19:19 UTC (History)
2 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this issue.
Description Richard (Rikki) Andrew Cattermole 2023-06-18 10:29:16 UTC
This is something I've run into while using @mustuse.

The struct could very well be used, without any error being checked.

While I'm not sure opCast(T:bool) should always be used for this purpose, it should be possible to require the compiler to error if it does not occur (although it would be nice not to do this when it comes from opApply/opApplyReverse.

The reason I have used the opCast is because of its integration into if statements which makes it quite nice to use, when it isn't crashing the program (due to error not being checked).

Without this, an ``alias this`` to a method would in theory be enough to keep the compiler happy even if it's null.
Comment 1 Dennis 2023-06-18 10:53:27 UTC
I don't follow, can you give a code example?
Comment 2 Richard (Rikki) Andrew Cattermole 2023-06-18 11:02:50 UTC
Okay lets say we have an error struct:

```d
@mustuse struct Result(T) {
   private T* value;

   bool isNull() {
       return value is null;
   }

   ref T get() {
       return *value;
   }

   alias get this;
}
```

We pass it into our function somehow (in this case I'll go with a parameter):

```d
void myFunction(Result!int arg) {
    writeln(arg.get);
}
```

Great, you use the variable! But the whole reason for @mustuse is so that we can guarantee that error checks have been handled in some way.

So we want some way for a function (like the isNull in the above example) to have to be checked, not just a method being called.

Without this, you will end up with runtime errors instead of compiler ones (like I have been experiencing).
Comment 3 Dennis 2023-06-18 12:09:04 UTC
@mustuse is supposed to prevent discarding values of that type. I'm not a fan of tacking on additional rules about `opCast` to a feature that should be simple and orthogonal. It looks like what you really want is type state, which Timon Gehr has been advocating for, but that would require a DIP.
Comment 4 Richard (Rikki) Andrew Cattermole 2023-06-18 12:19:00 UTC
I'm not saying it has to be opCast, that's just my use case as it works for me at runtime as-is.

Basically, I need a way to tell the compiler, if any of these specific methods are not called immediately, then this value has been discarded even if other methods have been called.
Comment 5 Paul Backus 2023-06-18 19:19:23 UTC
Dennis is correct. This is completely outside the purview of @mustuse. What you are asking for is a guarantee not just that the object has been used, but that it has been used *correctly* (according to some programmer-defined criteria).

For this specific example, I think the best solution is to redesign your Result type so that it is impossible to access the wrapped value without performing a null check. Here's one possible way to do so:

---
import core.attribute, std.stdio;

@mustuse struct Result {
    private int* value;
    bool isNull() => value is null;
    int opApply(scope int delegate(int) dg)
    {
        if (value is null) return 0;
        return dg(*value);
    }
}

Result getResult() => Result(new int(42));

void main()
{
    auto r = getResult();
    foreach (int n; r)
        writeln("got ", n);
}
---