Tuple struct to function coercion : what are the lifetime parameters of said function?


I don’t understand something about the feature that makes it possible to coerce a tuple struct to a function as in:

struct MyType(u8);

let optional_mytype: Option<MyType> = Some(12).map(MyType);
//                                                 ^^^^^^ here, MyType is treated as a function

In the example above, no lifetimes are involved: everything is easy. However, when the structure has a generic lifetime bound, I’m having troubles defining the exact signature of the function it would coerce to.

Here’s a MRE of something I’ve attempted to do, and that doesn’t work (sandbox link):

struct MyStruct<'a>(&'a ());

// This looks, to me, like the signature the "constructor" should have
type MyStructConstructor = for<'a> fn(&'a ()) -> MyStruct<'a>;

/// Coercion problem here
fn some_code() {
    let mut constructor: MyStructConstructor = MyStruct;

and the error given by the type-checker:

   Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
 --> src/lib.rs:7:48
7 |     let mut constructor: MyStructConstructor = MyStruct;
  |                                                ^^^^^^^^ one type is more general than the other
  = note: expected fn pointer `for<'a> fn(&'a ()) -> MyStruct<'a>`
             found fn pointer `fn(&()) -> MyStruct<'_>`

And I honestly don’t understand why my constructor type alias isn’t valid, and why the type suggested by the error message is much more lax than it should be. I mean, the lifetime <'a> of my structure should be exactly equal to that of the unit reference that is contained by the structure, not a random one.

Here‘s a more concrete (still minimal) example of what I’m actually trying to do, if having context helps you.

Thanks in advance

>Solution :

In fact, the error message might be a bit misleading. '_ does not mean any lifetime goes, it just means a lifetime I haven’t named explicitly, just as writing &() (which is the same as &'_ ()). Rust does that because that’s not the issue.

To understand, let’s actually annotate implicit generic lifetimes in your code:

fn some_code() {
  let constructor: MyStructConstructor = MyStruct<'_>;

Notice how something is wrong? On the right hand side, you have something whose type is generic over a lifetime. On the left hand side, you don’t.
In fact, a more appropriate constructor signature for MyStruct would be

struct MyStruct<'a>(&'a ());

type MyStructConstructor<'a> = fn(&'a ()) -> MyStruct<'a>;

fn some_code() {
  let constructor: MyStructConstructor = MyStruct;

leaving the lifetime implicit — see the playground.
This compiles.

So now you may understand why MyStructConstructor was more generic than MyStruct: because for MyStruct, you have to specify a lifetime to begin with, that is, the actual type is, for a given 'a, MyStruct<'a>. Then, with that constructor, you are only capable of building objects of type MyStruct<'a>, given &'a (). On the other hand, with MyStructConstructor, given a reference to a () with any lifetime, you would be able to build a MyStruct of the same lifetime, which is more general.

Leave a Reply Cancel reply