Skip to content

opaque type definition: strict lifetime equality vs equal-by-inference #113971

Closed

Description

@aliemjay

The following code compiles but I don't think it should:

#![feature(type_alias_impl_trait)]
trait Captures<'a> {}
impl<T> Captures<'_> for T {}

fn ensure_outlives<'a, X: 'a>(_: X) {}
fn relate<X>(_: X, _: X) {}

type Opaque<'a> = impl Copy + Captures<'a>;
fn test<'x>(_: Opaque<'x>) {
    let opaque = None::<Opaque<'_>>; // let's call this lifetime '?1
    let hidden = None::<u8>;
    ensure_outlives::<'x>(opaque); // outlives constraint: '?1: 'x
    relate(opaque, hidden); // defining use: Opaque<'?1> := u8
}

Here we have a defining use of an opaque type Opaque<'_> == u8 with a questionable legality. Let's call the inferred lifetime '?1.

At each defining use of an opaque type, we require its lifetime arguments (['?1]) to be equal to one of the lifetime parameters in scope (in this case there is only 'x). This defining use passes this check because ensure_outlives registers a region constraint '?1: 'x, and, because we infer the least possible value for lifetime variables, we end up inferring '?1 == 'x.

The current situation is fine in regard to borrowck soundness (which is the reason we have such check in the first place) but it requires a subtle reasoning of lifetimes and makes the code more fragile (more on that later). For these reason, I believe we should enforce a stricter form of equality that requires both constraints ['x: '?1, '?1: 'x] in order to consider the lifetimes equal. It would be backward-compatible to change that later.

To demonstrate how fragile the current behavior can be, here is a couple of trivial changes that break the original example.

  • '?1: 'x constraint is no longer generated at ensure_equal:
-type Opaque<'a> = impl Copy + Captures<'a>;
+type Opaque<'a> = impl Copy + Captures<'a> + 'static;
  • a fundamental limitation of member constraints is exposed. I believe it is hard to fix.
     let opaque = None::<Opaque<'_>>;
-    let hidden = None::<u8>;
+    let hidden = None::<&'x u8>;

cc @rust-lang/nitiative-impl-trait

Activity

  1. added
    C-enhancementCategory: An issue proposing an enhancement or a PR with one.
    A-impl-traitArea: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch.
    T-typesRelevant to the types team, which will review and decide on the PR/issue.
    on Jul 23, 2023
  2. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Jul 23, 2023
  3. removed
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Jul 23, 2023
  4. added 3 commits that reference this issue on Oct 19, 2023
    c679690
    e287a7d
    66abdf7
  5. added a commit that references this issue on Mar 28, 2024
    551abd6
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-impl-traitArea: `impl Trait`. Universally / existentially quantified anonymous types with static dispatch.C-enhancementCategory: An issue proposing an enhancement or a PR with one.F-type_alias_impl_trait`#[feature(type_alias_impl_trait)]`T-typesRelevant to the types team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions