Почему поведение указателя функции в Rust отличается в зависимости от изменчивости указателя функции? - PullRequest
4 голосов
/ 22 апреля 2019

При хранении необработанных указателей на функции в структурах в Rust поведение программы может неожиданно изменяться в зависимости от изменчивости необработанного указателя.

Использование const указателей дает ожидаемый результат.

Следующий код также можно просмотреть на детской площадке :

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 struct одинакова независимо от того, где вызывается функция, и использование метода FunctionHolder::call вызывает функцию по мере необходимости.

В модуле mut_ptr есть некоторые неожиданные различия:

  • Метод FunctionHolder::new возвращает структуру, содержащую различное значение в зависимости от функции, в которой он вызывается,

  • Метод FunctionHolder::call вызываетsegfault.

1 Ответ

5 голосов
/ 23 апреля 2019

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;

Изменяемые ссылки на литералы desugar для эффективного:

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

Используя необработанные указатели, вы теряете поддержку средства проверки заимствования, чтобы указать, что ваши ссылки не живут достаточно долго для изменяемого случая.

Смотри также:

...