-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Inferred struct lifetime bounds result in rejection of valid lifetimes #91942
Comments
@rustbot label +A-lifetimes |
Looks like this is due to the invariance of Variance is a somewhat advanced and confusing topic in Rust. I recommend you give this page a read: https://doc.rust-lang.org/nomicon/subtyping.html#variance. In short, though, mutable references have stricter effects on the pointee type's associated lifetime(s) than immutable references do. I tested this hypothesis by changing all of the Knowing this, my next thought was that you need to put an explicit lifetime in place of at least one of those fn debug(
&self,
s: &mut State<'db, '_>,
callable: &mut impl Fn(&mut State<'db, '_>), // <-----
) As written, I think that line really translates to: callable: &'1 mut impl Fn(&mut State<'db, '1>) Because of the invariance of That is my understanding, at least. I don't claim to be an expert on this. To fix it, you need to put an explicit lifetime in place of fn debug<'a>(
&self,
s: &mut State<'db, '_>,
callable: &mut impl Fn(&mut State<'db, 'a>),
) As far as I know, this is expected behavior and not a bug. I do think, though, that there is a lot of improvement that we can make to these diagnostics when invariance is at play. It is absolutely a stumbling block, and not just for beginners. @rustbot label -C-bug +C-enhancement +A-diagnostics +D-newcomer-roadblock +D-terse +D-confusing |
See #89336 for some work towards improving diagnostics around variance |
Reduction: struct WithLt<'lt> (
&'lt (),
);
impl<'db> WithLt<'db> {
fn check (_: impl FnOnce(*mut &'_ &'db ()))
{}
}
fn _for<'db> (
f: impl FnOnce(*mut &'_ &'db ()),
)
{
WithLt::<'db>::check(move |s| f(s))
}
Using |
Curiously, uncommenting the following turbofish annotation fixes the reduced example: struct WithLt<'lt> (
&'lt (),
);
impl<'db> WithLt<'db> {
fn check<'lt> (f: impl FnOnce(*mut &'_ &'lt ()))
where
'static : 'lt, // make it turbofishable
{
Self::check
// ::<'lt> /* compilation passes if uncommented */
(move |s| f(s))
}
} |
Thanks to this, a HRTB-signature-nudging funnel can be written, which palliates the issue: + fn funnel<'lt> (f: impl Fn(&mut State<'lt, '_>))
+ -> impl Fn(&mut State<'lt, '_>)
+ where
+ 'static : 'lt, // make it turbofishable
+ {
+ f
+ }
impl<'db> Trait<'db> for Impl<'db> {
fn foo(&self, s: &mut State<'db, '_>) {
- Car::new(s).debug(s, &mut (|s| {
+ Car::new(s).debug(s, &mut funnel::<'db>(|s| {
self.some_trait.foo(s);
}));
}
} |
@danielhenrymantilla can you elaborate more on how you arrived at that reduction? I'm a little lost as to why you changed the things you did and how they result in the same problem. Also, how is that |
The So, inside that function, it does not add any property nor restrict anything. It does, however, allow callers to turbofish that lifetime parameter (for "reasons" related to early bound vs late bound lifetimes), which is a nice thing to have for these "investigations", since sometimes the diagnostics can be improved. In this instance it happened to be serendipitous, since it allowed to feature a workaround! Regarding the reduction, it's hard to describe the steps that lead there: since literal closure expressions are known to sometimes be given incorrect signatures w.r.t. lifetime quantification, I suspected that part since the beginning, and just kept stripping all the other elements, as well as simplifying the types to involve the same variance and drop glue. |
@danielhenrymantilla Wow, thanks a lot for coming up with this. I still have to work on a
Isn't that a bug though? It just feels wrong that you have to wrap a closure in such a complex way. I understand that this might not be something that is immediately fixed, but it still feels like a compiler bug to me. |
@BGR360 I hope you do not mind me removing the diagnostic labels. The reason for this is that @danielhenrymantilla brought up the
@danielhenrymantilla It seems like that this is not necessary for the turbofish syntax?! If I remove the @rustbot label +C-bug -C-enhancement -A-diagnostics -D-newcomer-roadblock -D-terse -D-confusing |
@davidhalter you're right, the
So, indeed, I did not need to add that "extra dummy bound" 👍 |
I have skimmed about 100+ "similar" issues here and could find one that matches this issue, so apologies if this was already reported. The issue that seems most similar is this one: #87241.
I have tried to reduce this lifetime issue as much as possible to the essentials, but it's unfortunately still a bit of code:
I expected this to pass the borrow checker (
cannot infer an appropriate lifetime...
; see below). The problem could be that'db: 'a
is inferred forstruct State
, because if you rewrite that struct into this piece of code:the borrow checker is happy. However the borrow checker is also happy if I'm using
some_trait: &'db Impl<'db>
, so it feels like this is really an issue and not me misunderstanding lifetimes. I have tried various approaches like using HRTBs and a lot of complicatedwhere:
with explicit lifetimes, but nothing helped.I run a lot of similar code that passes, but this one doesn't, because the closure is using
self.some_trait
and not something given by the closure (in which case everything would work great!).So again, sorry for the "long" code piece, but I have tried to reduce it for hours and couldn't really get further. I hope I do not waste your time with this.
The actual long-form error:
Meta
rustc --version --verbose
:The text was updated successfully, but these errors were encountered: