Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does function pointer behaviour differ in Rust depending on the mutability of the function pointer?

Tags:

function

rust

ffi

When storing raw pointers to functions in structs in Rust, the behaviour of the program can change in unexpected ways depending on the mutability of the raw pointer.

Using const pointers gives the expected result.

The following code can also be viewed on the playground:

type ExternFn = unsafe extern "C" fn() -> ();

unsafe extern "C" fn test_fn() {
    println!("Hello!");
}

mod mut_ptr {
    use super::{ExternFn, test_fn};

    #[derive(Debug, Eq, PartialEq)]
    pub struct FunctionHolder {
        function: *mut ExternFn,
    }

    impl FunctionHolder {
        pub fn new() -> Self {
            FunctionHolder {
                function: (&mut (test_fn as ExternFn) as *mut _),
            }
        }

        pub fn call(&self) {
            if !self.function.is_null() {
                unsafe { (&*self.function)(); }
            }
        }
    }
}

mod const_ptr {
    use super::{ExternFn, test_fn};
    #[derive(Debug, Eq, PartialEq)]
    pub struct FunctionHolder {
        function: *const ExternFn,
    }

    impl FunctionHolder {
        pub fn new() -> Self {
            FunctionHolder {
                function: (&(test_fn as ExternFn) as *const _),
            }
        }

        pub fn call(&self) {
            if !self.function.is_null() {
                unsafe { (&*self.function)(); }
            }
        }
    }
}

// use const_ptr::FunctionHolder;
use mut_ptr::FunctionHolder;

fn check_holder(holder: &FunctionHolder) -> bool {
    let good = FunctionHolder::new();
    println!("parameter = {:#?}", holder);
    println!("expected = {:#?}", good);
    holder == &good
}

fn main() {
    let f0 = FunctionHolder::new();
    println!("{:?}", f0);

    let f1 = FunctionHolder::new();
    println!("{:?}", f1);

    // uncomment this line to cause a segfault if using the
    // mut_ptr version :-(
    // f1.call(); 

    assert!(check_holder(&f1));
}

In the const_ptr module, the code behaves as expected: The pointer value stored in the FunctionHolder struct is the same regardless of where the function is called, and using the FunctionHolder::call method calls the function as required.

In the mut_ptr module, there are some unexpected differences:

  • The FunctionHolder::new method returns a struct holding a different value depending on the function in which it is called,

  • The FunctionHolder::call method causes a segfault.

like image 480
burtonageo Avatar asked Apr 22 '19 20:04

burtonageo


People also ask

Does Rust have function pointers?

In Rust, a function pointer type, is either fn(Args...) -> Ret , extern "ABI" fn(Args...)

What is pointer in Rust?

A pointer is a general concept for a variable that contains an address in memory. This address refers to, or “points at,” some other data. The most common kind of pointer in Rust is a reference, which you learned about in Chapter 4. References are indicated by the & symbol and borrow the value they point to.

What are function pointers good for?

Function pointers can be useful when you want to create callback mechanism, and need to pass address of a function to another function. They can also be useful when you want to store an array of functions, to call dynamically for example.

How do you get a pointer of a variable in Rust?

You need to use the & operator to get the address of any variable, so you need to write &my_struct as *const _ (where _ can be a literal _ , or the type of the value behind the pointer).


1 Answers

fn() -> () is a function pointer. *const fn() -> () and *mut fn() -> () are function pointer pointers.

You want to use much simpler code, which also means there's no difference between the two implementations:

#[derive(Debug, Eq, PartialEq)]
pub struct FunctionHolder {
    function: Option<ExternFn>,
}

impl FunctionHolder {
    pub fn new() -> Self {
        FunctionHolder {
            function: Some(test_fn as ExternFn),
        }
    }

    pub fn call(&self) {
        if let Some(f) = self.function {
            unsafe { f(); }
        }
    }
}

As mentioned in the comments, taking a mutable reference to a literal value constructs a new value each time:

fn main() {
    println!("{:p}", &42);
    println!("{:p}", &42);
    println!("{:p}", &42);

    println!("{:p}", &mut 42);
    println!("{:p}", &mut 42);
    println!("{:p}", &mut 42);
}
0x55a551c03a34
0x55a551c03a34
0x55a551c03a34
0x7ffd40dbb95c
0x7ffd40dbb9bc
0x7ffd40dbba1c

Immutable references to literals have implicit static promotion:

let a = &42;
// More-or-less
static HIDDEN: i32 = 42;
let a = &HIDDEN;

Mutable references to literals desugar to effectively:

let mut hidden: i32 = 42;
let a = &mut hidden;

By using raw pointers, you lose the support of the borrow checker to point out that your references don't live long enough for the mutable case.

See also:

  • Why is it legal to borrow a temporary?
  • Why can I return a reference to a local literal but not a variable?
  • What are the semantics of mutably borrowing a literal in Rust?
like image 117
Shepmaster Avatar answered Oct 04 '22 10:10

Shepmaster