I shared this in a small D Discord about a year ago. I had had some ideas about the way the keywords are used in D, although I don’t necessarily program much in D myself. I just like thinking about it, for rather obscure reasons.
This is called “Programmer-Controlled @trusted” in contrast to “Language-Restricted @trusted”, which is what D has always had.
I was looking at the memory safety attributes of Rust, and realized that despite being a language entirely devoted to the idea of memory safety, it nonetheless used only one keyword (unsafe
) whereas D had three (@system
, @safe
, @trusted
).
These principles were arrived at in Spring of 2024:
-
All actions taken by a computer program are supposed to be memory-safe.
-
Most memory-safe actions can be detected as such by the compiler. So many, in fact, that having to mark the small percentage of potentially-unsafe actions will not be prohibitively distracting in general. Thus, as the consensus seems to be, programmers should be able to assume that every program action which is not marked as dangerous/unsafe is safe.
-
Most potentially-unsafe actions can be detected automatically by the compiler. In order to ensure that they are safe, therefore, the compiler can require that such actions are manually verified as such (
@trusted
). -
Some unsafe actions cannot be detected by the compiler, but can be detected by the programmer. Thus a programmer should be able to actively indicate that a given action is potentially unsafe (
@system
functions and variables). -
All potentially-unsafe actions, which have been manually verified to be safe, should be able to be "switched off" at precisely the location of the action, so that no excess is included in the verification. Thus, if the unsafe-but-verified action is an entire function, the function can be marked
@trusted
. If the unsafe-but-verified action is a statement, the statement can be marked@trusted
(same syntax astry
). And even if the verified action is a single expression, the programmer should still be allowed to mark it@trusted
. (The most natural D syntax for this would becast(@trusted)
.)So what we’re dealing with here is a system of alarms, and the means by which to switch them off. Most safety alarms are built-in, but some can be added manually. And the means to switch them off must be up to the programmer. He can cast a wide net, or be extremely specific as the needs of the situation demand.
-
D differs from Rust in that interaction with C is a huge priority. And since C modules are not checked for safety by default, the usability of C modules in D may become prohibitively distracting if every unsafe C action had to be switched off manually. Thus, we need a way to handle C imports without undo inconvenience. To solve this, we return to the principle which states that a programmer should be able to switch off the alarm wherever the needs of his situation demand. Thus,
@trusted import awesomeClibrary;
is now allowed.
———
Since posting in the small D Discord last year, two separate investigations (first by the leader of the OpenD fork, and then by the mainline D leader) have been conducted into the practical feasibility of having @safe
be the default. Both concluded that some variation of a two- or multi-tiered safety system is preferable to a universal @safe-by-default system. They both concluded that the highest level of memory safety which the compiler is capable of detecting would be too burdensome for the casual or non-safety-critical user—and thus a lower, less intrusive tier should be introduced as the default.
The above original proposal is nonetheless still suggested, as its cost-benefit profile is essentially orthogonal to whether it applies to just one, or to multiple safety tiers.
———
In order to accelerate the evaluation of "Programmer-Controlled @trusted
", I will address three points which were brought up when it was first posted:
Q: What would be the fate of @trusted
lambdas?
A: It's only where a potentially-unsafe operation (called @system
in D) occurs, that the compiler should generate an error. And there's no reason to force the programmer to enclose any more than precisely what is considered unsafe, when it is manually verified. This need has always been felt, but was not expressible, so people have been settling for the trusted lambdas because it's simply as close as they could get to being able to tell the compiler exactly what they wanted to @trust. Once you can mark statements or individual expressions specifically, there is no further need for @trusted
lambdas. @trusted
on any function would remain simply a shorthand for trusting the entire function. But the need for creating a whole function just to trust one statement or expression would obviously no longer exist.
Q: Rumor: The Rust language having only one related keyword unsafe
caused confusion when searching for unsafe code in Rust programs.
A: D is actually in a better position than Rust insofar as D already has two keywords (@trusted
and @system
) for these purposes. What I realized about Rust is that they got away with using only one keyword, because when attached to a code statement, unsafe
means "turn off the alarm." But when attached to a function signature, it means "turn on the alarm." D by contrast can always use @system
to turn the alarm on, and @trusted
to turn it off. Thus, searching for @trusted
will always take you straight to the potentially unsafe code in a project.
Q: What about cleanup code? How could you say you want to trust implicit destructors, for example?
Someone in the chat suggested scope(exit) @trusted;
as a kind of kludge to be able to trust a function’s cleanup code, which would probably work.