optional icon indicating copy to clipboard operation
optional copied to clipboard

Allow `oc` on `const(Optional!X)`, allow `frontOr` within `const nothrow @nogc`

Open SimonN opened this issue 2 years ago • 1 comments

Using optional 1.3.0.

The following code works with no problems.

import optional;

class X {
    int foo() const pure nothrow @safe @nogc {
        return 5;
    }
}

class Y {
    Optional!X x;

    int bar() pure @safe {
        return x.oc.foo.frontOr(3);
    }
}

This compiles fine.

The problem arises when we want to annotate bar() not only as pure @safe, but as const pure nothrow @safe @nogc in the same way as we did with foo(). If we add all those annotations to bar(), we get these errors:

  1. We can't call .oc.foo on a const Optional!X.
  2. We can't call frontOr within nothrow or @nogc, or on a const OptionalChain!int. The exact static assert is: "Unable to call frontOr on type const(OptionalChain!int). It has to either be an input range, a Nullable!T, or an Optional!T".

In theory, i.e., without looking at the implementation of the optional library, these extra annotations comply with D's type system:

  1. We don't modify x by maybe calling the const method foo() on (presumably) a const(X) through the const(Optional!X).
  2. We don't modify x.oc.foo by calling .frontOr(3) because we merely have to examine the range x.oc.foo for emptiness and possibly take its front element. We never have to advance this range. Only if we were to call .popFront on a range, the range struct would have to be mutable, but .frontOr(3) should never call .popFront on any range.
  3. We provide a default value for frontOr without calling anything that throws, therefore frontOr will never throw.
  4. Since frontOr never throws, it will never allocate with the GC.

We can argue that this report should be split into two separate issues: 1. oc should work on a const(Optional!X) and 2. frontOr should infer that it will neither modify, throw, nor allocate, given its usage with a default value like in our example. But I filed them together because they're so closely related, and because above example code is more meaningful when it shows both.

Do you see conceptual difficulties in allowing x.oc.foo.frontOr(3) from within a method annotated const pure nothrow @safe @nogc?

For now, I've rewritten the example as:

int bar() const pure nothrow @safe @nogc {
    return x.empty ? 3 : x.front.foo;
}

SimonN avatar Apr 21 '23 21:04 SimonN

Hi!

Thanks for the report. Been a minute since I’ve used D now 😅. I will try and find time to look into this. Off the top of my head I don’t see why this shouldnt work. But the interplay between annotations in D have always been from what I remember a royal pain. Will post back when I get the chance to!

On Fri, 21 Apr 2023 at 23:15, Simon Naarmann @.***> wrote:

Using optional 1.3.0.

The following code works with no problems.

import optional;

class X { int foo() const pure nothrow @safe @nogc { return 5; } }

class Y { Optional!X x;

int bar() pure @safe
{
    return x.oc.foo.frontOr(3);
}

}

This compiles fine.

The problem arises when we want to annotate bar() not only as pure @safe, but as const pure nothrow @safe @nogc in the same way as we did with foo. In theory, i.e., without looking at the implementation of the optional library, these extra annotations comply with the type system:

  • We don't modify x by maybe calling the const method foo() on (presumably) a const(X) through the const(Optional!X).
  • We don't modify x.oc.foo by calling .frontOr(3) because we merely have to examine the range x.oc.foo for emptiness and possibly take its front element. We never have to advance this range. Only if we were to call .popFront on a range, the range struct would have to be mutable, but .frontOr(3) should never call .popFront on any range. The exact static assert is: "Unable to call frontOr on type const(OptionalChain!int). It has to either be an input range, a Nullable!T, or an Optional!T".
  • We provide a default value for frontOr without calling anything that throws, therefore frontOr will never throw.
  • Since frontOr never throws, it will never allocate with the GC.

We can argue that this report should be split into two separate issues: 1. oc should work on a const(Optional!X) and 2. frontOr should infer that it will neither modify, throw, nor allocate, given its usage with a default value like in our example. But I filed them together because they're so closely related, and because above example code is more meaningful when it shows both.

Do you see conceptual difficulties in allowing x.oc.foo.frontOr(3) from within a method annotated const pure nothrow @safe @nogc?

— Reply to this email directly, view it on GitHub https://github.com/aliak00/optional/issues/57, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAUEDTD53C53WHVKV3C7WH3XCL2IJANCNFSM6AAAAAAXHKQTNY . You are receiving this because you are subscribed to this thread.Message ID: @.***>

aliak00 avatar Apr 26 '23 04:04 aliak00