Skip to content

Commit de7a342

Browse files
committed
Add clarifications on refinement, reparameterization
During the FCP, some questions came up related to how refinement and reparameterization in the impl are handled. This handling is implied by other text in the RFC and by the existing behavior of Rust, so let's go ahead and add clarifications to address these questions. The hardest of these questions relate to how things would behave if we were to allow `use<..>` in trait definitions to not capture the generic input parameters to the trait (including `Self`). It's unlikely this will be possible for the foreseeable future, and while we will not leave these as open questions, certainly much might be learned between now and the point at which that might become possible, so we'll make note of that. We'll also add a clarification to address a question that came up in the 2024-04-24 design meeting about what it means to capture a const generic parameter. (Thanks to aliemjay for raising many of these great questions.)
1 parent f06ae6a commit de7a342

File tree

1 file changed

+185
-4
lines changed

1 file changed

+185
-4
lines changed

text/3617-precise-capturing.md

Lines changed: 185 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ impl Trait for B {
377377
}
378378
```
379379

380-
If we only know that the value is of some type that implements the trait, then we must assume that the type returned by `foo` *might* have used the lifetime:
380+
If we only know that the value is of some type that implements the trait, then we must assume that the type returned by `foo` *might* use the lifetime:
381381

382382
```rust
383383
fn test_trait<T: Trait + 'static>(x: T) -> impl Sized + 'static {
@@ -386,7 +386,7 @@ fn test_trait<T: Trait + 'static>(x: T) -> impl Sized + 'static {
386386
}
387387
```
388388

389-
However, if we know we have a value of type `B`, we can *rely* on the fact that the lifetime was not used:
389+
However, if we know we have a value of type `B`, we can *rely* on the fact that the lifetime is not used:
390390

391391
```rust
392392
fn test_b(x: B) -> impl Sized + 'static {
@@ -415,11 +415,15 @@ impl Trait for () {
415415
}
416416
```
417417

418-
Similarly, for consistency, we'll lint against RPITIT cases where less is captured by RPIT in the impl as compared with the trait definition when using `use<..>`. E.g.:
418+
Similarly, for consistency, we'll lint against RPITIT cases where less is captured by RPIT in the impl as compared with the trait definition when using `use<..>`.
419+
420+
### Examples of refinement
421+
422+
In keeping with the rule above, we consider it refining if we don't capture in the impl all of the generic parameters from the function signature that are captured in the trait definition:
419423

420424
```rust
421425
trait Trait {
422-
fn foo(&self) -> impl Sized;
426+
fn foo(&self) -> impl Sized; // Or: `impl use<'_, Self> Sized`
423427
}
424428

425429
impl Trait for () {
@@ -434,6 +438,183 @@ impl Trait for () {
434438
}
435439
```
436440

441+
Similarly, if we don't capture, in the impl, any generic parameter applied as an argument to the trait in the impl header when the corresponding generic parameter is captured in the trait definition, that is refining. E.g.:
442+
443+
```rust
444+
trait Trait<'x> {
445+
fn f() -> impl Sized; // Or: `impl use<'x, Self> Sized`
446+
}
447+
448+
impl<'a> Trait<'a> for () {
449+
fn f() -> impl use<> Sized {}
450+
//~^ WARN impl trait in impl method signature does not match
451+
//~| trait method signature
452+
//~| NOTE add `#[allow(refining_impl_trait)]` if it is intended
453+
//~| for this to be part of the public API of this crate
454+
//~| NOTE we are soliciting feedback, see issue #121718
455+
//~| <https://github.com/rust-lang/rust/issues/121718>
456+
//~| for more information
457+
}
458+
```
459+
460+
This remains true even if the trait impl is *reparameterized*. In that case, it is refining unless *all* generic parameters applied in the impl header as generic arguments for the corresponding trait parameter are captured in the impl when that parameter is captured in the trait definition, e.g.:
461+
462+
```rust
463+
trait Trait<T> {
464+
fn f() -> impl Sized; // Or: `impl use<T, Self> Sized`
465+
}
466+
467+
impl<'a, 'b> Trait<(&'a (), &'b ())> for () {
468+
fn f() -> impl use<'b> Sized {}
469+
//~^ WARN impl trait in impl method signature does not match
470+
//~| trait method signature
471+
//~| NOTE add `#[allow(refining_impl_trait)]` if it is intended
472+
//~| for this to be part of the public API of this crate
473+
//~| NOTE we are soliciting feedback, see issue #121718
474+
//~| <https://github.com/rust-lang/rust/issues/121718>
475+
//~| for more information
476+
}
477+
```
478+
479+
Similarly, it's refining if `Self` is captured in the trait definition and, in the impl, we don't capture all of the generic parameters that are applied in the impl header as generic arguments to the `Self` type, e.g.:
480+
481+
```rust
482+
trait Trait {
483+
fn f() -> impl Sized; // Or: `impl use<Self> Sized`
484+
}
485+
486+
struct S<T>(T);
487+
impl<'a, 'b> Trait for S<(&'a (), &'b ())> {
488+
fn f() -> impl use<'b> Sized {}
489+
//~^ WARN impl trait in impl method signature does not match
490+
//~| trait method signature
491+
//~| NOTE add `#[allow(refining_impl_trait)]` if it is intended
492+
//~| for this to be part of the public API of this crate
493+
//~| NOTE we are soliciting feedback, see issue #121718
494+
//~| <https://github.com/rust-lang/rust/issues/121718>
495+
//~| for more information
496+
}
497+
```
498+
499+
## Lifetime equality
500+
501+
While the capturing of generic parameters is generally syntactic, this is currently allowed in Rust 2021:
502+
503+
```rust
504+
//@ edition: 2021
505+
fn foo<'a: 'b, 'b: 'a>() -> impl Sized + 'b {
506+
core::marker::PhantomData::<&'a ()>
507+
}
508+
```
509+
510+
Rust 2021 does not adhere to the Lifetime Capture Rules 2024 for bare RPITs such as this. Correspondingly, lifetimes are only captured when they appear in the bounds. Here, `'b` but not `'a` appears in the bounds, yet we're still able to capture `'a` due to the fact that it must be equal to `'b`.
511+
512+
To preserve consistency with this, the following is also valid:
513+
514+
515+
```rust
516+
fn foo<'a: 'b, 'b: 'a>() -> impl use<'b> Sized {
517+
core::marker::PhantomData::<&'a ()>
518+
}
519+
```
520+
521+
A more difficult case is where, in the trait definition, only a subset of the generic parameters on the trait are captured, and in the impl we capture a lifetime *not* applied syntactically as an argument for one of those captured parameters but which is equal to a lifetime that is applied as an argument for one of the captured parameters, e.g.:
522+
523+
```rust
524+
trait Trait<'x, 'y> {
525+
fn f() -> impl use<'y, Self> Sized;
526+
}
527+
528+
impl<'a: 'b, 'b: 'a> Trait<'a, 'b> for () {
529+
fn f() -> impl use<'b> Sized {
530+
core::marker::PhantomData::<&'a ()>
531+
}
532+
}
533+
```
534+
535+
For the purposes of this RFC, in the interest of consistency with the above cases, we're going to say that this is valid. However, as mentioned elsewhere, partial capturing of generics that are input parameters to the trait (including `Self`) is unlikely to be part of initial rounds of stabilization, and it's possible that implementation experience may lead us to a different answer for this case.
536+
537+
## Reparameterization
538+
539+
In Rust, trait impls may be parameterized over a different set of generics than the trait itself. E.g.:
540+
541+
```rust
542+
trait Trait<X, Y> {
543+
fn f() -> impl use<X, Y, Self> Sized;
544+
}
545+
546+
impl<'a, B, const C: usize> Trait<(), (&'a (), B, [(); C])> for () {
547+
fn f() -> impl use<'a, B, C> Sized {
548+
core::marker::PhantomData::<(&'a (), B, [(); C])>
549+
}
550+
}
551+
```
552+
553+
In these cases, what we look at is how these generics are applied as arguments to the trait in the impl header. In this example, all of `'a`, `B`, and `C` are applied in place of the `Y` input parameter to the trait. Since `Y` is captured in the trait definition, we're correspondingly allowed to capture `'a`, `B`, and `C` in the impl.
554+
555+
## The `Self` type
556+
557+
In trait definitions (but not elsewhere), `use<..>` may capture `Self`. Doing so means that in the impl, the opaque type may capture any generic parameters that are applied as generic arguments to the `Self` type. E.g.:
558+
559+
```rust
560+
trait Trait {
561+
fn f() -> impl use<Self> Sized;
562+
}
563+
564+
struct S<T>(T);
565+
impl<'a, B, const C: usize> Trait for S<(&'a (), B, [(); C])> {
566+
fn f() -> impl use<'a, B, C> Sized {
567+
core::marker::PhantomData::<(&'a (), B, [(); C])>
568+
}
569+
}
570+
```
571+
572+
## Handling of projection types
573+
574+
If we apply, in a trait impl header, a projection type to a trait in place of a parameter that is captured in the trait definition, that does not allow us to capture in the impl the generic parameter from which the type is projected. E.g.:
575+
576+
```rust
577+
trait Trait<X, Y> {
578+
fn f() -> impl use<Y, Self> Sized;
579+
}
580+
581+
impl<A: Iterator> Trait<A, A::Item> for () {
582+
fn f() -> impl use<A> Sized {}
583+
//~^ ERROR cannot capture `A`
584+
}
585+
```
586+
587+
The reason this is an error is related to the fact that, in Rust, a generic parameter used as an associated type does not constrain that generic parameter in the impl. E.g.:
588+
589+
```rust
590+
trait Trait {
591+
type Ty;
592+
}
593+
594+
impl<A> Trait for () {
595+
//~^ ERROR the type parameter `A` is not constrained
596+
type Ty = A;
597+
}
598+
```
599+
600+
## Meaning of capturing a const generic parameter
601+
602+
As with other generic parameters, a const generic parameter must be captured in the opaque type for it to be used in the hidden *type*. E.g., we must capture `C` here:
603+
604+
```rust
605+
fn f<const C: usize>() -> impl use<C> Sized {
606+
[(); C]
607+
}
608+
```
609+
610+
However, note that we do not need to capture `C` just to use it as a *value*, e.g.:
611+
612+
```rust
613+
fn f<const C: usize>() -> impl use<> Sized {
614+
C + 1
615+
}
616+
```
617+
437618
## Argument position impl Trait
438619

439620
Note that for a generic type parameter to be captured with `use<..>` it must have a name. Anonymous generic type parameters introduced with argument position `impl Trait` (APIT) syntax don't have names, and so cannot be captured with `use<..>`. E.g.:

0 commit comments

Comments
 (0)