Skip to content

Commit

Permalink
Add section on precise capturing use<..> syntax
Browse files Browse the repository at this point in the history
Thanks to CE for details on the compiler's use-case that informs an
example here.
  • Loading branch information
traviscross committed Oct 17, 2024
1 parent 307525a commit 8970b99
Showing 1 changed file with 77 additions and 0 deletions.
77 changes: 77 additions & 0 deletions posts/2024-10-17-Rust-1.82.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,83 @@ The Rust target `aarch64-apple-darwin` for macOS on 64-bit ARM (M1-family or lat

[The targets](https://doc.rust-lang.org/nightly/rustc/platform-support/apple-ios-macabi.html) are now tier 2, and can be downloaded with `rustup target add aarch64-apple-ios-macabi x86_64-apple-ios-macabi`, so now is an excellent time to update your CI pipeline to test that your code also runs in iOS-like environments.

### Precise capturing `use<..>` syntax

Rust now supports `use<..>` syntax within certain impl Trait bounds to control which generic lifetime parameters are captured.

Return-position impl Trait (RPIT) types in Rust *capture* certain generic parameters. Capturing a generic parameter allows that parameter to be used in the hidden type. That in turn affects borrow checking.

In Rust 2021 and earlier editions, lifetime parameters are not captured in opaque types on bare functions and on functions and methods of inherent impls unless those lifetime parameters are mentioned syntactically in the opaque type. E.g., this is an error:

```rust
//@ edition: 2021
fn f(x: &()) -> impl Sized { x }
```

```
error[E0700]: hidden type for `impl Sized` captures lifetime that does not appear in bounds
--> src/main.rs:1:30
|
1 | fn f(x: &()) -> impl Sized { x }
| --- ---------- ^
| | |
| | opaque type defined here
| hidden type `&()` captures the anonymous lifetime defined here
|
help: add a `use<...>` bound to explicitly capture `'_`
|
1 | fn f(x: &()) -> impl Sized + use<'_> { x }
| +++++++++
```

With the new `use<..>` syntax, we can fix this, as suggested in the error, by writing:

```rust
fn f(x: &()) -> impl Sized + use<'_> { x }
```

Previously, correctly fixing this class of error required defining a dummy trait, conventionally called `Captures`, and using it as follows:

```rust
trait Captures<T: ?Sized> {}
impl<T: ?Sized, U: ?Sized> Captures<T> for U {}

fn f(x: &()) -> impl Sized + Captures<&'_ ()> { x }
```

That was called ["the `Captures` trick"](https://github.com/rust-lang/rfcs/blob/master/text/3498-lifetime-capture-rules-2024.md#the-captures-trick), and it was a bit baroque and subtle. It's no longer needed.

There was a less correct way but more convenient way to fix this that was often used called ["the outlives trick"](https://github.com/rust-lang/rfcs/blob/master/text/3498-lifetime-capture-rules-2024.md#the-outlives-trick). The compiler even previously suggested doing this. That trick looked like this:

```rust
fn f(x: &()) -> impl Sized + '_ { x }
```

In this simple case, the trick is exactly equivalent to `+ use<'_>` for subtle reasons explained in [RFC 3498](https://github.com/rust-lang/rfcs/blob/master/text/3498-lifetime-capture-rules-2024.md). However, in real life cases, this overconstrains the bounds on the returned opaque type, leading to problems. For example, consider this code, which is inspired by a real case in the Rust compiler:

```rust
struct Ctx<'cx>(&'cx u8);

fn f<'cx, 'a>(
cx: Ctx<'cx>,
x: &'a u8,
) -> impl Iterator<Item = &'a u8> + 'cx {
core::iter::once_with(move || {
eprintln!("LOG: {}", cx.0);
x
})
//~^ ERROR lifetime may not live long enough
}
```

We can't remove the `+ 'cx`, since the lifetime is used in the hidden type and so must be captured. Neither can we add a bound of `'a: 'cx`, since these lifetimes are not actually related and it won't in general be true that `'a` outlives `'cx`. If we write `+ use<'cx, 'a>` instead, however, this will work and have the correct bounds.

There are some limitations to what we're stabilizing today. The `use<..>` syntax cannot currently appear within traits or within trait impls (but note that there, in-scope lifetime parameters are already captured by default), and it must list all in-scope generic type and const parameters. We hope to lift these restrictions over time.

Note that in Rust 2024, the examples above will "just work" without needing `use<..>` syntax (or any tricks). This is because in the new edition, opaque types will automatically capture all lifetime parameters in scope. This is a better default, and we've seen a lot of evidence about how this cleans up code. In Rust 2024, `use<..>` syntax will serve as an important way of opting-out of that default.

For more details about `use<..>` syntax, capturing, and how this applies to Rust 2024, see the ["RPIT lifetime capture rules"](https://doc.rust-lang.org/nightly/edition-guide/rust-2024/rpit-lifetime-capture.html) chapter of the edition guide. For details about the overall direction, see our recent blog post, ["Changes to `impl Trait` in Rust 2024"](https://blog.rust-lang.org/2024/09/05/impl-trait-capture-rules.html).

### Native syntax for creating a raw pointer

Unsafe code sometimes has to deal with pointers that may dangle, may be misaligned, or may not point to valid data. A common case where this comes up are `repr(packed)` structs. In such a case, it is important to avoid creating a reference, as that would cause undefined behavior. This means the usual `&` and `&mut` operators cannot be used, as those create a reference -- even if the reference is immediately cast to a raw pointer, it's too late to avoid the undefined behavior.
Expand Down

0 comments on commit 8970b99

Please sign in to comment.