Skip to content

Object lifetime defaults of GATs are not respected #115379

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
fmease opened this issue Aug 30, 2023 · 4 comments · May be fixed by #129543
Open

Object lifetime defaults of GATs are not respected #115379

fmease opened this issue Aug 30, 2023 · 4 comments · May be fixed by #129543
Assignees
Labels
A-associated-items Area: Associated items (types, constants & functions) A-dyn-trait Area: trait objects, vtable layout A-GATs Area: Generic associated types (GATs) A-lifetimes Area: Lifetimes / regions C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.

Comments

@fmease
Copy link
Member

fmease commented Aug 30, 2023

To infer the default trait object lifetime of a trait object type, we first look at the bounds on the relevant type parameter of the containing generic type. According to my interpretation of the Reference, RFC 599 and RFC 1156, any kind of generic type can be an elegible container type, not just ADTs. However, if the containing generic type is a GAT, the compiler doesn't properly take into consideration the bounds on its type parameters. The RFCs obviously predate GATs.

For example, I expected the following code to compile since dyn Inner in g's signature should be equivalent to dyn Inner + 'r given that 'r is the object lifetime default due to the bound T: 'a at least according to my reading of the Reference and according to #[rustc_object_lifetime_default] but it fails because the default is actually inferred to be 'static leading me to assume that rustc completely ignores the GAT param bounds.

trait Outer {
    type Ty<'a, T: ?Sized + 'a>;
}
impl Outer for () {
    type Ty<'a, T: ?Sized + 'a> = &'a T;
}
trait Inner {}

fn f<'r>(x: <() as Outer>::Ty<'r, dyn Inner + 'r>) { g(x) } //~ ERROR lifetime may not live long enough
fn g<'r>(_: <() as Outer>::Ty<'r, dyn Inner>) {}
error: lifetime may not live long enough
  --> src/lib.rs:10:56
   |
10 | fn f<'r>(x: <() as Outer>::Ty<'r, dyn Inner + 'r>) { g(x) }
   |      -- lifetime `'r` defined here                     ^ cast requires that `'r` must outlive `'static`

As attested by #[rustc_object_lifetime_default], we seem to (be able to) compute the object lifetime defaults for GATs (but we don't use them during resolution):

error: 'a
 --> src/lib.rs:5:17
  |
5 |     type Ty<'a, T: ?Sized + 'a>;
  |                 ^

Is this an oversight in the current implementation or does this work as intended?
Note that it would be a breaking change to fix this, I'm almost certain.

@rustbot label C-bug T-compiler A-lifetimes A-trait-objects A-associated-items F-generic_associated_types

@rustbot rustbot added needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. A-associated-items Area: Associated items (types, constants & functions) A-lifetimes Area: Lifetimes / regions A-dyn-trait Area: trait objects, vtable layout C-bug Category: This is a bug. F-generic_associated_types `#![feature(generic_associated_types)]` a.k.a. GATs T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Aug 30, 2023
@fmease fmease changed the title Object lifetime defaults of GAT parameters are not respected Object lifetime defaults of GATs are not respected Aug 31, 2023
@Noratrieb Noratrieb removed the needs-triage This issue may need triage. Remove it if it has been sufficiently triaged. label Sep 4, 2023
@fmease fmease added T-lang Relevant to the language team, which will review and decide on the PR/issue. I-lang-nominated Nominated for discussion during a lang team meeting. labels Sep 11, 2023
@fmease
Copy link
Member Author

fmease commented Sep 11, 2023

Nominating this for lang team discussion since I really don't know if this works as intended or not.

Feel free to remove the nomination if you don't think it's important enough for a team discussion.
I can nominate it for a types team discussion instead if you think that's more appropriate :)

@fmease
Copy link
Member Author

fmease commented Sep 11, 2023

Possibly relevant: #91302

@nikomatsakis
Copy link
Contributor

Hi @fmease !

We discussed this in our @rust-lang/lang triage meeting today. We felt the behavior is a bug and inconsistent. I'm nominating this issue for @rust-lang/types to discuss further and possibly fix.

@rustbot labels +I-types-nominated -I-lang-nominated

In particular, we felt that this example should behave the same as your motivating example:

trait Ref<'a, T: 'a + ?Sized> {
    type Ty;
}
trait Inner {}

fn f<'r, T>(x: <T as Ref<'r, dyn Inner + 'r>>::Ty) { g(x) }
fn g<'r, T>(x: <T as Ref<'r, dyn Inner>>::Ty) { g(x) } 

However, the above example gives an error, as does dyn Trait, but the GAT case seems to infer a bound of 'static. This seems like a bug. If any bound is inferred, in my opinion, it ought to be 'r.

We felt that it was ok to give a hard error since that is forwards compatible and consistent with how we currently behave for other parameters appearing in associated type references, but of course that is technically a breaking change, although the impact seems likely to be small.

@rust-lang/types, this kind of detail also feels like it's in your purview, how do you think we ought to proceed?

@rustbot rustbot added I-types-nominated Nominated for discussion during a types team meeting. and removed I-lang-nominated Nominated for discussion during a lang team meeting. labels Sep 19, 2023
@fmease fmease added T-types Relevant to the types team, which will review and decide on the PR/issue. and removed T-lang Relevant to the language team, which will review and decide on the PR/issue. labels Sep 19, 2023
@lcnr
Copy link
Contributor

lcnr commented Sep 25, 2023

discussed this in the @rust-lang/types meeting: zulip

our takeaways are as follows:

  • object lifetime defaults for projections are currently unspecified and something we should clean up
  • we should move towards handling projection arguments (and self type) by only looking at bounds on the trait declaration
  • write PR, run crater, add tests, start a types fcp changing this behavior

This is theoretically breaking as &'a <dyn Trait as Foo>::Assoc currently infers to &'a <dyn Trait + 'a as Foo>::Assoc but would change to 'static with the proposed change. Hopefully not too many - or well, no - crates depend on the current behavior

@lcnr lcnr removed the I-types-nominated Nominated for discussion during a types team meeting. label Sep 25, 2023
@fmease fmease self-assigned this Aug 25, 2024
bors added a commit to rust-lang-ci/rust that referenced this issue Aug 25, 2024
[crater] Properly deduce the object lifetime default in GAT paths

Fixes rust-lang#115379.

r? ghost
@fmease fmease added A-GATs Area: Generic associated types (GATs) and removed F-generic_associated_types `#![feature(generic_associated_types)]` a.k.a. GATs labels Sep 24, 2024
bors added a commit to rust-lang-ci/rust that referenced this issue Apr 20, 2025
[WIP] Properly deduce object lifetime defaults in GAT paths

FIXME: Proper explainer (several things fall out of this PR)

Fixes rust-lang#115379.

r? ghost
bors added a commit to rust-lang-ci/rust that referenced this issue Apr 21, 2025
[WIP] Properly deduce object lifetime defaults in projections & trait refs

FIXME: Proper explainer (several things fall out of this PR)

Fixes rust-lang#115379.

r? ghost
bors added a commit to rust-lang-ci/rust that referenced this issue May 7, 2025
Properly deduce object lifetime defaults in projections & trait refs

#### Object Lifetime Defaults (Primer, Refresher & Definitions)

You can read [this section](https://doc.rust-lang.org/reference/lifetime-elision.html#default-trait-object-lifetimes) in The Reference but it's not perfect IMO. Here's a small explainer by me that only mentions the parts relevant to this PR:

Basically, given `dyn Trait` (≠ `dyn Trait + '_`) we want to deduce its *object lifetime bound* from context (without relying on `rustc_infer`'s region inference as we might not be in a body[^1]). The "context" means the closest — what I call — *eligible generic container* `C<X0, …, Xn>` that wraps this trait object type. *(Eligible generic) container* is almost synonymous with type constructor but it also includes type aliases, traits & enum variants.

So if we have `C<…, dyn Trait, …>` (e.g., `&'r dyn Trait` or `Struct<'r, dyn Trait>`) or `C<…, N<…, dyn Trait, …>, …>` (e.g., `&'r (dyn Trait,)` or `Struct<'r, (dyn Trait,)>`) where `N` denotes a generic type that is **not** an eligible generic container, we use the explicit[^2] outlives-bounds on the corresp. type param of `C` to determine the object lifetime bound (the details[^3] aren't relevant here) (e.g., given `struct Struct<'a, T: 'a + ?Sized>(…);`, we elaborate `Struct<'r, dyn Trait>` to `Struct<'r, dyn Trait + 'r>`).

Lastly, I call object lifetime bounds used as the default for *constituent* trait object types of an eligible generic container `C` the *ambient object lifetime defaults* for / induced by `C` (these ambient defaults may be shadowed by inner containers).

---

#### Changes Made by This PR

1. Make associated type paths / projections *eligible generic containers*.
   * `<Y0 as TraitRef<X0, …, Xn>>::AssocTy<Y1, …, Ym>` now induces *ambient object lifetime defaults* for constituents Y0 to Ym (`TraitRef` is considered a separate container, see also list item **(2)**).
   * Similar case with type-relative ("shorthand") paths `Y0::AssocTy<Y1, …, Ym>`
   * Notably, for the self type Y0 of resolved projections we now look at the bounds on the `Self` type param of the relevant trait (e.g., given `trait Outer<'a>: 'a { type Proj; }` or `trait Outer<'a> where Self: 'a { type Proj; }` we elaborate `<dyn Inner as Outer<'r>>::Proj` to `<dyn Inner + 'r as Outer<'r>>::Proj`).
2. Fixes object lifetime defaults inside trait refs `TraitRef<X0, …, X1>` (this fell out from the previous changes). There used be completely broken due to a gnarly off-by-one error for not accounting for the implicit `Self` type param of traits which leads to cases like
   * `Outer<'r, dyn Inner>` (with `trait Outer<'a, T: 'a + ?Sized> {}`) getting rejected as "inderminate" (it tries to access a *lifetime* at index 1 instead 0) ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=0069c89b2313f0f447ff8b6f7de9adfa))
   * `Outer<'r, 's, dyn Inner>` (with `trait Outer<'a, 'b: T: 'a + ?Sized> {}`) elaborating `dyn Inner` to `dyn Inner + 's` instead of `dyn Inner + 'r`(!) ([playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=9c521165e0ac0d868a8087cd7ca861fe))

**These changes are theoretically breaking** because in certain cases they lead to different object lifetime bounds getting deduced compared to master which is obviously user observable. However, [the latest crater run](rust-lang#129543 (comment)) found 0 non-spurious regressions.

**Motivation**: Both object lifetime default RFCs never explicitly specify what constitutes an — what I call — *eligible generic container* but it only makes sense to include any type constructor or (generic) type alias that can bear outlives-bounds … like associated types. So it's only *consistent* to make this change.

Fixes rust-lang#115379.

r? ghost

[^1]: If we *are* in a body, we do however use to normal region inference as a fallback.
[^2]: Indeed, we don't consider implied bounds (inferred outlives-bounds).
[^3]: Like how we deal with 'ambiguities' or how we look at the bounds of inner TOT as a fallback.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-associated-items Area: Associated items (types, constants & functions) A-dyn-trait Area: trait objects, vtable layout A-GATs Area: Generic associated types (GATs) A-lifetimes Area: Lifetimes / regions C-bug Category: This is a bug. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-types Relevant to the types team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants