Skip to content

Misleading error message originating from the fact that closure types are not inferred being polymorphic over lifetimes. (And related errors in need of improvement.) #71209

Open
@steffahn

Description

@steffahn

The following code produces an error message that doesn’t at all explain the actual problem.

fn foo() -> &'static str {
    let identity = |x| x;
    let result = identity("result!");
    let local_string: String = "local".into();
    let _ = identity(&local_string);
    
    result
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0515]: cannot return value referencing local variable `local_string`
 --> src/lib.rs:7:5
  |
5 |     let _ = identity(&local_string);
  |                      ------------- `local_string` is borrowed here
6 |     
7 |     result
  |     ^^^^^^ returns a value referencing data owned by the current function

error: aborting due to previous error

For more information about this error, try `rustc --explain E0515`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

In particular looking at this statement – “returns a value referencing data owned by the current function” – that’s just not what is going on here.

What really is going on here (as far as I can tell) is that identity is inferred as some closure implementing Fn(A) -> A for A to be determined, then the first call identity("result!") tells the type inference that A is &'a str for some 'a to be determined, and finally identity(&local_string) already knows to apply unsized coercion and typechecks nailing 'a down to the lifetime of local_string. This means result is an &'a str with that same lifetime.

A bit less confusing of an error message but still suboptimal IMO is what you get when trying to use a closure truly polymorphically:

fn foo() {
    let identity = |x| x;
    identity(1u8);
    identity(1u16);
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
 --> src/lib.rs:4:14
  |
4 |     identity(1u16);
  |              ^^^^ expected `u8`, found `u16`
  |
help: change the type of the numeric literal from `u16` to `u8`
  |
4 |     identity(1u8);
  |              ^^^

error: aborting due to previous error

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

To learn more, run the command again with --verbose.

I think this could be improved by a message explaining where the expected type u8 comes from.





Back to the original example.. in my opinion it would be nice if the example would not error at all. After all the following does work:

fn str_closure<F: Fn(&str) -> &str>(f: F) -> F { f }

fn foo() -> &'static str {
    let identity = str_closure(|x| x);
    let result = identity("result!");
    let local_string: String = "local".into();
    let _ = identity(&local_string);
    
    result
}

However, this again doesn’t work:

fn str_closure<F: Fn(&str) -> &str>(f: F) -> F { f }

fn foo() -> &'static str {
    let closure = |x| x;
    let identity = str_closure(closure);
    let result = identity("result!");
    let local_string: String = "local".into();
    let _ = identity(&local_string);
    
    result
}





Finally, on the topic of trying to get more polymorphism out of closures than they support, I ran into this error message (which I could move into a separate issue if you guys think that it’s completely unrelated):

fn f<A>() -> A { unimplemented!() }
fn foo() {
    let _ = f;
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0282]: type annotations needed for `fn() -> A {f::<A>}`
 --> src/lib.rs:3:13
  |
3 |     let _ = f;
  |         -   ^ cannot infer type for type parameter `A` declared on the function `f`
  |         |
  |         consider giving this pattern the explicit type `fn() -> A {f::<A>}`, where the type parameter `A` is specified

error: aborting due to previous error

For more information about this error, try `rustc --explain E0282`.
error: could not compile `playground`.

To learn more, run the command again with --verbose.

Which is just weird, because: What is this fn() -> A {f::<A>} syntax supposed to mean?

@rustbot modify labels to T-compiler, T-lang, A-diagnostics, D-incorrect, D-terse, D-papercut, C-bug, C-enhancement, A-closures, A-inference.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-closuresArea: Closures (`|…| { … }`)A-diagnosticsArea: Messages for errors, warnings, and lintsA-inferenceArea: Type inferenceD-papercutDiagnostics: An error or lint that needs small tweaks.D-terseDiagnostics: An error or lint that doesn't give enough information about the problem at hand.T-compilerRelevant to the compiler team, which will review and decide on the PR/issue.T-langRelevant to the language team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions