In the D spec, and in this document, safety refers to an absence of undefined behavior. safe language features and safe functions cannot lead to undefined behavior as long they're combined with other safe features. It's implied in the definition that safety also means memory safety. Which means no memory corruption.
In contrast, unsafe features and functions are those that can lead to undefined behavior if used incorrectly. In particular, they might corrupt memory.
For example, dereferencing a pointer is considered safe. In actuality, dereferencing a pointer can have undefined behavior, if the pointer is invalid. And writing through such a pointer can certainly corrupt memory. In contrast, creating an invalid pointer doesn't have undefined behavior. But dereferencing is the more common and useful operation, so it has been decided that dereferencing is safe while features that might create invalid pointers are unsafe.
D has three function attributes that deal with safety: @system
, @safe
, and @trusted
.
@system
is the default and provides no restrictions or guarantees. In @system
code, it is your job as a programmer to avoid undefined behavior.
But history has shown that human programmers can't be trusted with that task, at least not on a large scale. That's why D has @safe
. When you tag a function with @safe
, the compiler will verify that it is in fact safe. If you try to use unsafe features, the compiler will reject your code. That means whatever bugs you might have in @safe
code, they can never be bad enough to trigger undefined behavior; they will never lead to memory corruption.
The third attribute, @trusted
is a sort of compromise between @system
and @safe
. Just like in @system
functions, you can use unsafe features when implementing an @trusted
function. The compiler doesn't put on its safety goggles when looking at your code. But like @safe
functions, @trusted
functions must be safe. They are not allowed to exhibit undefined behavior, and they're not allowed to affect the state of the program in an unsafe way.
For example, you can create an invalid pointer in an @trusted
function. But you must not dereference it, and you must not return it from the function.
We know that writing @system
code is hard (which is why we got @safe
). Writing @trusted
code is even harder. Not only do you have to avoid undefined behavior, you also have to ensure that the function as a whole is safe.
The best way to avoid mistakes in @trusted
code is to not write @trusted
code. If you think you have to do it, make sure you at least avoid these common pitfalls:
Say you have some code that's mostly safe. But you need to call that one unsafe function foo
. You know that you need to use @trusted
, and the simplest way seems to write a little @trusted
wrapper around foo
. But don't just do this:
auto trustedFoo(M maybe, S some, P parameters)
@trusted /* No! Bad programmer! */
{
return foo(maybe, some, parameters);
}
If foo
is unsafe, then so is trustedFoo
. But @trusted
functions must be safe.
It's unfortunate that we can find examples of this even in Phobos, D's standard library:
static void trustedFree(T[] p) @trusted
{
import core.stdc.stdlib : free;
free(p.ptr);
}
trustedFree(xform1);
As long as the code stays exactly like this, it likely won't cause any trouble (I haven't actually checked thorougly). But the @safe
attribute has already been undermined. In the future, some unlucky contributor might accidentally add another trustedFree(xform1)
call. And then we're looking at a double free, which means undefined behavior.
Remember this sentence from above: "whatever bugs you might have in @safe
code, they can never be bad enough to trigger undefined behavior; they will never lead to memory corruption". trustedFree
breaks that guarantee.
Say you have a function template bar
. You're using some unsafe features in the implementation, but you've made sure that they're used correctly. So you can mark the function as @trusted
, right? No, usually you can't.
Most templates have completely arbitrary or only loosely restricted parameters. And D types can overload many operations that look benign at a cursory glance. Your code might end up making all sorts of function calls that didn't even look like calls to you.
For example, consider this simple function template:
auto bar(T)(T x)
{
auto y = T(0);
y = x;
return x;
}
Looks like it doesn't do much at all, right? But it might call:
- a constructor,
- a destructor,
- a copy constructor,
opAssign
.
And all of those might be unsafe, which means they must not be called from @trusted
code without special consideration. But you can't give special consideration when you don't even know what type T
actually is.
Some other features that might lead to unexpected function calls are:
- overloaded
cast
, - overloaded unary and binary operators like
!
and+
, alias this
.
The list is not meant to be complete.
An often-given advice for @trusted
functions is to keep them small. The idea is to mark only those things as @trusted
that actually need it. Then the rest of the code can enjoy the helpfully strict checks of @safe
.
Generally, that's good advice, but you have to be careful. No matter how small it is, any @trusted
function must be safe. Even if it's a function literal that is called immediately, the function must be safe.
Another bad example from Phobos:
b = (() @trusted => b.ptr[0 .. desired])();
That tiny function isn't safe. Depending on the value of desired
, it might make an out-of-bounds access, i.e. exhibit undefined behavior. And desired
comes from @safe
code. So a mistake in @safe
code (setting desired
incorrectly) can lead to undefined behavior. Safety has been undermined once again by an incorrectly applied @trusted
.
I have showed some common mistakes in @trusted
code, but I haven't exactly told you how to do it right. That's because I don't have a nice set of guidelines. I can often point out errors related to @trusted
, but that doesn't mean that I could write it correctly without making some other mistake of my own.
By its nature, @trusted
is a feature for experts. You need to know D in and out in order to use it correctly. You might need to know the language better than it is currently specified. And it's still a moving target, too, with a good amount of related compiler bugs.
This line from above might be the only true advice I can give: The best way to avoid mistakes in @trusted
code is to not write @trusted
code.
An article from the official D blog from a while ago, albeit I'm not sure how this plays with the recent default
@safe
proposal.