Can't compile: storing global fn as a &'static fn

Minimal Failure Case:

fn foo() -> usize {
  return 42;
}

pub struct Container {
    pub f: &'static fn() -> usize
}

pub fn main() {
    let x = Container { f: &foo };
}

Why doesn't Rust let me do this? How do I fix this?

I also need Container to be Sync/Send (something that can be safely shared across threads.)

You don't need to make a reference to a function pointer, normal function pointers are Send + Sync, so if you are using them it should be fine.

fn foo() -> usize {
  return 42;
}

pub struct Container {
    pub f: fn() -> usize
}

pub fn main() {
    let x = Container { f: foo };
}

2 Likes

What do you suggest I read up on?

The reason I tried

&'static fn () -> usize,

but never tried

fn () -> usize

is that I incorrectly associated fn ... with the generate machine code (and thus varaible size), rather than a ptr to the function.

https://doc.rust-lang.org/stable/book/ch19-05-advanced-functions-and-closures.html?highlight=Function,pointer#function-pointers

Note that all fn(): 'static, as long as no unsafe if involved; closures which can be casted are treated just like functions, and both closures and regular functions which involve generic parameters are monomorphized at runtime to resolve the types, generating 'static machine code.

extern "ABI" fn (...) -> _ is the syntax chosen for a &'static Code<ABI>, so yes, it is a 'static valid Rust references. That being said, a &'_ &'static T can always coerce to a &'static T, so the fact that a &'_ fn(...) -> _ doesn't is indeed surprising.


I hadn't run the playground so I mispredicted the error cause. There is a second thing going on here, and that is that the type of foo is "fn () -> usize { foo }", that is, a unique zero-sized type that carries the address of the code of foo at the type-level / compile-time (called a fn item). This is like [T; 32] being the type of arrays of len 32: this length information is carried at the type-level.

And in the same fashion that you can coerce (a pointer to) a [T; 32] to (a pointer to) a [T], resulting in a unified type for all arrays (the slice type), thanks to the length being now carried within the runtime information of the fat pointer,
you can coerce a fn () -> usize { foo } to a fn () -> usize: the unified type for all functions, resulting in the address of the code being carried within the runtime information, of what has now become a function pointer.

And it turns out that Rust cannot coerce a &(fn () -> usize { foo }) to a &(fn () -> usize)

You can force it to:

fn foo() -> usize { 42 }

pub struct Container {
    pub f: &'static fn() -> usize,
}

fn main() {
    let _x = Container { f: &(foo as fn() -> usize) };
}

But yeah &'static fn(...) -> ... is almost never what you want.

1 Like

Yes, you can use an intermediate "coercion state" to get there, but what I meant is that it doesn't manage to get that coercion done on its own when behind a reference, which is what may be surprising (AFAIK, the other coercion are not only possible but even helped when the & _ operator is involved).