July 04

I already outlined it here. In present-day D, for any module, the default function attributes are @system, throw, @gc, and impure. The idea is to allow changing the default for a module or in a block of declarations.

Note: Changing the default only affects non-inferred types and declarations. Attribute inference for templates or auto and nested functions are unaffected.

Syntax

For changing the module default, use this:

default @safe
module m;

For changing the block default, use this:

default @safe:
// or
default @safe { … }

Semantics

There is a subtle difference between module defaults and block defaults:
Block defaults only affect declarations. Module defaults apply to anything lexically in the module which can carry the attribute, in particular, function pointer and delegate types.

That means that in a default @safe module, every function declaration and every function pointer or delegate type lexically in that module will be @safe unless marked @system. Blocks can override the module default for declarations.

default @safe
module m;

// Note: module defaults apply to functions and function pointer/delegate types
// spelled out in the module: `callback` is implicitly `@safe`
int f(int function() callback) => callback();
static assert(is(typeof(&f) == int function(int function() @safe) @safe));


// Note: `g` is not inferred; default (@safe) applies
void g()
{
    int* p;
    int x;
    p = &x; // Error: address of variable `x` assigned to `p` with longer lifetime
}

// Note: `h` is inferred @system; defaults are irrelevant
auto h()
{
    int* p;
    int x;
    p = &x; // Okay, makes `h` a `@system` function
}
static assert(!is(typeof(&h) : void function() @safe));

Default blocks are similar to normal blocks, except they don’t directly affect inference:

module m;

default @safe:

// Note: block defaults only apply to declarations (e.g. functions),
// but not function pointer/delegate types that are parameters or return types.
// For `callback`, the module default applies, which is unset, i.e. `@system`.
int f1(int function() callback); // => callback(); // Error
static assert(is(typeof(&f1) == int function(int function() @system) @safe));

// An alias is a declaration, so the block default applies,
// and `FP` is `int function() @safe`.
alias FP = int function();
int f2(FP callback) => callback();
static assert(is(typeof(&f2) == int function(int function() @safe) @safe));


// Note: `g` is not inferred; default (@safe) applies
// (same as above)
void g()
{
    int* p;
    int x;
    p = &x; // Error: address of variable `x` assigned to `p` with longer lifetime
}

// Note: `h` is inferred @system; defaults are irrelevant
// (same as above)
auto h()
{
    int* p;
    int x;
    p = &x; // Okay, makes `h` a `@system` function
}
static assert(!is(typeof(&h) : void function() @safe));

The behavior of block defaults is consistent with attribute blocks, which likewise affect only declarations, but not function parameters or return types of function pointer or delegate types.

// Note: Not a default, this is current-day D semantics.

@safe:

int f1(int function() callback);
static assert(is(typeof(&f1) == int function(int function() @system) @safe));

alias FP = int function();
int f2(FP callback) => callback();
static assert(is(typeof(&f2) == int function(int function() @safe) @safe));

Grammar

    ModuleDeclaration:
-       ModuleAttributes? module ModuleFullyQualifiedName ;
+       ModuleAttributes? ModuleDefaultAttributes? module ModuleFullyQualifiedName ;
+
+   ModuleDefaultAttributes:
+       default DefaultAttributeList

    ModuleAttributes:
        ModuleAttribute
        ModuleAttribute ModuleAttributes

    ModuleAttribute:
        DeprecatedAttribute
        UserDefinedAttribute
    DeclDef:
        AttributeSpecifier
+       DefaultAttributeSpecifier
        …

+   DefaultAttributeSpecifier:
+       default DefaultAttributeList :
+       default DefaultAttributeList { DeclDefs? }
+
+   DefaultAttributeList:
+       DefaultAttribute DefaultAttributeList?
+
+   DefaultAttribute:
+       pure
+       nothrow
+       @ safe
+       @ nogc

    Attribute:
        …
        const
+       default ( DefaultAttributeList )
        final
        …

This grammar allows pure default @safe @nothrow static (followed by {…} or :) and by maximum munch, would mean that default applies to @safe and @nothrow, but not static. The language should reject that and require
pure default(@safe nothrow) static, or else
pure{ default @safe nothrow: static: … } or
pure: default @safe nothrow: static:, respectively.
That is, default without parentheses is only allowed with default in front and no trailing that can’t be a default.

The downside of default(@safe) is that it somewhat suggests it’s an attribute of its own, but it would only be allowed for blocks and module declarations, but not on declarations directly, where it makes little sense:

  • On non-inferred declarations, default(@safe) is @safe.
  • On inferred declarations, default(@safe) means nothing.