Different result with type alias than same thing typed out?

This code works, but when I replace a type literal with a type alias which should be identical (as far as I can see), then it no longer compiles.

What am I missing here? Are aliases somehow different than just pasting the text?

trait Foo {
    fn foo() -> i32 where Self:Sized;
}

type FooFunc<R> = dyn Fn() -> R;

struct S;

impl Foo for S {
    fn foo() -> i32 { 1 }
}

fn gen<T:Foo>() {
    //let func : &dyn Fn() -> i32 = &T::foo; // works
    let func : &FooFunc<i32> = &T::foo; // doesn't work, but should be identical to the line above (???)
    println!("{}",func());
}

fn main() {
    gen::<S>();
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error[E0310]: the parameter type `T` may not live long enough
  --> src/main.rs:15:32
   |
13 | fn gen<T:Foo>() {
   |        -- help: consider adding an explicit lifetime bound `T: 'static`...
14 |     //let func : &dyn Fn() -> i32 = &T::foo; // works
15 |     let func : &FooFunc<i32> = &T::foo; // doesn't work, but should be identical to the line above (???)
   |                                ^^^^^^^
   |
note: ...so that the type `fn() -> i32 {<T as Foo>::foo}` will meet its required lifetime bounds
  --> src/main.rs:15:32
   |
15 |     let func : &FooFunc<i32> = &T::foo; // doesn't work, but should be identical to the line above (???)
   |                                ^^^^^^^

error: aborting due to previous error

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

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

dyn Trait has an implicit lifetime, which gets locked to 'static in your type alias. You can parameterize it:

type FooFunc<'a, R> = dyn Fn() -> R + 'a;

All trait objects have a lifetime. dyn Fn -> R is actually just syntax sugar for dyn Fn -> R + 'a, similar to how &T is sugar for &'a T. However, the way that this lifetime 'a is chosen differs based on context:

In a function body, typically all missing lifetime variables are effectively assigned fresh inference variables. This means that:

let func : &dyn Fn() -> i32 = &T::foo;

// is effectively the same as
let func : &'_ dyn Fn() -> i32 + '_ = &T::foo;

However, type inference and lifetime inference only exists inside function bodies. It does not exist in anything that could constitute a public interface, like a type or a function's signature. So omitting a lifetime argument in places such as these always means something else (if it is even allowed).

I'm a bit surprised that the type definition even compiles. But, given the fact that it does compile, it's pretty clear what the compiler has done:

type FooFunc<R> = dyn Fn() -> R;

// must surely be equivalent to
type FooFunc<R> = dyn Fn() -> R + 'static;

Thank you both, that makes a bit more sense now. However I am still confused why &T::foo can't be assigned to

dyn Fn() -> i32 + 'static

Isn't Foo::foo implicitly static since it doesn't take &self?

Yes and no.

Something does seem odd to me. Foo::foo should indeed be 'static, as should T::foo even for unknown T. However, this has nothing to do with whether or not it takes &self; it has to do with the fact that it has no context to close over.

It's a pointer to a function in static memory, just like any expression of the form path::fn_name or path::Type::fn_name. (or <path::Type as path::Trait>::fn_name, etc.)

More minimal example of the problem at hand:

trait Trait {
    fn foo();
}

fn is_static<F : 'static> (_: &'_ F) {}

fn foo<T : Trait> ()
{
    is_static(&T::foo);
}

gives

error[E0310]: the parameter type `T` may not live long enough
 --> src/lib.rs:9:5
  |
7 | fn foo<T : Trait> ()
  |        -- help: consider adding an explicit lifetime bound `T: 'static`...
8 | {
9 |     is_static(&T::foo);
  |     ^^^^^^^^^
  |
note: ...so that the type `fn() {<T as Trait>::foo}` will meet its required lifetime bounds
 --> src/lib.rs:9:5
  |
9 |     is_static(&T::foo);
  |     ^^^^^^^^^

In other words: even if T::foo must be 'static no matter T, the compiler still sees T::foo as only outliving what T outlives...

By the way, the same happens with &bar::<T> given fn bar<T>() {}.

This looks like a bug; but it kind of makes sense when we come to think of it: the zero-sized item representing bar<T> (or <T as Trait>::foo) is ill-formed when T is, such as when being outside T's lifetime. So an explicit coercion to the function pointer type (i.e., no longer zero-sized type-specific item, but a dynamic / runtime version of it, with type erasure and thus 'static lifetime) is needed to achieve 'staticness.

  • Playground

  • And once in that first level of runtime-ness / type-level erasure, fn() can be coerced to dyn Fn() + 'static since fn() is both Fn() and 'static :slight_smile:

Very good thanks Yandros :slight_smile: As you say it kinda makes sense. Though I wonder if the compiler shouldn't be able to know this.

I will mark your post as the solution, going through fn() seems to get the job done here.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.