Thread overview
Best way to make a template function conditionally @trusted
Apr 01, 2021
tsbockman
Apr 02, 2021
Paul Backus
Apr 02, 2021
tsbockman
Apr 02, 2021
ikod
Apr 03, 2021
tsbockman
April 01, 2021

Suppose I have a templated struct member function for which I can compute at compile-time when the function is memory safe, and when it is not. But, the compiler cannot correctly determine this automatically.

What is the best way to express this in code? Other than straight-up duplicating the implementation, the only answer I've come up with so far is to create a private @system implementation function, and then separate public @system and public @trusted wrappers with appropriate template constraints.

Is there a better way?

April 02, 2021
On Thursday, 1 April 2021 at 22:35:01 UTC, tsbockman wrote:
> Suppose I have a templated struct member function for which I can compute at compile-time when the function is memory safe, and when it is not. But, the compiler cannot correctly determine this automatically.
>
> What is the best way to express this in code? Other than straight-up duplicating the implementation, the only answer I've come up with so far is to create a `private @system` implementation function, and then separate `public @system` and `public @trusted` wrappers with appropriate template constraints.
>
> Is there a better way?

Here's a technique I've used:

    // infer function attributes
    auto func(...)
    {
        // do your compile-time memory-safety check here
        enum bool shouldBeSystem = ...;

        static if (shouldBeSystem) {
            // force inference of @system
            cast(void) () @system {}();
        }

        () @trusted {
            // rest of code goes here
        }();
    }

As long as the compile-time check is correct, this is sound: the @trusted lambda can be called from @safe code if and only if `shouldBeSystem == false`.
April 02, 2021
On Friday, 2 April 2021 at 00:03:32 UTC, Paul Backus wrote:
> On Thursday, 1 April 2021 at 22:35:01 UTC, tsbockman wrote:
>> Is there a better way?
>
> Here's a technique I've used:
> ...
> As long as the compile-time check is correct, this is sound: the @trusted lambda can be called from @safe code if and only if `shouldBeSystem == false`.

Thanks. That is somewhat better than my version.
April 02, 2021

On Thursday, 1 April 2021 at 22:35:01 UTC, tsbockman wrote:

>

Suppose I have a templated struct member function for which I can compute at compile-time when the function is memory safe, and when it is not. But, the compiler cannot correctly determine this automatically.

Compiler should be able to derive safety of templated functions. You may just omit @safe/@trusted. But there are some rules and restrictions: https://dlang.org/spec/function.html#function-attribute-inference

April 03, 2021

On Friday, 2 April 2021 at 19:49:30 UTC, ikod wrote:

>

On Thursday, 1 April 2021 at 22:35:01 UTC, tsbockman wrote:

>

Suppose I have a templated struct member function for which I can compute at compile-time when the function is memory safe, and when it is not. But, the compiler cannot correctly determine this automatically.

Compiler should be able to derive safety of templated functions. You may just omit @safe/@trusted.

The compiler's approach to verifying memory safety is very simplistic: it declares a function non-@safe if any potentially unsafe operation is found in its implementation, without regard for the context. It can only ever infer @safe or @system, never @trusted.

The reason @trusted is in the language at all is to allow the programmer to manually mark as memory safe those functions which contain operations that would be unsafe in some other context, but which the programmer has manually analyzed and verified to be incapable of violating memory safety, no matter what inputs it receives.

Consider the following program:

import std.stdio;

void writeSafe()(int[2] stuff ...) {
    foreach(n; 0 .. stuff.length)
        writeln(stuff[n]);
}
void writeTrusted()(int[2] stuff ...) {
    foreach(n; 0 .. stuff.length)
        writeln(stuff.ptr[n]);
}

void main() @safe {
    writeSafe(3, 5);
    writeTrusted(3, 5);
}

writeSafe and writeTrusted generate identical code, and are equally memory safe in reality. The compiler even knows this on some level, because it correctly deduces that the bounds check for stuff[n] can never fail, and omits it even in writeSafe.

Nevertheless, because .ptr can be used to violate memory safety in other contexts, writeSafe is inferred as @safe, while writeTrusted is inferred as @system. And so, the program above will not compile as it stands.

This is, of course, a trivial example where there is no benefit to using the non-@safe version, but there are more complex cases where the desired algorithm is memory safe as a whole, but it cannot be expressed in D without using some operations that are forbidden in @safe code.