14

I am writing an OS in Rust and need to directly call into a virtual address that I'm calculating (of type u32). I expected this to be relatively simple:

let code = virtual_address as (extern "C" fn ());
(code)();

However, this complains that the cast is non-primitive. It suggests I use the From trait, but I don't see how this could help (although I am relatively new to Rust and so could be missing something).

error[E0605]: non-primitive cast: `u32` as `extern "C" fn()`
 --> src/main.rs:3:16
  |
3 |     let code = virtual_address as (extern "C" fn ());
  |                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait

I have everything in libcore at my disposal, but haven't ported std and so can't rely on anything that isn't no_std

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Isaac Woods
  • 1,114
  • 1
  • 17
  • 28

1 Answers1

22

Casts of the type _ as f-ptr are not allowed (see the Rustonomicon chapter on casts). So, as far as I can tell, the only way to cast to function pointer types is to use the all mighty weapon mem::transmute().

But before we can use transmute(), we have to bring our input into the right memory layout. We do this by casting to *const () (a void pointer). Afterwards we can use transmute() to get what we want:

let ptr = virtual_address as *const ();
let code: extern "C" fn() = unsafe { std::mem::transmute(ptr) };
(code)();

If you find yourself doing this frequently, various kinds of macros can remove the boilerplate. One possibility:

macro_rules! example {
    ($address:expr, $t:ty) => {
        std::mem::transmute::<*const (), $t>($address as _)
    };
}
let f = unsafe { example!(virtual_address, extern "C" fn()) };
f(); 

However, a few notes on this:

  • If you, future reader, want to use this to do simple FFI things: please take a moment to think about it again. Calculating function pointers yourself is rarely necessary.
  • Usually extern "C" functions have the type unsafe extern "C" fn(). This means that those functions are unsafe to call. You should probably add the unsafe to your function.
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
  • Please note that I'm absolutely not an expert on unsafe-things! If someone knows better, please let me know; then I can edit or delete my answer! – Lukas Kalbertodt Sep 09 '17 at 20:18
  • Rust doesn't do anything fancy here. `fn()` is a plain function pointer. As long as there's a function at `virtual_address`, calling conventions and signatures match, it's OK to do the cast and call the function. – red75prime Sep 09 '17 at 21:14
  • Awesome, I'll need to do some more setup to see if this actually works, but it does compile! Thanks very much @LukasKalbertodt. – Isaac Woods Sep 09 '17 at 22:23
  • @red75prime The code itself isn't fancy, but does rely on being passed a valid virtual address in my case (with numerous things to think about: physical -> virtual calculation, actually mapping the code etc.) so I think it does fit as an `unsafe fn` in Rust's paranoia (in a good sense of the word) – Isaac Woods Sep 09 '17 at 22:25
  • can we tell Rust which calling convention such an inline-casted function has? – Benni May 30 '23 at 18:53