I recently came across a case where I needed to use a raw function pointer like C in Rust. There were quite a few potholes, so I’ll document them here.

We know that C can refer to functions via function Pointers, such as:

int foo(a)
{
    return 42;
}

int main(a)
{
    int (*p_fn)() = &foo;
    printf("%d\n", (*p_fn)());
    // Output: 42
}
Copy the code

But in Rust, the same idea doesn’t work, such as:

fn foo() - >i32 { 42 }

fn main() {
    let p_fn = &foo as *const fn() - >i32;
    unsafe {
        dbg!((*p_fn)());
    }
}
Copy the code

Produces an error like this:

error[E0606]: casting `&fn() -> i32 {foo}` as `*const fn() -> i32` is invalid --> src/main.rs:4:16 | 4 | let p_fn = &foo as *const fn() -> i32; | ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^Copy the code

It seems Rust doesn’t want us to convert function references to function Pointers. What if we get rid of the &?

fn foo() - >i32 { 42 }

fn main() {
    let p_fn = foo as *const fn() - >i32;
    unsafe {
        dbg!((*p_fn)());
    }
}
Copy the code

At this point, the program is ready to compile, but running the program produces a segment error. Why is that?

In fact, the first Rust code above does not express the same meaning as the C language. This is because in Rust, a type like fn() -> i32 is actually a function pointer rather than a function. Mentally? Look at this:

// foo is a * function *, fn() -> i32 is used as a * function definition *
fn foo() - >i32 { 42 }

fn main() {
    P_fn is a * function pointer *, fn() -> i32 is used as a * type *
    let p_fn: fn() - >i32 = foo;
}
Copy the code

That is, when fn() -> i32 is a variable type, the variable will be a function pointer, not a function. In Rust, this type actually has reference-like properties, such as we can convert it to a bare pointer, as we did in the second Rust code:

    // This can be compiled
    let p_fn = foo as *const fn() - >i32;
Copy the code

However, such a conversion is problematic. This is because, since fn() -> i32 is already a function pointer, *const fn() -> i32 should be a pointer to a function pointer, not just a function pointer. Therefore, if we try to plug in a function pointer, we will have the segment error we talked about earlier.

As the argument goes, when we want a bare pointer in Rust, we should not use *const fn() -> i32. We should look elsewhere. An easy way to do this is to use the *const () type:

    // The following code is semantically ambiguous, but feasible
    let p_fn = foo as *const(a);Copy the code

However, if we try to convert it back, another error occurs:

    let p_fn = foo as *const(a);let fn_ref = p_fn as fn() - >i32;
Copy the code

The compile error message is:

error[E0605]: non-primitive cast: `*const ()` as `fn() -> i32`
 --> src/main.rs:5:18
  |
5 |     let fn_ref = p_fn as fn() -> i32;
  |                  ^^^^^^^^^^^^^^^^^^^ invalid cast
Copy the code

This error occurs because fn() -> i32 is not a primitive type and only primitive types can be converted to each other via the AS keyword.

In other words, the transform is actually unsafe, as a function pointer of type fn() -> i32 can be called directly in Safe Rust, but there is no guarantee that our *const () will be a valid function. This step is the equivalent of converting a bare pointer to a reference, and corresponds to the dereference in the Unsafe Rust, not just a simple type conversion. So Rust naturally does not allow this transformation directly through the AS keyword.

In this sense, the rust-like solution is to add an unaddressed from_unchecked() method to the fn() -> i32 type, similar to Box::from_raw, An object of type FN () -> i32 can be constructed directly from a bare pointer. Unfortunately, the standard Rust does not provide such a function, so this approach is also unworkable.

While as and from_unchecked() won’t work, we’ve got one last big kill: STD ::mem:: Transmute (). In short, transmute is an unsafe function for type conversions, which translates an A-type variable from TYPE A to type B by treating the underlying data directly as data from another B-type variable. A bit around? Look directly at examples:

fn main() {
    let pi = std::f32::consts::PI;
    // Treat the underlying data of the floating point number PI as an integer directly
    let pi_as_u32: u32 = unsafe { std::mem::transmute(pi) };
    // Prints out the underlying data of the floating point number PI
    println!("{:x}", pi_as_u32);
    // Output: 40490fdb
}
Copy the code

To write the above example in C language:

int main(a)
{
    float pi = acosf(- 1);
    unsigned pi_as_u32 = *(unsigned *) π
    printf("%x\n", pi_as_u32);
    // Output: 40490fdb
}
Copy the code

Just as the above example extracts the underlying representation of a floating point number, since fn() -> i32’s underlying representation is actually a function address, we can “transform” a function address directly into a function pointer by transmute. It’s not pretty and the logic is complex, but it’s semantically correct, and it’s the only “correct” way to do it in standard Rust.

fn foo() - >i32 { 42 }

fn main() {
    let p_fn = foo as *const(a);let fn_ref: fn() - >i32 = unsafe{ std::mem::transmute(p_fn) }; dbg! (fn_ref());[SRC /main.rs:6] fn_ref() = 42
}
Copy the code

In fact, transmute’s documentation states that one of the uses of this function is a constructor pointer, such as:

fn foo() - >i32 {
    0
}
let pointer = foo as *const(a);let function = unsafe {
    std::mem::transmute::<*const (), fn() - >i32>(pointer)
};
assert_eq!(function(), 0);
Copy the code

However, the documentation also points out that this method is not actually cross-platform, because on some platforms the length of function Pointers is different from that of normal Pointers. In this case, the above code will fail to compile. For such platforms, it is best to encapsulate the function pointer in a structure or Box, as in:

#[repr(transparent)]
#[derive(Copy, Clone)]
struct CallbackFn(fn() - >i32);

fn main() {
    // Structure with function pointer
    let fn_a = CallbackFn(foo);
    // Box with function pointer
    let fn_b: Box<fn() - >i32> = Box::new(foo);
    // Convert Box to a bare pointer
    let fn_c: *const fn() - >i32 = fn_b.as_ref() as *const fn() - >i32;
}
Copy the code