Jump to page: 1 28  
Page
Thread overview
@trusted attribute should be replaced with @trusted blocks
Jan 15
Ogi
Jan 15
ag0aep6g
Jan 15
ag0aep6g
Jan 15
ag0aep6g
Jan 15
ag0aep6g
Jan 15
ag0aep6g
Jan 16
IGotD-
Jan 16
ag0aep6g
Jan 16
ag0aep6g
Jan 17
jmh530
Jan 17
jmh530
Jan 17
jmh530
Jan 17
IGotD-
Jan 16
ag0aep6g
Jan 16
Ogi
Jan 15
IGotD-
Jan 15
IGotD-
Jan 15
IGotD-
Jan 15
ag0aep6g
Jan 16
IGotD-
Jan 16
IGotD-
Jan 16
Ogi
January 15
There was a discussion a few years ago [1], but now it came up again in the light of @safe-by-default DIP. I’ve created a new thread for this subject because it’s off-topic to DIP 1028 but still deserves attention, especially now when there’s a shift towards memory safety.

This proposal is independent to DIP 1028. We can have @trusted blocks without @safe by default and vice versa. But they go along nicely.

The idea is to remove @trusted as a function attribute and instead introduce @trusted blocks:

@safe fun() {
    //safe code here
    @trusted {
        //those few lines that require manual checking
    }
    //we are safe again
}

Generally, only a few lines inside @trusted are actually unsafe. With @trusted blocks parts that require manual checking will stand out. Why should we leave the task of identifying problematic places to the reader?

Once the dangerous parts are enclosed inside @trusted blocks, memory safety of the rest of the function is guaranteed by a compiler. Why should we refuse from the additional checks?

Actually, there’s already a workaround for injecting unsafe section to @safe code: just put it inside @trusted lambda that’s called immediately. It’s featured in the “How to Write @trusted Code” in D blog. Even Walter admits that he’s using this trick in Phobos. So why should we resort to dirty hacks that could also harm performance and compilation time if we can make it part of a language?

Some would say that we need @trusted attribute to distinguish functions that are checked by compiler and functions that are checked manually. But is @safe actually safe? You can’t tell that a “safe” function doesn’t call @trusted functions somewhere along the road, or doesn’t use the aforementioned lambda hack. Why pretend that @safe guarantees safety if it’s nothing more than a pinky promise?

If you think of it, it makes no sense for @trusted to be a function attribute. It doesn’t describe its behavior but its implementation. If you call a function, it should not be your concern; @safe and @trusted are the same thing for you. But these two attributes result in two different signatures. If a function expects a @safe callback, you can’t pass a @trusted function to it without @safe wrapping. If some library makes a function that used to be @safe @trusted, that’s a breaking change. Etc, etc. Why should we introduce complexity out of nowhere?

Without @trusted the safety design will be much simpler to grasp. Currently the are three vague keywords, complex rules on which functions can call which and best practices on which attribute you should choose. Without @trusted, it’s simple as potato: there are @safe and @system functions, @safe require putting unsafe parts inside @trusted blocks and can only call other @safe functions. We should not disregard the learning experience, because with every overcomplicated aspect of the language we lose some potential users.

With @safe as default upon us, it’s especially important to get safety right.

There is a risk that users would slap @trusted on everything just to make it work. While the new syntax wouldn’t stop them all, putting a whole function (or an entire module) inside of a block would scream code smell.

We can’t expect all users to be world-class programmers. The proposed design is more foolproof than the current safety trichotomy.
A user could create a @trusted function to do something unsafe and do it properly, but also do something that he expects to be safe while it’s not (like, calling a @system function thinking that it’s @safe). In a @safe function with @trusted blocks it wouldn’t be possible to do something unsafe if you don’t mean it.

To sum up, @trusted blocks will make it easier to write safe code, read safe code and learn the language. This proposal aligns with the current direction of the language towards memory safety.

How much will this change break? Probably not that much, because @trusted functions are not common.

To upgrade your code, the proper thing to do would be to put only the unsafe parts inside @trusted blocks. It would require some manual work, but it’s not for naught, since it would re-enable compiler checks outside of @trusted blocks. You can even find some bugs your eye could miss!

Of course, you can also just put the whole function body inside a @trusted block and call it a day. Not great, but just as bad as the current @trusted attribute.

Additional thoughts.

Obviously, @trusted block must not introduce a new scope. If you need a new scope, just use double curly.

We could also allow applying @trusted to a single line:
@trusted unsafeFunction();
But I don’t think that’s a good idea, as it would make it just too easy to call unsafe functions from @safe code.

With only @safe and @system attributes left, it would make perfect sense to rename @system to @unsafe, as suggested by Manu. The only ones who will protest are those who use “I write @system code” as a pickup line.

It would be possible to write a tool that analyzes code safety and e.g. shows how many @trusted lines are in the dub package.

The @ should be probably dropped.

[1] https://forum.dlang.org/thread/blrglebkzhrilxkbprgh@forum.dlang.org
January 15
On Wednesday, 15 January 2020 at 14:30:02 UTC, Ogi wrote:
> The idea is to remove @trusted as a function attribute and instead introduce @trusted blocks:
>
> @safe fun() {
>     //safe code here
>     @trusted {
>         //those few lines that require manual checking
>     }
>     //we are safe again
> }
>
> Generally, only a few lines inside @trusted are actually unsafe.

So here's the problem with this approach (which was mentioned by several people in the discussion): the actual safety of a function like this is usually down to the combination of the lines that (in your example) are both inside and outside the @trusted block.

What that means is that it's important that an external user of the function doesn't just see it as @safe, but recognizes that the function -- as a whole -- is one whose promise of safety is conditional on its internals being correct.  And that's essentially what @trusted is for.

So, a better approach would be for the function to be marked up like this:

@trusted fun ()    // alerts the outside user
{
    // lines that on their own are provably safe go here
    @system {
        // these lines are allowed to use @system code
    }
    // only provably safe lines here again
}

... and the compiler's behaviour would be to explicitly verify standard @safe rules for all the lines inside the @trusted function _except_ the ones inside a @system { ... } block.

Cf. Steven Schveighoffer's remarks here: https://forum.dlang.org/post/qv7t8b$2h2t$1@digitalmars.com

This way the function signature gives a clear indicator to the user which functions are provably @safe, and which are safe only on the assumption that the developer has done their job properly.
January 15
On 15.01.20 17:54, Joseph Rushton Wakeling wrote:
> On Wednesday, 15 January 2020 at 14:30:02 UTC, Ogi wrote:
[...]
>> @safe fun() {
>>     //safe code here
>>     @trusted {
>>         //those few lines that require manual checking
>>     }
>>     //we are safe again
>> }
[...]
> So here's the problem with this approach (which was mentioned by several people in the discussion): the actual safety of a function like this is usually down to the combination of the lines that (in your example) are both inside and outside the @trusted block.

Yup. But a proposal could specify that that's the intended meaning for an @safe function that contains @trusted blocks. Whereas it's more of a cheat when we use @trusted nested functions like that.

[...]
> So, a better approach would be for the function to be marked up like this:
> 
> @trusted fun ()    // alerts the outside user
> {
>      // lines that on their own are provably safe go here
>      @system {
>          // these lines are allowed to use @system code
>      }
>      // only provably safe lines here again
> }
> 
> ... and the compiler's behaviour would be to explicitly verify standard @safe rules for all the lines inside the @trusted function _except_ the ones inside a @system { ... } block.
> 
> Cf. Steven Schveighoffer's remarks here: https://forum.dlang.org/post/qv7t8b$2h2t$1@digitalmars.com
> 
> This way the function signature gives a clear indicator to the user which functions are provably @safe, and which are safe only on the assumption that the developer has done their job properly.

I don't think that's what Steven had in mind. In that world, @safe would be very, very limited, because it couldn't be allowed to call @trusted functions. That means @safe would only apply to trivial functions, and @trusted would assume the role that @safe has today. But you'd have to wrap every call from an @trusted to another @trusted function in an @system block. It wouldn't be practical.

The real purpose of @trusted in that example is to allow the @system block in the body, and to signal to reviewers and maintainers that the whole function is unsafe despite the mechanical checks that are done on most of the lines. To a user, @trusted functions would still be the same as @safe ones.

Unfortunately, adding the mechanical checks of @safe to @trusted would mean breaking all @trusted code that exists. So implementing that scheme seems unrealistic.

But as Steven says, it can be done when we use @trusted blocks instead of @system blocks and @safe instead of @trusted on the function. I.e.:

    @safe fun ()
    {
        // lines that the compiler accepts as @safe go here
        @trusted {
            // these lines are allowed to use @system code
        }
        // only @safe lines here again
    }

It weakens the meaning of @safe somewhat, but it's often treated that way already. There's clearly a need.
January 15
Am Wed, 15 Jan 2020 19:06:11 +0100 schrieb ag0aep6g:

> 
> The real purpose of @trusted in that example is to allow the @system block in the body, and to signal to reviewers and maintainers that the whole function is unsafe despite the mechanical checks that are done on most of the lines. To a user, @trusted functions would still be the same as @safe ones.
> 
> Unfortunately, adding the mechanical checks of @safe to @trusted would mean breaking all @trusted code that exists. So implementing that scheme seems unrealistic.
> 


I think it shouldn't be much of a problem, as there is a very nice transition path:

* Add @system block support
* Add -transition=systemBlocks which enforces @system blocks in trusted
functions
* Users gradually add @system blocks to their trusted functions, until
everything compiles with -transition=systemBlocks. If you did not add all
blocks yet, your code will still compile fine without -
transisition=systemBlocks
* -transition=systemBlocks becomes default

> But as Steven says, it can be done when we use @trusted blocks instead of @system blocks and @safe instead of @trusted on the function. I.e.:
> 
>      @safe fun ()
>      {
>          // lines that the compiler accepts as @safe go here @trusted {
>              // these lines are allowed to use @system code
>          }
>          // only @safe lines here again
>      }
> 
> It weakens the meaning of @safe somewhat, but it's often treated that way already. There's clearly a need.


I don't really like this. It makes @trusted functions completely useless legacy cruft. And there's no longer any way to annotate a function as 'this is 100% safe code', so then you'll have to check every safe function as thoroughly as trusted functions.


-- 
Johannes
January 15
On Wednesday, 15 January 2020 at 18:06:11 UTC, ag0aep6g wrote:
> I don't think that's what Steven had in mind. In that world, @safe would be very, very limited, because it couldn't be allowed to call @trusted functions.

Well, apologies to Steven if I've misinterpreted his proposal.  But what I had in mind was that @safe would be able to call @trusted just as it does now.

So, put that together with what I wrote above, and you have something that allows better validation of the internals of @trusted functions, and still gives the user clarity about which functions are safe in their own terms, and which are safe based on some programmer provided guarantees.
January 15
On 15.01.20 19:38, Johannes Pfau wrote:
> Am Wed, 15 Jan 2020 19:06:11 +0100 schrieb ag0aep6g:
[...]
> I think it shouldn't be much of a problem, as there is a very nice
> transition path:
> 
> * Add @system block support
> * Add -transition=systemBlocks which enforces @system blocks in trusted
> functions
> * Users gradually add @system blocks to their trusted functions, until
> everything compiles with -transition=systemBlocks. If you did not add all
> blocks yet, your code will still compile fine without -
> transisition=systemBlocks
> * -transition=systemBlocks becomes default

If that's deemed acceptable, I'm on board. But the alternative requires zero work from users (if we just keep @trusted functions around as legacy cruft).

>> But as Steven says, it can be done when we use @trusted blocks instead
>> of @system blocks and @safe instead of @trusted on the function. I.e.:
[...]
> I don't really like this. It makes @trusted functions completely useless
> legacy cruft. And there's no longer any way to annotate a function as
> 'this is 100% safe code', so then you'll have to check every safe
> function as thoroughly as trusted functions.

You already have to check for @trusted nested functions if you want to make sure an @safe function is really 100% safe. And those already routinely leak their unsafety into the surrounding @safe function. I don't see what would change in that regard.
January 15
On 15.01.20 19:41, Joseph Rushton Wakeling wrote:
> Well, apologies to Steven if I've misinterpreted his proposal. But what I had in mind was that @safe would be able to call @trusted just as it does now.
> 
> So, put that together with what I wrote above, and you have something that allows better validation of the internals of @trusted functions, and still gives the user clarity about which functions are safe in their own terms, and which are safe based on some programmer provided guarantees.

You're saying that an @safe function `f` is somehow more guaranteed to be safe than an @trusted function `g`, even though `f` may be calling `g`. I don't see how that makes sense.

----
R f(P params) @safe { return g(params); }
R g(P params) @trusted { /* ... whatever ... */ }
----

Any assumptions you have about `f` better also be true about `g`.
January 15
On Wednesday, 15 January 2020 at 14:30:02 UTC, Ogi wrote:
> Without @trusted the safety design will be much simpler to grasp. Currently the are three vague keywords, complex rules on which functions can call which and best practices on which attribute you should choose.

I also don't understand what's the point with @trusted. Should it only be used as a trampoline for safe code into the unknown or "some holds allowed" like something in between @system and @safe. I find this highly confusing. It's like x86 protection rings (ring 0 - 3) where rings 1 - 2 are seldom used.

If you think about it @trusted is not necessary as you say that @trusted "must be manually verified", aka unsafe.

I think you should be allowed to call unsafe code from safe code and it is up to the programmer to check the code that they call and make an assessment on stability. @trusted call can call unsafe code further down the line so @trusted isn't that useful.

I might have misunderstood the point of @trusted totally and you can try to make me understand what's the point of it. I didn't find the documentation very helpful.
January 15
On Wednesday, 15 January 2020 at 19:27:42 UTC, IGotD- wrote:
> On Wednesday, 15 January 2020 at 14:30:02 UTC, Ogi wrote:
>> Without @trusted the safety design will be much simpler to grasp. Currently the are three vague keywords, complex rules on which functions can call which and best practices on which attribute you should choose.
>
> I also don't understand what's the point with @trusted. Should it only be used as a trampoline for safe code into the unknown or "some holds allowed" like something in between @system and @safe. I find this highly confusing. It's like x86 protection rings (ring 0 - 3) where rings 1 - 2 are seldom used.
>
> If you think about it @trusted is not necessary as you say that @trusted "must be manually verified", aka unsafe.

No.  @trusted is about saying "This function should be safe to use, but that safety has been designed by the developer rather than being automatically verifiable by the compiler."

Contrast with @system which can be applied to functions that are inherently not guaranteed to be safe -- e.g. where the safety (or not) depends on what input the user provides.
January 15
On Wednesday, 15 January 2020 at 19:27:42 UTC, IGotD- wrote:
> I might have misunderstood the point of @trusted totally and you can try to make me understand what's the point of it. I didn't find the documentation very helpful.

"How to Write @trusted Code in D", on the D blog, is a good introduction:

https://dlang.org/blog/2016/09/28/how-to-write-trusted-code-in-d/
« First   ‹ Prev
1 2 3 4 5 6 7 8