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

Confusing type error due to strange inferred type for a closure argument #41078

Open
jseyfried opened this issue Apr 5, 2017 · 35 comments
Open
Labels
A-closures Area: Closures (`|…| { … }`) A-diagnostics Area: Messages for errors, warnings, and lints A-inference Area: Type inference A-type-system Area: Type system C-enhancement Category: An issue proposing an enhancement or a PR with one. D-confusing Diagnostics: Confusing error or lint that should be reworked. D-newcomer-roadblock Diagnostics: Confusing error or lint; hard to understand for new users. P-medium Medium priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Comments

@jseyfried
Copy link
Contributor

jseyfried commented Apr 5, 2017

This example:

pub struct Request<'a, 'b: 'a> {
    a: &'a (),
    b: &'b (),
}
pub trait Handler: Send + Sync + 'static {
    fn handle(&self, &mut Request) -> Result<(),()>;
}
impl<F> Handler for F where F: Send + Sync + 'static + Fn(&mut Request) -> Result<(),()> {
    fn handle(&self, _: &mut Request) -> Result<(),()> {
        unimplemented!()
    }
}
fn make_handler(h: &'static Handler) -> Box<Handler> {
    Box::new(move |req| h.handle(req))
}

errors with

error[E0271]: type mismatch resolving `for<'r, 'r, 'r> <[closure@<anon>:14:14: 14:38 h:_] as std::ops::FnOnce<(&'r mut Request<'r, 'r>,)>>::Output == std::result::Result<(), ()>`
  --> <anon>:14:5
   |
14 |     Box::new(move |req| h.handle(req))
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected bound lifetime parameter , found concrete lifetime
   |
   = note: concrete lifetime that was found is lifetime '_#9r
   = note: required because of the requirements on the impl of `Handler` for `[closure@<anon>:14:14: 14:38 h:_]`
   = note: required for the cast to the object type `Handler`

Annotating the closure parameter |req: &mut Response| allow the example to compile.
Interesting, annotating with |req: &&mut Response| produces a similarly-structured error, so I believe we're inferring &&mut here (maybe related to autoderef?).

@jseyfried
Copy link
Contributor Author

jseyfried commented Apr 5, 2017

cc @nikomatsakis @eddyb

@jseyfried jseyfried changed the title Confusing type error due to a strange inferred type for a closure argument Confusing type error due to strange inferred type for a closure argument Apr 5, 2017
@jseyfried jseyfried added A-diagnostics Area: Messages for errors, warnings, and lints A-type-system Area: Type system T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Apr 5, 2017
@eddyb
Copy link
Member

eddyb commented Apr 6, 2017

This is a known issue IIRC, but I don't know how to actually find the original issue.

@nikomatsakis
Copy link
Contributor

Sorry, I've been putting off this tab for days trying to squeeze out some time to write a more detailed comment. But in short the problem is that we do not infer when to "generalize" the region -- in particular, we will infer a type of &'a mut Handler for some 'a, but your type demands a type of &'a mut Handler for all 'a (i.e., the difference between fn(&'a mut Handler) and for<'a> fn (&'a mut Handler). We do not (yet) infer generalizations except when the expected type provides the hint -- now, in this case, I might expect us to be able to trace things through the expected type. We would I think do so if you were returning Box<FnMut()>, but because the FnMut() requirement is "hidden" (this is a trait that happens to be implemented by closures...) we fail to deduce it. I'm pretty sure there's an open issue on this somewhere.

@gmorenz
Copy link

gmorenz commented Apr 15, 2017

Just ran into this (or at least I'm pretty sure it's this, otherwise feel free to split this off into a new bug)

Smaller test case:

fn main() {
    let suitable = |_| true;
    vec![1,2,3].into_iter().find(suitable);
}

Workarounds: Inline suitable into find, or annotate suitable with a & type, i.e. let suitable = |_: &_| true.

I think all of these issues are older duplicates

@Mark-Simulacrum
Copy link
Member

Mark-Simulacrum commented Apr 15, 2017

More duplicates, or at least the same underlying issue:

We should decide on a single (or two, one for the diagnostic and one for the underlying issue) canonical issue and deduplicate.

@Mark-Simulacrum
Copy link
Member

Leaving the following issues open, since I don't think they're quite duplicates. The rest of the issues linked in the two comments above are about to be closed referencing this issue.

@rminderhoud
Copy link

rminderhoud commented Apr 29, 2017

I wanted to re-include and extend @shepmaster's example from #26937 as it includes a potential work-around (?).

Namely that including the closure directly into the call of the function vs. binding it avoids the problem entirely and using type annotations can sometimes avoid the problem. Hopefully this is insightful and those more experienced can weigh in more.

https://is.gd/6jjGgX

fn caller<F>(f: F)  where F: Fn(&i32) {
    f(&42);
}

fn caller_ref_return<F>(f: F) where F: Fn(&i32) -> &i32 {
    f(&42);
}

fn main() {
    // Works
    caller(|a| println!("{}", a));
    
    // Works 
    let f = |a: &i32| println!("{}", a);
    caller(f);
    
    // Doesn't work (error: type mismatch)
    let f = |a| println!("{}", a);
    caller(f);
    
    // Works 
    caller_ref_return(|a| a);
    
    // Doesn't work (error: expected bound lifetime parameter, found concrete lifetime)
    let f = |a: &i32| a;
    caller_ref_return(f);
    
    // Doesn't work (error: type mismatch)
    let f = |a| a;
    caller_ref_return(f);
}

@Rufflewind
Copy link
Contributor

Not sure if anyone else pointed this out, but wanted to specifically highlight this part of the error message:

for<'r, 'r, 'r>

This is unreadable − all the lifetime parameters are assigned the same name!

@Mark-Simulacrum Mark-Simulacrum added the C-enhancement Category: An issue proposing an enhancement or a PR with one. label Jul 26, 2017
@jonhoo
Copy link
Contributor

jonhoo commented Jan 14, 2018

Another instance of this was just brought up on #rust-beginners with the regexp crate:

let s = "hello";
let re = regex::Regex::new("[aeiou]").unwrap();
let after = re.replace_all(s, |c| "xxx".to_string());
println!("{} -> {}", s, after);

fails with the supremely unhelpful error:

error[E0271]: type mismatch resolving `for<'r, 's> <[closure@src/main.rs:7:35: 7:56] as std::ops::FnOnce<(&'r regex::Captures<'s>,)>>::Output == std::string::String`
 --> src/main.rs:7:20
  |
7 |     let after = re.replace_all(s, |c| "xxx".to_string());
  |                    ^^^^^^^^^^^ expected bound lifetime parameter, found concrete lifetime
  |
  = note: required because of the requirements on the impl of `regex::Replacer` for `[closure@src/main.rs:7:35: 7:56]`

error[E0631]: type mismatch in closure arguments
 --> src/main.rs:7:20
  |
7 |     let after = re.replace_all(s, |c| "xxx".to_string());
  |                    ^^^^^^^^^^^    --------------------- found signature of `fn(_) -> _`
  |                    |
  |                    expected signature of `for<'r, 's> fn(&'r regex::Captures<'s>) -> _`
  |
  = note: required because of the requirements on the impl of `regex::Replacer` for `[closure@src/main.rs:7:35: 7:56]`

error: aborting due to 2 previous errors

Compiles fine if you use |c: &regex::Captures|.

@quadrupleslap
Copy link

quadrupleslap commented Jan 15, 2018

I'm dumb, but isn't this a lot like the monomorphization restriction in Haskell, but with lifetimes instead of types?

@nikomatsakis
Copy link
Contributor

Regarding this example that @jonhoo cited (playground link), the cause is that, because the signature for replace_all requires only that R: Replacer, rather than say R: FnMut(&Captures) -> String, we don't have the "expected type" for the closure's arguments at the time that we need them. As a result, we fail to infer that the region must be bound in the closure (sorry, jargon).

I note this because, in addition to improving the error msg, I'd of course prefer if that example just worked -- but resolving that issue (figuring out bound regions more generally) is a bit of an open question. In principle it may be possible, I haven't thought hard about it, but it'll require a lot of refactoring before we get there.

Anyway, we ought to be able to improve the error message in the meantime I suppose.

@nikomatsakis
Copy link
Contributor

(I suppose that we might, in this particular case, be able to get smarter about the expected type. There is even an open issue on this, but I can't find it just now.)

@estebank
Copy link
Contributor

estebank commented Dec 3, 2018

Another repro case:

struct My(i32);

fn call_with_ref<F>(some_closure: F) -> i32
where
    F: for<'a> Fn(&'a My) -> &'a i32,
{
    let value = My(0);
    *some_closure(&value)
}

fn main() {
    
    
    let _f = |arg: &My| &arg.0;
    //This doesn't work
    call_with_ref(_f);
    
    //This is ok.
    //call_with_ref(|arg: &My| &arg.0);
}

@Wizr
Copy link

Wizr commented Jul 26, 2019

Maybe similar issue playground

fn main() {
    let s = "";
    ((&|_| s) as &dyn Fn(&str) -> &str)(s);
}

But this works

fn main() {
    let s = "";
    let f: &dyn Fn(&str) -> &str = &|_| s;
    f(s);
}

And another one

@estebank estebank added the D-newcomer-roadblock Diagnostics: Confusing error or lint; hard to understand for new users. label Oct 15, 2019
@jyn514 jyn514 added the D-confusing Diagnostics: Confusing error or lint that should be reworked. label Oct 15, 2020
@apiraino
Copy link
Contributor

Assigning P-medium as discussed as part of the Prioritization Working Group procedure and removing I-prioritize.

Additionally we nominate this longstanding issue to be discussed during the next meeting

@apiraino apiraino added I-nominated P-medium Medium priority and removed I-prioritize Issue: Indicates that prioritization has been requested for this issue. labels Oct 15, 2020
@spastorino
Copy link
Member

@estebank
Copy link
Contributor

estebank commented Sep 15, 2021

Triage: Current output:

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=456b5353ab48999eb273c83cfc0ae6f7

error[E0308]: mismatched types
  --> src/lib.rs:14:5
   |
14 |     Box::new(move |req| h.handle(req))
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one type is more general than the other
   |
   = note: expected type `for<'r, 's, 't0> Fn<(&'r mut Request<'s, 't0>,)>`
              found type `Fn<(&mut Request<'_, '_>,)>`
note: this closure does not fulfill the lifetime requirements
  --> src/lib.rs:14:14
   |
14 |     Box::new(move |req| h.handle(req))
   |              ^^^^^^^^^^^^^^^^^^^^^^^^

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=8342936d11149e38cafc35da516274cb

error[E0521]: borrowed data escapes outside of closure
 --> src/main.rs:3:28
  |
2 |     let mut fields: Vec<&str> = Vec::new();
  |         ---------- `fields` declared here, outside of the closure body
3 |     let pusher = |a: &str| fields.push(a);
  |                   -        ^^^^^^^^^^^^^^ `a` escapes the closure body here
  |                   |
  |                   `a` is a reference that is only valid in the closure body

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=28594d6b627049e7a1de6cd590dfcae8

error[E0308]: mismatched types
  --> src/main.rs:30:5
   |
30 |     foo2(|x,y| ()); // Error, but works with one argument
   |     ^^^^ lifetime mismatch
   |
   = note: expected type `for<'r, 's> Fn<(&'r str, &'s str)>`
              found type `Fn<(&str, &str)>`
note: this closure does not fulfill the lifetime requirements
  --> src/main.rs:30:10
   |
30 |     foo2(|x,y| ()); // Error, but works with one argument
   |          ^^^^^^^^
note: the lifetime requirement is introduced here
  --> src/main.rs:15:12
   |
15 | fn foo2<F: Foo>(f: F) {
   |            ^^^

error: implementation of `Fn` is not general enough
  --> src/main.rs:31:10
   |
31 |     foo3(Box::new(&|x,y| ())); // Error, even with one argument
   |          ^^^^^^^^^^^^^^^^^^^ implementation of `Fn` is not general enough
   |
   = note: closure with signature `fn(&'2 str, &str)` must implement `Fn<(&'1 str, &str)>`, for any lifetime `'1`...
   = note: ...but it actually implements `Fn<(&'2 str, &str)>`, for some specific lifetime `'2`

error: implementation of `Fn` is not general enough
  --> src/main.rs:31:10
   |
31 |     foo3(Box::new(&|x,y| ())); // Error, even with one argument
   |          ^^^^^^^^^^^^^^^^^^^ implementation of `Fn` is not general enough
   |
   = note: closure with signature `fn(&str, &'2 str)` must implement `Fn<(&str, &'1 str)>`, for any lifetime `'1`...
   = note: ...but it actually implements `Fn<(&str, &'2 str)>`, for some specific lifetime `'2`

https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=0254e96af9155a0a99880e06b4a75b95

error: implementation of `FnOnce` is not general enough
 --> src/main.rs:3:29
  |
3 |     vec![1,2,3].into_iter().find(suitable);
  |                             ^^^^ implementation of `FnOnce` is not general enough
  |
  = note: closure with signature `fn(&'2 i32) -> bool` must implement `FnOnce<(&'1 i32,)>`, for any lifetime `'1`...
  = note: ...but it actually implements `FnOnce<(&'2 i32,)>`, for some specific lifetime `'2`

@Aaron1011
Copy link
Member

Related: #91966

@108anup
Copy link

108anup commented Nov 2, 2023

I don't think any of the posts here mention a workaround (until https://rust-lang.github.io/rfcs/3216-closure-lifetime-binder.html gets implemented).

I have been able to use the following workaround (shown for the example posted by @estebank).

struct My(i32);

fn call_with_ref<F>(some_closure: F) -> i32
where
    F: for<'a> Fn(&'a My) -> &'a i32,
{
    let value = My(0);
    *some_closure(&value)
}

fn coerce<F>(closure: F) -> F
where F: for<'a> Fn(&'a My) -> &'a i32
{
    return closure
}

fn fun()
{
    // Does not work
    // let _f = |arg: &My| &arg.0;
    // call_with_ref(_f);

    // Workaround
    let _f = coerce(|arg: &My| &arg.0);
    call_with_ref(_f);

    // This is ok.
    call_with_ref(|arg: &My| &arg.0);
}

While in the above example, the closure can be passed to call_with_ref directly also and the problem seems contrived, this is not the case in the below example. Here the explicit coerce function seems helpful. I am guessing this will also work with Box instead of Option (for the request handling example in the issue).

fn optional_call_with_ref<F>(some_closure: Option<F>) -> i32
where
    F: for<'a> Fn(&'a My) -> &'a i32,
{
    let value = My(0);
    *(some_closure.unwrap())(&value)
}

fn fun2()
{
    // Does not work, have to use explicit coercion.
    // optional_call_with_ref(Some(|arg: &My| &arg.0));

    // Workaround
    let _f = coerce(|arg: &My| &arg.0);
    optional_call_with_ref(Some(_f));
}

https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=893482858c3f6e96f464b052e1cf321d

I had three questions about this workaround:

  1. Is it possible to give a name to the trait bound for<'a> Fn(&'a My) -> &'a i32 so that multiple instances of this bound can use the name instead of repeating the definition.
  2. Is it possible to write a macro to generate the coerce function? Can macros accept a trait bound and a closure. E.g., to replace with let _f = coerce_macro!(for<'a> Fn(&'a My) -> &'a i32, |arg: &My| &arg.0)
  3. Is there a better workaround?

Disclaimer: I am fairly new to rust.

@estebank
Copy link
Contributor

estebank commented Nov 3, 2023

@108anup there's no way of being explicit yet in stable Rust, but nightly Rust supports specifying the for lifetime let _f = for<'a> |arg: &'a My| -> &'a i32 { &arg.0 };. Your work-around is what I'd use in stable.

@douglas-raillard-arm
Copy link

@108anup: Yes to 1. and 2.:
https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0a7258b5644e37aca7a25008d5ebca3d

We can't properly give a name to the bound AFAIK since trait alias are unstable, but we can make a coercion function with a user-chosen name which is basically as good in this case. Then the user can just reuse that coercion function without having to repeat the details.

I hope the for<'a>|&'a ...| syntax gets stabilized soon, nom (or I suppose any closure-heavy lib) is a very special kind of hell without that.

@108anup
Copy link

108anup commented Nov 8, 2023

@douglas-raillard-arm Thanks, this is very helpful!!

I wanted to give a name to the trait bound so that I can use the name in (1) the coercion function (when creating the closure) as well as (2) the function (or struct) that consumes the closure. E.g., in your playground link, the function signature is repeated on line 34. Is there a way to avoid repeating the definition (function signature).

My current workaround has been on the following lines (i.e., store reference to trait object). I think there were some cases of closures I wan't able to represent this way. I don't recall exactly what though.

type SetRealValueFn<'a, 'ctx> = &'a dyn Fn(&'ctx z3::Context, RealNumRep) -> z3::ast::Bool<'ctx>;
type GetFeasibleRealValueFn<'a> = &'a dyn Fn(&z3::Solver) -> RealNumRep;
type RealNumInt = i32;
type RealNumRep = Ratio<RealNumInt>;

struct BinarySearchDetails<'a, 'ctx> {
    // https://stackoverflow.com/questions/27831944/how-do-i-store-a-closure-in-a-struct-in-rust
    name: &'a String,
    feasible: Option<RealNumRep>,
    lo: RealNumRep,
    hi: RealNumRep,
    set_value: SetRealValueFn<'a, 'ctx>,
    get_feasible_closure: GetFeasibleRealValueFn<'a>,
    use_int: bool,
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-closures Area: Closures (`|…| { … }`) A-diagnostics Area: Messages for errors, warnings, and lints A-inference Area: Type inference A-type-system Area: Type system C-enhancement Category: An issue proposing an enhancement or a PR with one. D-confusing Diagnostics: Confusing error or lint that should be reworked. D-newcomer-roadblock Diagnostics: Confusing error or lint; hard to understand for new users. P-medium Medium priority T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests