Thread overview
a design flaw in DIP1035, its remedy, and the implication for @system variables
Apr 13, 2022
Zach Tollen
Apr 13, 2022
Dennis
Apr 13, 2022
Nick Treleaven
Apr 13, 2022
Paul Backus
April 13, 2022

DIP1035 has a design flaw in its current form.

Let us assume it has been implemented as described.

Let us assume a naive user has an extern variable, which we will use as the example for the whole illustration.

extern int* x;

According to the DIP, since this variable could possibly hold an unsafe value, it is immediately promoted to a @system variable. Our naive user then tries to write to it in a @safe setting.

void main() @safe {
    *x = 10; // error: @system variable x may not be accessed in @safe code
}

Even if the user reassigns the pointer to a safe value, thus making the actual value safe, the variable is forevermore interpreted by the compiler as @system.

void fn() @safe {
    () @trusted { x = new int; }();  // we're @safe now, right?
    *x = 10; // error: @system variable x may not be accessed in @safe code
}

Promoting x to a @system variable has had the effect of basically making it unusable. Eventually, the user realizes that the only way solve the problem is to declare the original variable @trusted.

@trusted extern int* x;

That's what he wanted the whole time. Indeed, that's what everyone who inadvertently initializes a variable to an unsafe value wants. No one ever wants a variable to become permanently unwieldy, just because it may have begun in an unsafe state.

Now let's imagine that a compiler writer realizes this is a common pattern, and wants to improve the error message. So he makes a special flag to mark unsafely initialized variables internally, to distinguish them from explicitly @system ones. The semantics don't change; it's just a clearer error.

extern int* x;
@system int y;

void fn() @safe {
    x = new int; // error: unsafely initialized variable `x` cannot be accessed
                 //   in @safe code - try marking its declaration @trusted?
    y++;         // error: @system variable y may not be accessed in @safe code
}

We're almost there. The distinction between unsafely-initialized, and explicitly-@system variables is starting to be felt.

Finally, it dawns on the compiler writers that they've been abusing the user the whole time. They already knew, from the moment the variable was declared, that there was going to be a problem. They had an unsafely-initialized variable. They should have forced the user then and there to say whether it was @trusted or @system.

extern int* x; // error: unsafely initialized variables must be explicitly
               //    annotated with `@trusted`, or `@system`

Boom. We have the real solution to unsafely-initialized global variables.

So that fixes the design flaw.

And it leaves us with a new interpretation of @system variables. Key points:

  • Implicitly inferring a variable to be @system is little more than a form of torture. The user always wanted @trusted.

  • Actual @system variables are rare, and never accidental. Being intentionally hard to use, they should only be given to people who ask for them directly.

April 13, 2022

On Wednesday, 13 April 2022 at 12:14:53 UTC, Zach Tollen wrote:

>

Finally, it dawns on the compiler writers that they've been abusing the user the whole time. They already knew, from the moment the variable was declared, that there was going to be a problem. They had an unsafely-initialized variable. They should have forced the user then and there to say whether it was @trusted or @system.

extern variables should be rare in D, they're usually the result of translated C code. Translated C code isn't @safe, so has no problem accessing @system variables. Introducing forced attributes seems more annoying to me than inferred @system variables.

April 13, 2022

On Wednesday, 13 April 2022 at 12:14:53 UTC, Zach Tollen wrote:

>
extern int* x;
@system int y;

void fn() @safe {
    x = new int; // error: unsafely initialized variable `x` cannot be accessed
                 //   in @safe code - try marking its declaration @trusted?

That would encourage people to use trusted even when it is actually an unsafe value.

...

>

They should have forced the user then and there to say whether it was @trusted or @system.

That would break existing code. @system is a good default for extern (C) unsafe globals and functions, even if we make D @safe by default.

April 13, 2022

On Wednesday, 13 April 2022 at 12:14:53 UTC, Zach Tollen wrote:

>
void fn() @safe {
    () @trusted { x = new int; }();  // we're @safe now, right?
    *x = 10; // error: @system variable x may not be accessed in @safe code
}

Promoting x to a @system variable has had the effect of basically making it unusable. Eventually, the user realizes that the only way solve the problem is to declare the original variable @trusted.

@trusted extern int* x;

Worth noting that this is not the only solution. One can also provide a @trusted interface using getter and setter functions; e.g.,

extern int* x; // inferred @system
@system bool initializedX = false;

@trusted void safeX(int* value)
{
    x = value;
    initializedX = true;
}

@trusted int* safeX()
in (initializedX)
{
    return x;
}

@safe void fn()
{
    safeX = new int;
    *safeX = 10;
}