@trusted considered harmful

Jonathan M Davis jmdavisProg at gmx.com
Sat Jul 28 13:51:09 PDT 2012


On Saturday, July 28, 2012 10:02:43 Andrei Alexandrescu wrote:
> On 7/27/12 8:08 PM, David Nadlinger wrote:
> > First, there is no point in having @trusted in the function signature.
> > Why? From the perspective of the caller of the function in question,
> > @safe and @trusted mean exactly the same thing. If you are not convinced
> > about that, just consider that you can wrap any @trusted function into a
> > @safe function to make it @safe, and vice versa.
> 
> If @trusted is not part of the signature, we can't enable e.g. analyzers
> that verify an entire program or package to be safe. This is not
> something that's currently used, but I'd hate to look back and say,
> "heck, I hate that we conflated @trusted with @safe!"

But from the caller's perspective, @safe and @trusted are identical. And if 
you want a function to be @safe rather than @trusted, all you have to do is 
create an @trusted helper function which has the implementation and have the 
actual function call it, and the the function can be @safe. I don't see how it 
makes any difference at all whether a function is marked @safe or @trusted 
except for the fact that with @trusted, the implementation can _directly_ use 
@system stuff, whereas with @safe, there has to be another layer in between. 
There's really no difference from the outside and no difference in terms of the 
actual level of safety.

As far as I can tell, having @trusted being treated differently in the function 
signature buys us nothing. All of the difference is in whether the compiler 
should error out on @system stuff being use inside the functions. So, if we had 
@trusted blocks, and @trusted on a function meant that it was @safe with the 
entire body being inside of an @trusted block, as far as the caller and 
signature go, the behavior would be identical to now except that the mangling 
would be different.

> > But the much bigger problem is that @trusted doesn't play well with
> > template attribute inference and makes it much too easy to accidentally
> > mark a function as safe to call if it really isn't. Both things are a
> > consequence of the fact that it can be applied at the function level
> > only; there is no way to apply it selectively to only a part of the
> > function.
> 
> This could be a more serious problem. Could you please write a brief
> example that shows attribute deduction messing things up? I don't
> understand how marking a template as @trusted is bad.

Marking pretty much _any_ templated function - especially range-based 
functions -  as @trusted is bad. If I have

auto func(R)(R range) if(isForwardRange!R)
{
    while(!r.empty && !condition)
    {
        //do @system stuff
    }

    return range.front;
}

and func does @system stuff which I _know_ is valid based on how it's used 
(emplace, scoped, pointer arithmetic, etc.), then ideally I'd be able to mark 
those operations as @trusted, but what about front, popFront, empty, save, 
etc.? I don't know what they do internally. I don't know whether they're @safe 
or @system. So, I can't mark func as @trusted, or I could be marking @system 
code as @trusted when it really isn't safe.

Take the new std.range.RefRange for example. The body of Its save function is 
supposed to look like this:

import std.conv;
alias typeof((*_range).save) S;
static assert(isForwardRange!S, S.stringof ~ " is not a forward range.");
auto mem = new void[S.sizeof];
emplace!S(mem, cast(S)(*_range).save);
return RefRange!S(cast(S*)mem.ptr);

All of the stuff related to emplace can be marked as @trusted, but save can't 
be marked as @trusted because of that call to _range's save, which may or may 
not be @safe. So, RefRange uses static ifs and mixins to create multiple 
versions of the function which are marked as @system or @trusted based on 
whether _range's save is @system or @safe. It's made even worse by the fact 
that constness isn't inferred (issue# 8407), but the problem with @safe 
remains even if const or inout starts being inferred.

In some cases, you can use helper functions which are marked as @trusted to 
segregate the @system code that you know is safe, but in others, that just 
doesn't work (or is really messy if it does), and you need to use static ifs 
to provide multiple versions of the function. It's exactly the kind of 
situation for which we introduced attribute inferrence in the first place, 
except in this case, you _can't_ use inferrence, because you obviously can't 
infer @trusted.

By being able to mark blocks of code as @trusted instead of whole functions, 
the required code duplication is significantly reduced, and if the functions of 
unknown safety being called are separate enough from the code marked as 
@trusted (i.e. not intertwined like they are with the line using save in 
RefRange's save), then _no_ code duplication is required. But even if the 
potentially unsafe code is intertwined with the @trusted code, at least the 
code duplication can be restricted to just a few lines. So, with const/inout 
inferred and @trusted blocks, RefRange's save should be able to become 
something like this;

@property auto save()
{
    import std.conv;
    alias typeof((*_range).save) S;
    static assert(isForwardRange!S, S.stringof ~ " is not a forward range.");

    auto mem = new void[S.sizeof];

    static if(isSafelyCallable!((){(*_range).save;}))
        @trusted { emplace!S(mem, cast(S)(*_range).save); }
    else
        emplace!S(mem, cast(S)(*_range).save);

    @trusted { return RefRange!S(cast(S*)mem.ptr); }
}

which is _far_ shorter than what it is now (around 50 lines of code), since 
you only need _one_ function declaration instead of four.

- Jonathan M Davis


More information about the Digitalmars-d mailing list