@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