Local unsafe constructs

Hi, all. I'm considering a kind of unsafe construct that is lexically limited. I'm not sure whether this is suitable as a Pre-RFC or RFC. Any feedback is appreciated.

Consider the following code:

if unsafe { foo.check_condition() } {
  unsafe { foo.work() }
}

This is unpleasant due to repeated unsafes. One may want to rewrite it as:

unsafe {
    if foo.check_condition() {
        foo.work()
    }
}

This introduces another level of indentation.

unsafe { if foo.check_condition() {
    foo.work()
}}

Better, but the braces of unsafe still look redundant and ugly.

I think it would be nice if one is able to write:

unsafe if foo.check_condition() {
    foo.work()
}

The semantics are: both the condition expression and the body are marked unsafe, as if it was written

if unsafe { expr } { unsafe { body } }

Additionally, this intuition can apply to other constructs, including while and loop.

There are two ways to implement this:

  1. Extend the grammar to handle unsafe if, unsafe while, unsafe loop, etc.
  2. Allow unsafe expr.

The first one looks easy to implement, as it involves only grammar changes. (I'm not familiar with rustc, so correct me if I'm wrong!) It's also forward-compatible with the second approach if we were to adopt that. However, I will not fight for the second approach here: it needs detailed design.

I'm not sure if this was already proposed before. (There is a related but different proposal on Rust Internals.) IMHO, this is a nice and small improvement of the syntax, and I'd like to see it eventually become part of Rust. If this is OK to be an RFC or a Pre-RFC, I will prepare one. :slight_smile:

Personally I don't think the fact that it introduces another level of indentation is that bad. For the similar reason why if and for must take a block with brackets it helps to show the extent of what is being marked as "trust me".

Furthermore, who this also extend to the else or else if? Because then we are disussing a single keyword being used to mark multiple disjoint blocks.

12 Likes

Thanks for your feedback!

I agree with you. This is more an aesthetic issue than a technical issue.

To me, putting unsafe at the beginning of a line is obvious enough. Currently, syntax like unsafe if is rejected completely by the compiler, and there's no strong reason why we should reject it.

Good point! I myself will go for the extending approach, because it aligns better with the intuition of unsafe expr, and if we don't, there is no obvious way to mark else as unsafe. The if part of else if too inherits unsafe from the outer unsafe if.

1 Like

I would agree with this entirely: the extra indentation is a feature, and I don't think we should make unsafe blocks less visible or their extent less obvious.

7 Likes

This is the rule I'd deduced as well for the difference between things that need {} and those that would be fine without them -- if a construct affects everything in the block, then it has braces, whereas if it only affects the final value then it doesn't. (See how it's not return { 4 }, for example, because return only cares about the value produced, and has no impact on the subexpressions.)

There's an argument to be made that while such a change looks innocent enough, the goto fail Apple bug could have been prevented by adding braces in the proper places. Note in particular rule 1.3a of Barr's embedded C coding standard book:

Braces shall always surround the blocks of code
(a.k.a., compound statements), following if, else,
switch, while, do, and for statements; single
statements and empty statements following these
keywords shall also always be surrounded by
braces.

So at the very least the question should be asked and answered: could doing this cause similar issues? If it could, then that's a serious reason to not do this, or the same idea applied to different constructs.

Not that I am unsympathetic to the idea in abstract (in particular I'd like to be able to write loop match ... { ... } rather than loop { match ... { ... } }). But if it leads to easy to miss logic errors that can cause the likes of gotofail, then perhaps the downsides outweigh the upsides.

1 Like

This is a valid concern, but I doubt it would cause any harm in this case.

  1. unsafe does not alter control flow. It only marks some unsafe functions usable in its scope. Therefore, it's impossible to introduce any control-flow-related bugs, unlike a more general construct (loop match etc.)
  2. Currently and for the foreseeable future, Rust won't have "unsafe-polymorphism" (a temporary made-up word): two functions do completely different things and their signatures are the same except for the unsafe annotation. The semantics of the code are deterministic and won't cause any surprise.

While it's true that someone innocent may be tricked into thinking that an unsafe function is not annotated that way, but I argue that a blind unsafe { ... } wrapping a large piece of code (common practice) is actually the opposite of our intent: it blurs the extent of unsafe worse than unsafe if and other similar constructs. Having a lexically limited unsafe construct may actually improve the status quo. What do y'all think?

1 Like

And even if one disagrees with the point that indentation helps readability, the extremely minor convenience of not having to write braces doesn't in itself justify extending the syntax of the core language at all.

4 Likes

unsafe is meant to be noisy and burdensome, so in a way the inconvenience is a feature, not a bug :slight_smile:

3 Likes