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.