error[E0310]: the parameter type `T` may not live long enough
--> src/main.rs:3:5
|
2 | trait FnSet<T> {
| - help: consider adding an explicit lifetime bound...: `T: 'static`
3 | const FUNCTIONS: &'static [fn(&T)];
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ...so that the reference type `&'static [for<'r> fn(&'r T)]` does not outlive the data it points at
Usually, everything that can be defined as a constant I make a constant. But I see no reason why I couldn't create a constant array of function pointers.
As I understand, the lifetime annotation allows compiler to check a data in memory is alive. But why is this necessary when I have no data? I just have function pointers that own nothing.
I could add T: 'static bound, but I want this trait to be able to be implemented for structs with references.
Yes, here you are hitting a limitation of the type checker / borrow checker that, imho is unnecessary: it's not because a function's parameter is not 'static that the pointer itself should be prevented from being 'static. And yet that's how Rust currently works . I think this happens because the moment some type parameter is mentioned in a type, including a function pointer type, then the whole type is considered to be bounded by the lifetime of that type parameter among other things).
To circumvent this, you have to add an explicit generic lifetime parameter:
which does compile. IMO this demonstrates that there really is no good reason why the const FUNCTIONS: ... version didn’t work. If there isn’t already something about this, it could IMO be worth it to open an issue about this.
Indeed it is quite surprising that &'static [fn(&T)] is both:
a well-defined type,
not a : 'static type (because fn(&T) is not 'static).
So the assumption that given two types T and U so that type T = &'lifetime U; is a usable type, we don't necessarily have U : 'lifetime and thus don't have T : 'lifetime either.
EDIT: by that I mean that Rust's constraint solver does not consider that these types satisfy thouse bounds, evn though they must do so in order for the type T to be usable and thus well-formed
Wait a minute, maybe I'm not understanding something correctly, but how is this not unsafe? I thought &'lifetime T always implied T: 'lifetime otherwise you can get an invalid reference pointing into freed memory, no?
It's possible for T: 'lifetime to be true and implied without the compiler being able to explicitly realize it. In such a case there's nothing unsafe, only that some safe programs that couldn't be written. Unless you can actually construct a T that violates T: 'lifetime, then there's a real danger.
Not saying that's how it is, but it's a possibility.
Got some quirky behavior out of this (and similar things). Apparently, stating a return type of a function like this kind-of defers the lifetime-check to the caller...
e.g. this compiles fine, too:
fn foo<'a>() -> &'static &'a () {
&&()
}
but using it as in
fn bar<'a>() {
foo::<'a>();
}
doesn’t work.
Going back to the example at hand, here are a few things that I tried:
But isn't that what @Yandros claimed in his post above? He said it was surprising that &'static [fn(&T)] is a well-defined type because fn(&T) is not 'static and so [fn(&T)] is not 'static and so if we simplify by substituting in some type variables we essentially have a &'lifetime T where T: !'lifetime and yet the compiler is still compiling it without raising any errors?
Actually, is it true that fn(&T) is not 'static? This seems to work just fine:
trait FnSet<T> {
fn functions() -> &'static [fn(&T)];
}
fn takes_num_ref(_x: &i32) {}
fn also_takes_num_ref(_x: &i32) {}
struct Struct;
impl FnSet<i32> for Struct {
fn functions() -> &'static [fn(&i32)] {
&[
takes_num_ref,
also_takes_num_ref,
]
}
}
fn main() {
let funcs = Struct::functions();
for num_ref in &[1, 2, 3] {
for func in funcs {
func(num_ref);
}
}
}
Or is the issue that fn(&T) can potentially be non-'static? If so can I see an example because I can't think of any way off the top of my head on how to make a non-'static function pointer...
I don't think there's a safe way to do it, but I believe you can have function pointers into dynamically loaded libraries, and those could have non-static lifetime constraints. But that's probably not relevant here because you won't be able to put them into a static array.
I think everyone is agreeing here that at least when T: 'static , then fn(&T): 'static .
This is confusing to me, because I can't imagine why lifetime of the function's arguments have any bearing on whether the function itself exists. You can obviously create a function before you pass arguments into it, so you can create a reference to the function that has a longer lifetime than the lifetime of its arguments. Unless I'm misunderstanding something...
What exactly do you mean by confusing. Do you disagree? I was saying "at least" to say that that statement is uncontroversion, but I’m not implying that anyfn is not 'static. Just saying that for surefn(&T) is 'static if T: 'static. If T: !'static, I don’t know. And AFAICT the compiler currently seems to be of the opinion that fn(&T) isn’t static in this case.
I am equally confused, and I believe for the same reason as @skysch: I don't understand why the lifetime of a function pointer's argument would have any bearing on the lifetime of the function pointer itself... and yet... this does not compile:
trait FnSet<T> {
fn functions() -> &'static [fn(&T)];
}
fn takes_nonstatic(_x: &NonStatic<'_>) {}
fn also_takes_nonstatic(_x: &NonStatic<'_>) {}
struct NonStatic<'a>(&'a i32);
struct Struct;
impl FnSet<NonStatic<'_>> for Struct {
fn functions() -> &'static [fn(&NonStatic<'_>)] {
&[
takes_nonstatic,
also_takes_nonstatic,
]
}
}
fn main() {
let nums = [0, 1];
let nonstatics = [
NonStatic(&nums[0]),
NonStatic(&nums[1]),
];
let funcs = Struct::functions();
for nonstatic in &nonstatics {
for func in funcs {
func(nonstatic);
}
}
}
error[E0597]: `nums[_]` does not live long enough
--> src/main.rs:24:19
|
24 | NonStatic(&nums[0]),
| ^^^^^^^^ borrowed value does not live long enough
...
28 | for nonstatic in &nonstatics {
| ----------- argument requires that `nums[_]` is borrowed for `'static`
...
33 | }
| - `nums[_]` dropped here while still borrowed
error[E0597]: `nums[_]` does not live long enough
--> src/main.rs:25:19
|
25 | NonStatic(&nums[1]),
| ^^^^^^^^ borrowed value does not live long enough
...
28 | for nonstatic in &nonstatics {
| ----------- argument requires that `nums[_]` is borrowed for `'static`
...
33 | }
| - `nums[_]` dropped here while still borrowed
Why does a function pointer which can take a non-'static argument also become non-'static?
What I am saying is rather on the side of false positives, i.e., of Rust being overly conservative: if type T = &'lifetime U is a usable type (e.g., can be used as the return value of a function), then it is not unsound for T (and U) to verify that each is : 'lifetime. And yet they don't happen to be viewed as such per Rust "constraint solver": that's what I meant by "they are not : 'static".