Applying outer attributes to let statements is already stable, as are outer attributes on expression statements, expressions in comma separated lists, and block tail expressions, so long as the annotated expressions are not binops or ranges.
Macro statements don't take outer arguments per the reference grammar, but rustc still accepts it. Item statements obviously accept outer attributes.
The main struggle with attributes on expressions is actually specifically when they're ambiguous with a statement attribute. It's fairly simple to say that attributes on expressions bind tighter than binary operators, so #[a] b + #[c] d
is (#[a] {b}) + (#[c] {d})
, not #[a] {b + #[c] {d}}
, but this is less clear when you have a statement expression, e.g.
#[a]
b + d;
Barring that example, the choice of how tightly outer attributes bind is still an arbitrary choice, but it's a choice that's easier to make.
Marking an irrefutable let as likely/unlikely obviously doesn't make sense. Marking a refutable let, however, does make sense; you're hinting as to whether the refutable pattern match is successful.
Rust has a track record, for better or for worse, of refusing to stabilize the "simple, works, but potentially suboptimal" choice when a potentially better design is known to exist.
This was specifically called out as not working above:
so asserting that
is false for existing backends. The issue is that you end up with low-level code that looks something like
let foo: Option<&T>;
// likely(foo.is_some());
let _1: usize = transmute_copy(&foo);
let _2: bool = _1 != 0;
likely(_2);
// if let Some(bar) = foo
let _3: usize = transmute_copy(&foo);
let _4: bool = _3 != 0;
if _4 {
let bar: &T = transmute_copy(&foo);
// todo!()
}
and if the optimizer is going to "propagate the weight changes for the variable", then it needs to reverse the computation of the test boolean if it wants to derive information about the shared variable which is actually used to derive the later branch.
Your scheme might work for likely(b); if b { /* ... */ }
, since the same variable is used both in the hint and in the branch. But using a non-place expression in the hint is basically never going to work.
On the contrary, likely(foo.is_some());
is frighteningly high-level for the optimizer, since you're deriving a single boolean from a larger state and saying "hey this is likely" then throwing that boolean away and deriving a new boolean which could be related, but it might not be, the optimizer needs to derive that somehow, and you've derived the new test in a different manner. This is a nightmare to resolve and turn into something useful for the optimizer. Where on the other hand, taking specific branching operations (e.g. an if
or a fallible pattern match in let
/match
) and hinting the relative likelihood of the branches is much more directly meaningful to the optimizer.