Referencing to a value with lifetime to pass it to a FnMut

Hey, I want to implement a trait function that:

  • takes a type by value that contains a mutable reference
  • takes a FnMut that take the reference of the type to actually hinder mutation

The trait function should still be generic enough to allow me to pass it as a mutable reference in another impl than what I show here. So having FnMut(&'b Foo<'a>) in the trait definition is not an option.

I opted into GATs (I am optimistic the feature stabilizes before my project is done) as well. This is the code:

#![feature(generic_associated_types)]

struct Foo<'a>{
    foo: &'a mut usize
}
trait Bar<'a> {
  type FooRef<'b> where 'a: 'b;
  fn uwu(foo: Foo<'a>, f: impl for<'b> FnMut(Self::FooRef<'b>));
}
impl<'a> Bar<'a> for (){
  type FooRef<'b> = &'b Foo<'a> where 'a: 'b;
  fn uwu(foo: Foo<'a>, f: impl for<'b> FnMut(Self::FooRef<'b>)){
      f(&foo);
  }
}

This is the error: ( ^^^ marks uwu)

error[E0478]: lifetime bound not satisfied
  --> src/lib.rs:12:6
   |
12 |   fn uwu(foo: Foo<'a>, f: impl for<'b> FnMut(Self::FooRef<'b>)){
   |      ^^^
   |

I can't attach a where clause to the impl for<'b> sadly as I think that would fit here.

I think this has less to do with GATs and more to do with type aliases being unable to carry forward HRTB lifetimes. This, e.g., compiles, because now the HRTBs work as expected:

trait Bar<'a> {
  fn uwu(foo: Foo<'a>, f: impl for<'b> FnMut(&'b Foo<'a>));
}
impl<'a> Bar<'a> for (){
  fn uwu(foo: Foo<'a>, mut f: impl for<'b> FnMut(&'b Foo<'a>)){
      f(&foo);
  }
}

The reason I say it's unrelated to GATs is because you'll see the same kind of behavior if you try to make use of regular type aliases in the same way, i.e.

type Foo<'a, T> = &'a T;

// This'll give you lifetime trouble
fn bar<T, F: for<'a> Fn(Foo<'a, T>)>(f: F) {}

// This won't give you lifetime trouble
fn baz<T, F: for<'a> Fn(&'a T)>(f: F) {}

I see, but this essentially means I can't do what I wanted as I need to explicitly name the kind of reference (mutable or immutable) of the FnMut arguments?

I don't know if it's rude to @ people here, but I feel like if anyone can shine a better light on whether something like this is achievable, it's @Yandros.

2 Likes

if it's rude

I would hope it generally isn't, but at the very least I can say that for me it isn't: people should feel free to ping me :slightly_smiling_face:


Regarding the question at hand, at first glance it looks like a compiler bug. Indeed, if we use

stable-Rust polyfill of generic_associated_types, which thus happens to be more stable (for the cases it supports), we end up with a snippet which does compile:

  • Rust explorer demo

    • (I had to replace 'a : 'b with &'a () : 'b since nougat does not currently support the former bounds in GAT position)

If I had to guess where the original issue stems from, I'd say that it's that 'a : 'b bound which is not correctly propagated to the impl for<'b> FnMut… signature: given the bound, we should be dealing with a for<'b where 'a : 'b> kind of quantification, but it may not be the case?

Be it as it may, if we replace Self::FooRef<'b> in the impl with the &'b Foo<'a> actual type, we do get an ICE, so … :grimacing: :sweat_smile:

error: internal compiler error: failed region resolution while normalizing ParamEnv { caller_bounds: [Binder(ProjectionPredicate(ProjectionTy { substs: [impl for<'b> FnMut(&'b Foo<'a>), (<() as Bar<'a>>::FooRef<'b>,)], item_def_id: DefId(2:3527 ~ core[d628]::ops::function::FnOnce::Output) }, Ty(())), [Region(BrNamed(DefId(0:12 ~ playground[e047]::Bar::uwu::{opaque#0}::'b), 'b))]), Binder(TraitPredicate(<impl for<'b> FnMut(&'b Foo<'a>) as std::ops::FnMut<(<() as Bar<'a>>::FooRef<'b>,)>>, polarity:Positive), [Region(BrNamed(DefId(0:12 ~ playground[e047]::Bar::uwu::{opaque#0}::'b), 'b))]), Binder(TraitPredicate(<impl for<'b> FnMut(&'b Foo<'a>) as std::ops::FnOnce<(<() as Bar<'a>>::FooRef<'b>,)>>, polarity:Positive), [Region(BrNamed(DefId(0:12 ~ playground[e047]::Bar::uwu::{opaque#0}::'b), 'b))]), Binder(TraitPredicate(<impl for<'b> FnMut(&'b Foo<'a>) as std::marker::Sized>, polarity:Positive), [])], reveal: UserFacing, constness: NotConst }: [ConcreteFailure(RelateRegionParamBound(src/lib.rs:26:5: 29:6 (#0)), RePlaceholder(Placeholder { universe: U1, name: BrNamed(DefId(0:12 ~ playground[e047]::Bar::uwu::{opaque#0}::'b), 'b) }), ReEarlyBound(0, 'a)), ConcreteFailure(RelateRegionParamBound(src/lib.rs:26:5: 29:6 (#0)), RePlaceholder(Placeholder { universe: U2, name: BrNamed(DefId(0:12 ~ playground[e047]::Bar::uwu::{opaque#0}::'b), 'b) }), ReEarlyBound(0, 'a)), ConcreteFailure(RelateRegionParamBound(src/lib.rs:26:5: 29:6 (#0)), RePlaceholder(Placeholder { universe: U3, name: BrNamed(DefId(0:12 ~ playground[e047]::Bar::uwu::{opaque#0}::'b), 'b) }), ReEarlyBound(0, 'a))]
  --> src/lib.rs:26:5
   |
26 | /     fn uwu (
27 | |         foo: Foo<'a>,
28 | |         mut f: impl for<'b> FnMut(&'b Foo<'a>),
29 | |     )
   | |_____^
   |
   = note: delayed at compiler/rustc_trait_selection/src/traits/mod.rs:246:22

thread 'rustc' panicked at 'Box<dyn Any>', compiler/rustc_errors/src/lib.rs:1426:13

note: the compiler unexpectedly panicked. this is a bug.

note: we would appreciate a bug report: https://github.com/rust-lang/rust/issues/new?labels=C-bug%2C+I-ICE%2C+T-compiler&template=ice.md

note: rustc 1.65.0-nightly (86c6ebee8 2022-08-16) running on x86_64-unknown-linux-gnu

EDIT

It turns out that replaceing 'a : 'b with &'a () : 'b and also replacing Self::FooRef<'b> with its actual &'b Foo<'a> meaning does make the snippet compile :person_shrugging:

2 Likes

Thanks for the interesting input! Just inserting the actually picked type in the fn implementation is a nice workaround... without the ICE. :joy:

Should I report that compiler bug or is that tracked already? Is that ICE related to GAT now?

Feel free to report it: whilst it's very likely it has been reported already (there is already a nice list of ICEs and bugs caused by GATs), these things are hard to properly locate / indentify (at least for me), and since there is a small chance this is a new (gat-related) ICE, not reporting it could mean thiis specific issue goes unnoticed

1 Like