Programmer-Controlled `@trusted`
Zach Tollen
notmyrealemail at whatever.eee
Fri Jan 10 17:40:41 UTC 2025
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:
1) *All* actions taken by a computer program are *supposed* to be
memory-safe.
2) *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.
3) *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`).
4) *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).
5) 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 as `try`). 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 be `cast(@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.
6) 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.
More information about the dip.ideas
mailing list