为什么Rust中的函数指针行为会有所不同,具体取决于函数指针的可变性?

问题描述 投票:4回答:1

当将原始指针存储到Rust中的结构中的函数时,程序的行为可能会以意外的方式改变,具体取决于原始指针的可变性。

使用const指针给出了预期的结果。

以下代码也可以在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));
}

const_ptr模块中,代码按预期运行:无论调用函数的位置如何,存储在FunctionHolder结构中的指针值都是相同的,并且使用FunctionHolder::call方法根据需要调用函数。

mut_ptr模块中,存在一些意想不到的差异:

  • FunctionHolder::new方法返回一个结构,该结构具有不同的值,具体取决于调用它的函数,
  • FunctionHolder::call方法导致段错误。
function rust ffi
1个回答
5
投票

fn() -> ()是一个函数指针。 *const fn() -> ()*mut fn() -> ()是函数指针指针。

您希望使用更简单的代码,这也意味着两个实现之间没有区别:

#[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(); }
        }
    }
}

正如评论中所提到的,对文字值进行可变引用每次构造一个新值:

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

对文字的不可变引用具有隐含的static促销:

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

对文字的可变引用有效地说:

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

通过使用原始指针,您将失去借用检查器的支持,以指出您的引用对于可变案例而言不够长。

也可以看看:

© www.soinside.com 2019 - 2024. All rights reserved.