Skip to content
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

Incorrect lifetime error with a generic FnMut after annotating the type in a closure. #22557

Closed
theemathas opened this issue Feb 20, 2015 · 13 comments
Labels
A-lifetimes Area: Lifetimes / regions C-enhancement Category: An issue proposing an enhancement or a PR with one. I-needs-decision Issue: In need of a decision. P-low Low priority T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@theemathas
Copy link
Contributor

The code:

struct Nothing;

fn foo<'a, F: FnMut(&'a mut Nothing) -> T, T>(_: F) {}

fn bar<'a, F: FnMut(&'a mut Nothing) -> &'a mut Nothing>(_: F) {}

fn main() {
    bar(|x| x); // OK
    bar(|x: &mut Nothing| x); // OK
    foo(|x| x); // OK
    foo(|x: &mut Nothing| x); // Error
}

Playpen

Compiling this on playpen results in this compile error:

<anon>:11:27: 11:28 error: cannot infer an appropriate lifetime due to conflicting requirements
<anon>:11     foo(|x: &mut Nothing| x); // Error
                                    ^
<anon>:11:27: 11:28 note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the block at 11:26...
<anon>:11     foo(|x: &mut Nothing| x); // Error
                                    ^
<anon>:11:27: 11:28 note: ...so that expression is assignable (expected `&mut Nothing`, found `&mut Nothing`)
<anon>:11     foo(|x: &mut Nothing| x); // Error
                                    ^
<anon>:11:5: 11:8 note: but, the lifetime must be valid for the expression at 11:4...
<anon>:11     foo(|x: &mut Nothing| x); // Error
              ^~~
<anon>:11:5: 11:8 note: ...so that the type `&mut Nothing` will meet its required lifetime bounds
<anon>:11     foo(|x: &mut Nothing| x); // Error
              ^~~
error: aborting due to previous error

I think that this code is supposed to compile, and the error message isn't very helpful either. What exactly is (expected &mut Nothing, found &mut Nothing)?

@theemathas
Copy link
Contributor Author

Note that declaring bar as this also compiles for the //OK lines:

fn bar<'a, 'b, F: FnMut(&'a mut Nothing) -> &'b mut Nothing>(_: F) {}

@huonw huonw added A-lifetimes Area: Lifetimes / regions I-nominated labels Feb 20, 2015
@huonw
Copy link
Member

huonw commented Feb 20, 2015

The code in the comment seems... bad. This is possibly related to #22077.

@theemathas
Copy link
Contributor Author

A variant that uses &T instead of &mut T and Fn instead of FnMut:

struct Nothing;

fn foo<'a, 'b, F: Fn(&'a Nothing) -> T, T: 'b>(_: F) { }
fn bar<'a, 'b, F: Fn(&'a Nothing) -> &'b Nothing>(_: F) { }

fn main() {
    bar(|x| x); // OK
    bar(|x: &Nothing| x); // OK
    foo(|x| x); // OK
    foo(|x: &Nothing| x); // Error
}

playpen

@theemathas
Copy link
Contributor Author

Possibly related to #22340 and #17004

@pnkfelix
Copy link
Member

cc @pnkfelix @nikomatsakis

punting to next week's triage.

@pnkfelix pnkfelix added the C-enhancement Category: An issue proposing an enhancement or a PR with one. label Mar 5, 2015
@pnkfelix
Copy link
Member

pnkfelix commented Mar 5, 2015

an interesting corner case. we could try to fix this (and such a fix should be backwards compatible), but its not a terribly high priority. P-low, I-enhancement

@pnkfelix pnkfelix added P-low Low priority and removed I-nominated labels Mar 5, 2015
@nikomatsakis
Copy link
Contributor

This is actually the expected behavior, I believe, though writing this comment is making me want to dig a bit deeper to make sure.

What is happening is that when an explicit annotation is provided, |x: &mut Nothing| yields a type that is qualified over all 'a, I believe, and it then ignores the expected type. This winds up (I think) being a subtype of what is actually required in the various cases and hence things type check, except when they don't. Sorry this is not detailed, as I said, I want to dig in to confirm and maybe then provide a more detailed trace of what is going on.

That said, if my hypothesis is correct, then there are two routes to fix this:

  1. Either we combine the expected type and the manual annotation.
  2. We augment the manual annotation so that we can more precisely specify what is needed here.

Similarly the example from #22557 (comment) is I think harmless -- basically the caller is providing a fn where 'a and 'b will be bound the same lifetime, and that's ok, but the callee doesn't get to assume that it does.

@nikomatsakis
Copy link
Contributor

OK, so, I looked a bit at this. The reason the results are a bit surprising is that the code is a bit hokey right now but not (I believe) unsound (though it wouldn't surprise me if there was a gotcha in some particular case). One thing it does do is separate out the expected argument and return types. So in these cases part of what results for the inconsistent errors is that we infer the return type based on the expectation. For bar, that is &mut Nothing with the same lifetime as the input -- and that case type checks. But for foo, that is a fresh variable to represent T. In that case, we wind up relating this variable to a pointer whose lifetime is a free region specific to foo. This yields a compilation error because T must outlive the call to foo, and it can't if the region is confined to foo. This is a "known hokey" part of the impl because I feel that this kind of error should be enforced in a different way, but we still get some kind of error when an error is expected.

An example of code that probably ought to compile but doesn't because of this phenomena is:

fn main() {
    let f = |x: &i32| x;
    let i = &3;
    let j = f(i);
}

In this case, there is no expected return type, so you wind up with a fresh variable, just as in foo. To make this compile you have to add an explicit return type annotation.

@huonw
Copy link
Member

huonw commented Oct 27, 2015

This (or something similar) appears to happen with higher-ranked bounds too:

fn foo<F>(_: F) where F: for<'a> Fn(&'a str) -> &'a str
{}

fn main() {
    foo(|s| s); // ok
    foo(|s: &str| s); // not ok
}
<anon>:6:19: 6:20 error: cannot infer an appropriate lifetime for automatic coercion due to conflicting requirements [E0495]
<anon>:6     foo(|s: &str| s); // not ok
                           ^
<anon>:4:1: 7:2 help: consider using an explicit lifetime parameter as shown: fn main()
<anon>:4 fn main() {
<anon>:5     foo(|s| s); // ok
<anon>:6     foo(|s: &str| s); // not ok
<anon>:7 }

@brson brson added T-lang Relevant to the language team, which will review and decide on the PR/issue. I-needs-decision Issue: In need of a decision. labels Mar 23, 2017
@brson
Copy link
Contributor

brson commented Mar 23, 2017

Expected behavior or not?

@estebank
Copy link
Contributor

All cases except for @nikomatsakis' no longer cause any error. Should we keep this issue open or reopen a more targeted one for that case?

@pnkfelix
Copy link
Member

pnkfelix commented Feb 1, 2019

Its even questionable whether @nikomatsakis 's example should compile. (To be fair, they did qualify that example with "probably should compile.")

Lets close this and open up a separate issue dedicated to that example, especially since I think that example is related to #22340

@pnkfelix
Copy link
Member

pnkfelix commented Feb 1, 2019

Closing and leaving the remaining example in its own issuein #58052.

@pnkfelix pnkfelix closed this as completed Feb 1, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-lifetimes Area: Lifetimes / regions C-enhancement Category: An issue proposing an enhancement or a PR with one. I-needs-decision Issue: In need of a decision. P-low Low priority T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

6 participants