Thread overview
Optional Annotations for Memory Safety Categories
1 day ago
Mike Shah
1 day ago
Paul Backus
17 hours ago
Mike Shah
2 hours ago
Mike Shah
6 hours ago
Atila Neves
4 hours ago
Ben Jones
1 hour ago
Mike Shah
1 hour ago
Mike Shah
1 day ago

Title: Optional Annotations for Memory Safety Categories

Proposal Short Description

Allow an optional AssignExpression (similar to deprecated) for @safe, @trusted, and @system code.

// Currently legal syntax

// Per function safety category
void func1() @safe{}
void func2() @trusted{}
void func3() @system{}

// Everything that follows has safety category applied
@trusted:
   void func4(){}

// Everything within braces has safety category applied
@trusted{
   void func5(){}
}
// Proposed syntax to also be legal
void func1() @safe("This needs to be safe as it's part of client interface"){}
void func2() @trusted("Code Reviewed by Mike Pull Request #1234567, and is trusted for reason XYZ"){}
void func3() @system("Need to make a system call..."){}


// For consistency, this *could* also be possible as part of the proposal
// Every function below gets the same annotation.
@trusted("Module full of my safe library interface and glue functions"):
   void func4(){}
   void func_4() @trusted("annotation here overrides top level annotation");

// Annotation, *could* be allowed here as part of proposal for consistency.
@safe("Functions part of library XYZ must all be safe"){
   void func5(){}
   void func_5() @trusted("annotation here overrides top level annotation");
}

Background

The D programming language provides three categories of functions for memory safety: @safe @trusted, and @system. Currently these annotations are placed at the function level and enforce rules within the compiler for how code can be called from each annotated function. Currently functions marked with safe, trusted, and attribute can call following the graph below.

@safe <--> @safe <---> @trusted <--> @system <--> @system

  • @safe code provides memory safety gaureentees, and enforces bounds checking (even in release mode). An @safe function can call only into other @safe code, or @trusted code with a @safe interface.
  • @trusted code is the 'bridge' between calling @safe and @system code (and otherwise can call other @trusted code). @trusted code otherwise can do anything @system code can (though the interface must be @safe -- see https://dlang.org/spec/function.html#trusted-functions).
  • @system code can do anything, and is the current default. @safe code cannot call @system code.

Currently the default for functions in the language is @system. @system code allows operations to be performed that may otherwise cause memory corruption. @trusted code has the capabilities of @system functions, but we require that the function has an @safe interface.

Motivation

The primary motivation for this proposal is to annotate @trusted, @safe, and @system code at the site of the safety category designation. '@trusted' (as an example) annotation is reliant on a programmers verification or code review of the function. The why a function is @trusted however may or may not be documented. Code comments or documentation that otherwise exists (e.g. a pull request) and are not directly on the '@trusted' site may be more likely to grow 'stale'(authors personal anecdote).

@trusted is the particularly interesting to be annotated because it is the bridge between @safe and @system code. The temptation to make code @trusted in order to 'get things to work' should be documented. Observe below.

void func2() @trusted("made @trusted so that code works with @safe code... will update to @safe later");

The idea above is that with the AssignExpression (shown within the quotes above), the documentation is tightly coupled with the important annotation. Tooling may benefit otherwise from being able to directly extract out these annotations for "TODO" or other important notes in these annotations. Source control and the ability to see how these annotations 'change' over time could also provide value to developers for 'why' an interface became trusted/safe/system over time.

Adding an AssignExpression is also interested for @system and @safe and should be considered for consitency. As demonstrated below,

void libraryFunc1() @system("The @safe interface is in progress")

The motivation thus is to ensure memory safety annotations can be cleanly documented at the location of the attribute.

Other Notes

While @safe can be inferred and @system otherwise is the default, it may be of greater importance to have annotations available for @trusted. https://dlang.org/spec/memory-safe-d.html#usage.

  • Since Inference can detect @safe, it may be of use to have @safe("Compiler inferred") labeled annotations which could be useful to delineate between compiler generated attributes and explicitly user marked @safe code.
    • Otherwise, no change is needed.
  • Lack of providing a reason at the cite of a trusted/system/safe function could signal 'in-progress' or 'buggy' code when debugging. Thus documented safety categories may further provide confidence in code correctness.

Breakage to language

  • No known/expected breakage to current code, as this is an extension.
  • Note under pitfalls difference between getFunctionAttributes and getAttributes however, where I show how with UDA's we could get a useful string of information by a function.

Consistency

  • The deprecated attribute currently allows for an AssignExpression as a string literal or manifest constant to provide additional information as to why the deprecation message. https://dlang.org/spec/attribute.html#deprecated
  • assert and static_assert also optionally allow an argument list following the initial boolean expression for annotating useful information about expected behavior.

Potential Pitfalls/Discussion

  • Adding additional strings to safety attributes will take time to parse and store during compilation in the case of errors during compilation.
  • It is possible to use User Defined Attributes (UDAs) to otherwise implement this functionality. The proposed extension to syntax however is again to avoid having many UDAs attached to serve purely as annotations which could be forgotten by programmers. It appears 'getFunctionAttributes' and 'getAttributes' otherwise both return AliasSeq.
// Demonstration of the proposed feature
import std.stdio;

// ----------- Current ----------
void func2() @trusted{
		writeln("Do something");
}

// ----------- Possible ---------
// Using UDA's we can do something
// similar to the proposal.
struct trustmore{string x;}

void func() @trusted @trustmore("specific reason for @trusted"){
		writeln("Do something");
}

/* ---------- Proposed ----------
	 void func3() @trusted("reason"){
	 writeln("Do something");
	 }
 */

void main(){
		//      pragma(msg, __traits(getAttributes, func2));
		pragma(msg, __traits(getFunctionAttributes, func2));

		pragma(msg, __traits(getAttributes, func));
		pragma(msg, __traits(getFunctionAttributes, func));
}

// =================================================
// Output
//AliasSeq!("@trusted")
//AliasSeq!(trustmore("specific reason for @trusted"))
//AliasSeq!("@trusted")
// =================================================

  • mixins and templates could also combine multiple UDAs to add annotations. However, the proposed feature as part of the core language I speculate may be useful for tooling later on (e.g. with DMD as a library static analysis, or otherwise as part of error messaging). Being able to pull out the specific memory category (trusted/system/safe) rather than a collection of UDAs I suspect will be easier on the toolside.

  • Stylistically we may want to ask ourselves how adding annotations to the safety category would 'look' when reading/writing code. Below is an example with template constraints, a contract, and other attributes with the proposed feature. Below is a function otherwise to see what this looks like when combining other language features (other annotations, template constraint, and contract).

int func7(T)(T x) pure @nogc
@safe("adding some long documentation to provide value but this does require us to stylistically put safety category on a separate line so we can reasonably read someone elses code. Perhaps d-format can help here.")
  if(is(T: int))
  in{
    assert(x > 5);
  }
do {
	return x;
}

- The last thought I'll give is whether this opens the floodgates for other attributes (e.g. @nogc, pure, etc.) for allowing annotations as well. I'd like us to focus on the specific use case here where I think there is good value in annotating @trusted per-function or block, which I think is most valuable *and main focus** of this proopsal. But if we accept to move forward with this proposal, as always, it may be worth considering if adding this feature would effect other features (for either improving consistency in the language, or otherwise if this adds too much complexity to the compiler).

Other language references

1 day ago

On Sunday, 2 March 2025 at 15:07:30 UTC, Mike Shah wrote:

>

The primary motivation for this proposal is to annotate @trusted, @safe, and @system code at the site of the safety category designation. '@trusted' (as an example) annotation is reliant on a programmers verification or code review of the function. The why a function is @trusted however may or may not be documented. Code comments or documentation that otherwise exists (e.g. a pull request) and are not directly on the '@trusted' site may be more likely to grow 'stale'(authors personal anecdote).

@trusted is the particularly interesting to be annotated because it is the bridge between @safe and @system code. The temptation to make code @trusted in order to 'get things to work' should be documented. Observe below.

void func2() @trusted("made @trusted so that code works with @safe code... will update to @safe later");

I don't see how using a string literal for this instead of a comment makes any difference in how likely programmers are to document their use of @trusted, or keep that documentation up to date.

The reason comments become outdated is that (a) they are not checked by the compiler, so you do not automatically get feedback when they are wrong; and (b) since they are usually on separate lines from the code they refer to, they are often excluded from git diff output and are easy to miss in code review.

This proposal does not change either of these things. The annotation is a human-readable string which cannot be checked by the compiler, and putting it in the function's signature separates it from the code whose safety it is supposed to comment on.

19 hours ago
What is the functional difference to doing this:

```d
/**
Comment goes here
 */
@safe {

}
```
17 hours ago
On Monday, 3 March 2025 at 04:03:06 UTC, Richard (Rikki) Andrew Cattermole wrote:
> What is the functional difference to doing this:
>
> ```d
> /**
> Comment goes here
>  */
> @safe {
>
> }
> ```

Thanks for the feedback Rikki and Paul.

Same idea as comment, but comment lives with annotation. Similar to an assert, static_assert, or deprecate statement. The *why* is always attached.

If I wanted to for instance pull all @trusted functions from a codebase, I would not have to guess if comments above or below describe the behavior -- too often I've been bitten by stale comments from my own or others code.
16 hours ago
On 03/03/2025 7:03 PM, Mike Shah wrote:
> If I wanted to for instance pull all @trusted functions from a codebase, I would not have to guess if comments above or below describe the behavior -- too often I've been bitten by stale comments from my own or others code.


Can you please provide some examples of this?

Seeing it concretely show cases your motivation and the pain of not having it.

6 hours ago
On Monday, 3 March 2025 at 06:03:06 UTC, Mike Shah wrote:
> On Monday, 3 March 2025 at 04:03:06 UTC, Richard (Rikki) Andrew Cattermole wrote:
>> What is the functional difference to doing this:
>>
>> ```d
>> /**
>> Comment goes here
>>  */
>> @safe {
>>
>> }
>> ```
>
> Thanks for the feedback Rikki and Paul.
>
> Same idea as comment, but comment lives with annotation. Similar to an assert, static_assert, or deprecate statement. The *why* is always attached.

The difference in the two kinds of assertions is that the message gets printed. I also don't see a difference betweeen the proposal and a comment.

4 hours ago

On Monday, 3 March 2025 at 16:53:26 UTC, Atila Neves wrote:

>

On Monday, 3 March 2025 at 06:03:06 UTC, Mike Shah wrote:

>

On Monday, 3 March 2025 at 04:03:06 UTC, Richard (Rikki) Andrew Cattermole wrote:

>

What is the functional difference to doing this:

/**
Comment goes here
 */
@safe {

}

Thanks for the feedback Rikki and Paul.

Same idea as comment, but comment lives with annotation. Similar to an assert, static_assert, or deprecate statement. The why is always attached.

The difference in the two kinds of assertions is that the message gets printed. I also don't see a difference betweeen the proposal and a comment.

With the annotation, the compiler could include that in the error message:

@safe function xyz() cannot call @system function abc(): "unsafe because of syscalls"

2 hours ago
On Monday, 3 March 2025 at 06:09:24 UTC, Richard (Rikki) Andrew Cattermole wrote:
> On 03/03/2025 7:03 PM, Mike Shah wrote:
>> If I wanted to for instance pull all @trusted functions from a codebase, I would not have to guess if comments above or below describe the behavior -- too often I've been bitten by stale comments from my own or others code.
>
>
> Can you please provide some examples of this?
>
> Seeing it concretely show cases your motivation and the pain of not having it.

- The use case started from the discussion here: https://forum.dlang.org/thread/v88gmi$1v6p$1@digitalmars.com?page=3
- As a test case I took my raytracer and played with @safe a bit to see if I could make my raytracer functions @safe (I have not pushed the changes yet, but here is the codebase if anyone wants to try: https://github.com/MikeShah/Talks/tree/main/2022/2022_dconf_online/raytracer) .
- The easiest thing for me to do is make every function @safe by hand, and if that fails just make each function @trusted and work backwards.
- That's about as far as I got before I had more time to put into things.
- If I hand this code to someone, I then am wondering if they'll trust my annotations. Will they read my comments (hopefully!)? Will I trust my annotations?


The idea to revisit something like @trusted("reason") reemerged this past week when I created a bug in another graphics program. Or rather -- an old comment led many students astray on what I was actually asking. Oops :)

It reminded me (even as an avid documentation writer) that the source is the ground truth. Providing more context as an annotation I would find useful.

The question is, if others find this of high value. Hence, the rest of the proposal to try to make this consistent with some other areas (e.g. deprecated, assert, etc.), and potentially open the flood gates for something like unittest("reason"){} which I think Dennis (and probably others) have mentioned.


1 hour ago
On Monday, 3 March 2025 at 16:53:26 UTC, Atila Neves wrote:
> On Monday, 3 March 2025 at 06:03:06 UTC, Mike Shah wrote:
>> On Monday, 3 March 2025 at 04:03:06 UTC, Richard (Rikki) Andrew Cattermole wrote:
>>> What is the functional difference to doing this:
>>>
>>> ```d
>>> /**
>>> Comment goes here
>>>  */
>>> @safe {
>>>
>>> }
>>> ```
>>
>> Thanks for the feedback Rikki and Paul.
>>
>> Same idea as comment, but comment lives with annotation. Similar to an assert, static_assert, or deprecate statement. The *why* is always attached.
>
> The difference in the two kinds of assertions is that the message gets printed. I also don't see a difference betweeen the proposal and a comment.

This might be part of an answer to Rikki's question as well, but I want to be able to pull out the @trusted 'string' easily for tooling rather than parse comments above or below the function.

```d
struct trustmore{string x;}

/*
Some other comments about the function

TODO make this safe later

Some other comments
*/
void func() @trusted @trustmore("TODO make this safe later"){
	// ...
}


void main(){
	pragma(msg, __traits(getAttributes, func));
	// pragma(msg, __traits(getFunctionAttributes, func));
}
```

Can be done using UDA's as shown above, but my thought is this is more consistent with other features (e.g. static_assert, assert, deprecate) that it may be good to make easier.

1 hour ago

On Monday, 3 March 2025 at 18:26:33 UTC, Ben Jones wrote:

>

On Monday, 3 March 2025 at 16:53:26 UTC, Atila Neves wrote:

>

On Monday, 3 March 2025 at 06:03:06 UTC, Mike Shah wrote:

>

On Monday, 3 March 2025 at 04:03:06 UTC, Richard (Rikki) Andrew Cattermole wrote:

>

What is the functional difference to doing this:

/**
Comment goes here
 */
@safe {

}

Thanks for the feedback Rikki and Paul.

Same idea as comment, but comment lives with annotation. Similar to an assert, static_assert, or deprecate statement. The why is always attached.

The difference in the two kinds of assertions is that the message gets printed. I also don't see a difference betweeen the proposal and a comment.

With the annotation, the compiler could include that in the error message:

@safe function xyz() cannot call @system function abc(): "unsafe because of syscalls"

Indeed, I was thinking about a few scenarios where a more verbose error report could be useful. You could imagine the scenario you have above, but also to see something in a stack trace:

ERROR: exception out of bounds
@trusted function xyz1("trusted because of mike");
@trusted function xyz2("trusted because of mike");
@trusted function xyz3("trusted because of mike");

^ Oops, don't trust the code Mike wrote :)

I was also thinking about code coverage reports for code marked @trusted. Being able to see a summary of the 'why' and hand it off to someone to chop away at it is appealing. My specific use case for this scenario is thinking about binding development and transitioning to provide a @safe interface over time.