Skip to content

Commit

Permalink
Revise after splitting rust-lang#8 for match arm attributes (now land…
Browse files Browse the repository at this point in the history
…ed).
  • Loading branch information
huonw committed May 17, 2014
1 parent fed1f9f commit 7520d05
Showing 1 changed file with 59 additions and 87 deletions.
146 changes: 59 additions & 87 deletions active/0000-more-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@

# Summary

Allow attributes on more places inside functions, such as match arms
and statements.
Allow attributes on more places inside functions, such as statements,
blocks and (possibly) expressions.

# Motivation

One sometimes wishes to annotate things inside functions with, for
example, lint `#[allow]`s, conditional compilation `#[cfg]`s, branch
weight hints and even extra semantic (or otherwise) annotations for
external tools.
example, lint `#[allow]`s, conditional compilation `#[cfg]`s, and even
extra semantic (or otherwise) annotations for external tools.

For the lints, one can currently only activate lints at the level of
the function which is possibly larger than one needs, and so may allow
Expand All @@ -24,54 +23,9 @@ let L = List::new(); // lowercase looks like one or capital i
```

For the conditional compilation, the work-around is duplicating the
whole containing function with a `#[cfg]`. A case study is
[sfackler's bindings to OpenSSL](https:/sfackler/rust-openssl),
where many distributions remove SSLv2 support, and so that portion of
Rust bindings needs to be conditionally disabled. The obvious way to
support the various different SSL versions is an enum

```rust
pub enum SslMethod {
#[cfg(sslv2)]
/// Only support the SSLv2 protocol
Sslv2,
/// Only support the SSLv3 protocol
Sslv3,
/// Only support the TLSv1 protocol
Tlsv1,
/// Support the SSLv2, SSLv3 and TLSv1 protocols
Sslv23,
}
```

However, all `match`s can only mention `Sslv2` when the `cfg` is
active, i.e. the following is invalid:

```rust
fn name(method: SslMethod) -> &'static str {
match method {
Sslv2 => "SSLv2",
Sslv3 => "SSLv3",
_ => "..."
}
}
```

A valid method would be to have two definitions: `#[cfg(sslv2)] fn
name(...)` and `#[cfg(not(sslv2)] fn name(...)`. The former has the
`Sslv2` arm, the latter does not. Clearly, this explodes exponentially
for each additional `cfg`'d variant in an enum.

Branch weights would allow the careful micro-optimiser to inform the
compiler that, for example, a certain match arm is rarely taken:

```rust
match foo {
Common => {}
#[cold]
Rare => {}
}
```
whole containing function with a `#[cfg]`, or breaking the conditional
code into a its own function. This does mean that any variables need
to be explicitly passed as arguments.

The sort of things one could do with other arbitrary annotations are

Expand All @@ -85,59 +39,77 @@ and then have an external tool that checks that that `unsafe` block's
only unsafe actions are FFI, or a tool that lists blocks that have
been changed since the last audit or haven't been audited ever.

The minimum useful functionality would be supporting attributes on
blocks and `let` statements, since these are flexible enough to allow
for relatively precise attribute handling.

# Detailed design

Normal attribute syntax:
Normal attribute syntax on `let` statements and blocks.

```rust
fn foo() {
#[attr]
#[attr1]
let x = 1;

#[attr]
foo();
#[attr2]
{
// code
}

#[attr]
match x {
#[attr]
Thing => {}
#[attr3]
unsafe {
// code
}
}
```

## Extension to arbitrary expressions

It would also be theoretically possible to extend this to support
arbitrary expressions (rather than just blocks, which are themselves
expressions). This would allow writing

```rust
fn foo() {
#[attr4] foo();

#[attr]
if foo {
} else {
#[attr5] if cond {
bar()
} else #[attr6] {
baz()
}

let x = #[attr7] 1;

qux(3 + #[attr8] 2);

foo(x, #[attr9] y, z);
}
```

# Alternatives
These last examples indicate a possible difficulty: what happens with
say `1 + #[cfg(foo)] 2`? This should be an error, i.e. `#[cfg]` is
only allowed on the "exterior" of expressions, that is, legal in all
examples above except `#[attr7]` and `#[attr8]`. `#[attr9]` is
questionable: if it were a `cfg`, then it could reasonably be
interpreted as meaning `foo(x, z)` or `foo(x, y, z)` conditional on
the `cfg`.

There aren't really any general alternatives; one could probably hack
around the conditional-enum-variants & matches with some macros and
helper functions to share as much code as possible; but in general
this won't work.
Allowing attributes there would also require considering
precedence. There are two sensible options, `#[...]` binds tighter
than everything else, i.e. `#[attr] 1 + 2` is `(#[attr] 1) + 2`, or it
is weaker, so `#[attr] 1 + 2` is `#[attr] (1 + 2)`.

The other instances could be approximated with macros and helper
functions, but to an even lesser degree (e.g. how would one annotate a
# Alternatives

These instances could possibly be approximated with macros and helper
functions, but to a low degree degree (e.g. how would one annotate a
general `unsafe` block).

# Unresolved questions

- Should one be able to annotate the `else` branch(es) of an `if`? e.g.

```rust
if foo {
} #[attr] else if bar {
} #[attr] else {
}
```

or maybe
- Are the complications of allowing attributes on arbitrary
expressions worth the benefits?

```rust
if foo {
} else #[attr] if bar {
} else #[attr] {
}
```
- Which precedence should attributes have on arbitrary expressions?

0 comments on commit 7520d05

Please sign in to comment.