Function Pointer cast
Function Pointer cast
Posted Jan 30, 2024 13:25 UTC (Tue) by tialaramex (subscriber, #21167)In reply to: Function Pointer cast by khim
Parent article: Defining the Rust 2024 edition
You can have a *mut T instead of a *const T if you want, they both exist in this API, so that's nice. But you can't safely make a function pointer this way AFAIK.
Also, you definitely can't have an actual function. Unlike C or C++, Rust's functions have unique unnameable zero size types, more similar to a C++ lambda. So you definitely can't make those, they're figments of the compiler's imagination.
let mut why_not = core::mem::needs_drop::<u8>; // Sure. Value of a Zero Size Type representing the abstract idea of this predicate
why_not = core::mem::needs_drop::<i8>; // Won't compile, they're both predicates with identical signatures and the exact same implementation but in theory they're different functions, thus it's a type mismatch.
... You can make function pointers though, which are the thing you'd get in C or C++ when you try to put the function in a variable. You can undoubtedly make a function pointer from an integer and have it work. But no, you probably cannot convince Miri that's OK.
Posted Jan 30, 2024 13:50 UTC (Tue)
by khim (subscriber, #9252)
[Link] (3 responses)
No, they are not. You may write something like this:
How may I would consider it a defect in Miri and a pretty serious one. While Miri is not supposed to handle all possible Rust programs there are way to many APIs that assume that you may create function pointers from integers. But yeah, ultimately Miri is just a model of Rust, not the full Rust.
Posted Jan 30, 2024 15:52 UTC (Tue)
by tialaramex (subscriber, #21167)
[Link] (2 responses)
Posted Jan 30, 2024 16:59 UTC (Tue)
by khim (subscriber, #9252)
[Link] (1 responses)
But isn't the fact that you now have
Posted Jan 30, 2024 18:18 UTC (Tue)
by tialaramex (subscriber, #21167)
[Link]
But in my response add_or_sub is now a function item, a Zero Size variable which is always (in this particular case) our add function. ZSTs have a singular value, which is why we don't need storage space for them, the value of the (now mis-named) add_or_sub is always just add, a specific function we wrote to add numbers together. Even another function with not just the same signature, but exactly the same body and resulting machine code is a different function and so cannot be assigned to the (again, now misnamed) add_or_sub variable.
Transmute is a red herring, transmuting things into a ZST is silly. If you can *spell* the ZST then you're welcome to have one, since it has only a single value the compiler knows from the type what its value is, we don't need to "transmute" it. However like a lambda type these function items are unnameable, so you can't do that.
Function pointers are very nameable, and I'd guess you've been thinking about function pointers all along, but I wanted to emphasise that Rust does have (and make good use of) types signifying specifically the function, not just a pointer.
Yes I imagine you can take a magic integer (with some well chosen value) and transmute it into a function pointer, and (on a typical modern computer) you could call the function via that pointer and it'd work. I am not surprised that MIRI cannot justify this, and I think there aren't a lot cases where anybody has a good reason to do it, so if "Miri doesn't like it" dissuades someone from doing this that's probably good.
> So you definitely can't make those, they're figments of the compiler's imagination.
Function Pointer cast
fn main() {
let add_or_sub = if std::env::args().len() % 2 == 0 { add } else { sub };
println!("{}", add_or_sub(42, 2));
}
fn add(x: i32, y: i32) -> i32 {
x + y
}
fn sub(x: i32, y: i32) -> i32 {
x - y
}
add_or_sub
variable even exist if function pointers are just a figments of the compiler's imagination?
What you've got there is just a function pointer, of type fn(i32, i32) -> i32
Although Rust doesn't have the uh, exciting variety of integer coercions of C, it does have coercions, and in particular what your snippet does is coerce the two ZSTs (which are distinct types) into merely function pointers (which are compatible if the functions have the same signature).
Watch what happens if we modify your code a little:
Function Pointer cast
fn main() {
let mut add_or_sub = add;
add_or_sub = if std::env::args().len() % 2 == 0 { add } else { sub };
println!("{}", add_or_sub(42, 2));
}
error[E0308]: mismatched types
--> src/main.rs:3:68
|
3 | add_or_sub = if std::env::args().len() % 2 == 0 { add } else { sub };
| ^^^ expected fn item, found a different fn item
|
= note: expected fn item `fn(_, _) -> _ {add}`
found fn item `fn(_, _) -> _ {sub}`
= note: different fn items have unique types, even if their signatures are the same
= help: consider casting both fn items to fn pointers using `as fn(i32, i32) -> i32`
Function Pointer cast
add_or_sub
which is not “a mere function pointer” means that you actually can make these (with transmute
) e.g.?Function Pointer cast