Weird bounds demanded for GAT

I'm facing the following problem:

#![feature(generic_associated_types)]

trait X<'a> {
    type Y<'b>;
    // when I add the following line,
    // it is required that 'b outlives 'a
    fn foo(&self) -> Self::Y<'static>;
    // why?
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error: missing required bound on `Y`
 --> src/lib.rs:4:5
  |
4 |     type Y<'b>;
  |     ^^^^^^^^^^-
  |               |
  |               help: add the required where clause: `where 'b: 'a`
  |
  = note: this bound is currently required to ensure that impls have maximum flexibility
  = note: we are soliciting feedback, see issue #87479 <https://github.com/rust-lang/rust/issues/87479> for more information

error: could not compile `playground` due to previous error

I have seen requirements such as Self: 'b (which make sense), but this is something else and doesn't seem to be related to the referenced issue #87479.

What is going on?

I think it is that issue.

    fn foo(&self) -> Self::Y<'static>;
//  <Self as X<'a>>::Y<'static>
//   P0        P1      P2
// Compiler can show 'static: 'a (P2: P1)
// Implies 'b: 'a in Y<'b>

But I also admit I don't really get it. Particularly since this "turns off" the lint:

    // in addition to `foo`
    fn bar(&self) -> Self::Y<'_>;

Maybe the lint is just too dumb? I'm getting the impression that the lint just gives up when there's more than one GAT-using method signature.

I think that the intuitively "correct" required bound should actually be 'b: 'static.

I suppose the example does follow the rules that @quinedot linked correctly, though. (And by the way, no, the lint doesn't give up I believe. It just didn't find any bounds that are required by all methods at the same time.) So maybe the rules themselves have room for improvement?


So the 'b: 'a bound is an implication of the only use case of the GAT behind the parameter being just the lifetime 'static, but the cited rules only consider relations between type arguments and lifetime arguments of the GAT and the trait, as well as Self; so from the 'b: 'static in the use-case, the compiler was able to deduce 'b: 'a. (The list would grow if you add more lifetime arguments to the trait.) Perhaps/possibly the rules could be improved by adding 'static to the list of things being considered. In any case, I think this case is confusing to the user, so it's a good case to provide as feedback in said issue.

I definitely think I want to have some methods using Self::Y<'b> where 'b is as short as a method call, i.e. it sometimes will be 'static but sometimes just very short (for temporary values that I pass to methods). It's difficult to show an example of where I need it, but I can try to assemble that (assuming you really think that 'b: 'static should be required just because method foo exists).

I'm still confused and not sure if a bound is required for some reasons I don't understand yet, but wanted to give the feedback that (I think) I want no bounds on 'b. I need 'b to be very short sometimes.


Basically I want this:

#![feature(generic_associated_types)]

trait X<'a> {
    type StaticY; // = Y<'static>
    type Y<'b>;
    fn foo(&self) -> Self::StaticY;
    fn bar<'b>(&self, arg: Self::Y<'b>);
}

(Playground)

Here I work around the problem by using another associated type in the static case. You can see that there are no bounds on 'b in case of method bar (which is what I want).

The above workaround compiles without errors.

I think we mean the same thing, and I just phrased it rather poorly. I don't really have a better idea though; perhaps my negative reaction was just because I haven't thought it through enough. It does imply an alternative general workaround is to add a dummy method to your trait with unique bounds.

Given @jbe's latest example though, bar doesn't imply 'b: 'a, but doesn't "cancel" the lint when Self::StaticY is replaced with Self::Y<'static> either. So it probably ignores methods with an empty set of heuristic bounds even if those methods involve the GAT? [1] Perhaps it shouldn't.


  1. For some definition of empty; 'a: 'a isn't empty I guess. ↩︎

Not quite, the rules are such that you'd only get the 'b: 'static bound if all methods just use Self::Y<'static>. Its not a existential quantified but a universal quantifier. Not

but instead "because all the methods involving Self::Y support the 'b: 'static bound".

Ah okay, I was wondering how these bounds are created. I should read into the rules.

So the following example should demonstrate my problem better then:

#![feature(generic_associated_types)]

trait X<'a> {
    type Y<'b>;
    //type StaticY; // = Y<'static>
    //fn foo(&self) -> Self::StaticY;
    fn foo(&self) -> Self::Y<'static>;
    fn bar<'b>(&self, arg: Self::Y<'b>);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error: missing required bound on `Y`
 --> src/lib.rs:4:5
  |
4 |     type Y<'b>;
  |     ^^^^^^^^^^-
  |               |
  |               help: add the required where clause: `where 'b: 'a`
  |
  = note: this bound is currently required to ensure that impls have maximum flexibility
  = note: we are soliciting feedback, see issue #87479 <https://github.com/rust-lang/rust/issues/87479> for more information

error: could not compile `playground` due to previous error

(Which is my workaround example with the workaround removed commented-out.)

There still might be another bug regarding demanding 'b: 'a instead of 'b: 'static in the original example, I guess?

Are there any examples where

  • Not having a bound in the 'static-only case is a problem that 'b: 'a solves?
  • [...] is a problem that 'b: 'static solves and 'b: 'a does not?

Hopefully it's not the case when these bounds become implied, but adding explicit bounds as suggested by the lint is a backwards compatibility hazard when they're not needed, as removing (or loosening) the bounds is a breaking change. If there's not a use case where the bounds are needed, they probably just shouldn't be there.

(Alternatively put, why have a 'static-only-using GAT instead of a non-G associated type, unless you're planning to expand in the future and want to do so backwards-compatibly?)

1 Like

After understanding the issue better, I can share the following (cleaned up) "real world" code now, where my problem occurred, and how I currently work around it:

pub trait Machine<'a> {
    type Datum<'b, 'c>
    where
        Self: 'b;
    type Function<'b>: for<'c> Function<Datum<'c> = Self::Datum<'b, 'c>>
    where
        Self: 'b;
}

pub trait Function {
    type Datum<'c>;
    /* … */
}

pub trait Module<'a, 'b>: Sized {
    type Machine: Machine<'a>; // TODO: replace with `type Datum<'c>;`, when compiler is fixed
    fn open(&self, name: &str) -> Result<Self, MachineError>;
    fn get(
        &self,
        name: &str,
    ) -> Result<<Self::Machine as Machine<'a>>::Datum<'b, 'static>, MachineError>;
    fn set<'c>(
        &self,
        name: &str,
        datum: <Self::Machine as Machine<'a>>::Datum<'b, 'c>,
    ) -> Result<(), MachineError>;
}

And this is how the traits are used (for example):

fn load_my_module<'a, 'b, M>(machine: &'b M) -> Result<(), MachineError>
where
    M: Machine<'a> + HasModules<'a> + Callback<'a>,
    for<'c> M::Datum<'b, 'c>: MaybeFloat,
{
    let mymod = machine.open("mymod")?;
    mymod.set("pi_approx", 3.14.into())?;
    mymod.set("double", machine.callback_1arg(|x| {
        Ok([(x.try_as_f64()? * 2.0).into()])
    })?)?;
    Ok(())
}

I don't have good examples yet that demonstrate the use of 'static vs 'c though. Basically a &'c str can be converted into a Machine<'a>::Datum<'b, 'c> where 'c can be very short (and most importantly be shorter than 'static, or 'a or 'b). But I haven't implemented that part yet.

FWIW,

#![feature(generic_associated_types)]

trait X<'a> : Z {
    fn foo (self: &'_ Self)
      -> Self::Y<'static>
    ;
    fn bar<'b> (self: &'_ Self, _: Self::Y<'b>)
      -> Self::Y<'static>
    ;
}

trait Z {
    type Y<'b>;
}

works (it's the canonical workaround of #87479, but feel free to share your use case directly there).

1 Like

Thanks a lot. I'll look into that, and I also plan to share my usecase as a feedback. (I'm currently trying to solve yet another GAT issue that I encountered (but not sure yet if it's a compiler bug or just me being confused). Might post more on that in this thread later, if I think it's a bug.)


Oh, I just realized that the GAT is moved to a different trait in the workaround you posted. I think I prefer my workaround (for my particular problem). But it's good to know about the other workaround too.

Here we go:

#![feature(generic_associated_types)]

trait Machine {
    type Datum<'a>;
}

trait MaybeStringMap
where
    //Self: Sized,
{
}

trait HasStringMap: Machine {
    fn new_string_map<'a, I>(&self, entries: I) -> ()
    where
        I: IntoIterator<Item = Self::Datum<'a>>,
        <Self as Machine>::Datum<'static>: MaybeStringMap,
    {
        let mut _x = entries.into_iter();
        //let _ = _x.next();
    }
}

(Playground)

This code compiles.

However, if I uncomment both commented-out lines, then I get an error:

-    //Self: Sized,
+    Self: Sized,
/* … */
-        //let _ = _x.next();
+        let _ = _x.next();

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
  --> src/lib.rs:20:20
   |
20 |         let _ = _x.next();
   |                    ^^^^ lifetime mismatch
   |
   = note: expected type `<<Self as Machine>::Datum<'a> as Sized>`
              found type `<<Self as Machine>::Datum<'static> as Sized>`
note: the lifetime `'a` as defined here...
  --> src/lib.rs:14:23
   |
14 |     fn new_string_map<'a, I>(&self, entries: I) -> ()
   |                       ^^
   = note: ...does not necessarily outlive the static lifetime

For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground` due to previous error

Note that if only one of those lines is uncommented and the other is commented, then the code compiles.

:woozy_face:

P.S.: Is this unrelated to the other problem? I'd guess so? But it's really difficult for me to overlook the topic. I assume this new problem is a compiler bug too. I don't understand how the line let _ = _x.next(); should cause a type mismatch.

Maybe the same as this one. There seems to be something specific about Sized bounds that I don't understand (and indeed, if you change your Sized bound to Send or Display, it works).

Simplified playground, maybe submit as a possible test case.

1 Like

As a new issue?

PR 92191 is the apparent fix; probably ask there if it fixes this one too (since 'static is explicit) and suggest it as an additional test case if so (there's already one in that PR for #89352).

Alright, will look into it very soon. Time to eat.

:yum:

Thanks a lot for your help, workarounds, links to issues, etc.!

1 Like

I opened a new issue #92798 and referenced the PR (and added a comment).

1 Like

I asked there.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.