Использование замыканий FnMut () в статической функции - PullRequest
2 голосов
/ 23 апреля 2019

Справочная информация: Я пытаюсь избежать использования Mutex / RefCell / Option dance в обработчике прерываний для встроенной системы. Я не хочу использовать кучу (и я не думаю, что это необходимо, но не стесняйтесь показывать мне неправильно). Я не могу использовать std. Я посмотрел на cortex-m-rtfm, и это аккуратно, но довольно агрессивно. И вообще, это немного учебное упражнение. Если это сработает, я бы предпочел использовать замыкания для обработки прерываний, так как он кажется ближе к голой Rust. Я новичок в Rust - я работаю с ним около недели. Я попробовал много различных вариантов этого, поскольку я прочитал документацию, перечитал книгу Rust, сообщения в блоге и т. Д.,. Я не могу понять, что я делаю здесь неправильно.

Вот пример кода. Вопросы для следования:

use core::cell::UnsafeCell;

pub struct Handler<'a> {
    h: UnsafeCell<&'a dyn FnMut()>,
}

impl<'a> Handler<'a> {
    pub fn new<T: FnMut()>(closure: &'a dyn FnMut()) -> Self {
        Handler {
            h: UnsafeCell::new(closure),
        }
    }

    pub fn call(&self) {
        unsafe {
            // NOTE: type returned by `self.h.get()` is
            // `*mut &'a (dyn std::ops::FnMut() + 'a)`
            let h: *mut FnMut() = self.h.get();
            h();
        }
    }
}
unsafe impl<'a> Sync for Handler<'a> {}

fn default_handler() {}

static HANDLER: Handler = Handler {
    h: UnsafeCell::new(&default_handler),
};

#[test]
fn call_handler() {
    let mut a: u32 = 0;
    let foo = move || a += 1;
    let mut handler = Handler::new(&foo);
    handler.call();
    a += 2; // Shouldn't this cause compilation failure because `a`
            // was moved into the closure above?
    assert_eq!(a, 1);
}

Error

error[E0618]: expected function, found `*mut dyn std::ops::FnMut()`
  --> src/lib.rs:19:13
   |
18 |             let h: *mut FnMut() = self.h.get();
   |                 - `*mut dyn std::ops::FnMut()` defined here
19 |             h();
   |             ^--
   |             |
   |             call expression requires function

error[E0277]: expected a `std::ops::Fn<()>` closure, found `(dyn std::ops::FnMut() + 'a)`
  --> src/lib.rs:18:35
   |
18 |             let h: *mut FnMut() = self.h.get();
   |                                   ^^^^^^^^^^^^ expected an `Fn<()>` closure, found `(dyn std::ops::FnMut() + 'a)`
   |
   = help: the trait `std::ops::Fn<()>` is not implemented for `(dyn std::ops::FnMut() + 'a)`
   = note: wrap the `(dyn std::ops::FnMut() + 'a)` in a closure with no arguments: `|| { /* code */ }
   = note: required because of the requirements on the impl of `std::ops::FnMut<()>` for `&'a (dyn std::ops::FnMut() + 'a)`
   = note: required for the cast to the object type `dyn std::ops::FnMut()`

Объяснение: Надеюсь, мои намерения очевидны: я настрою замыкание на HANDLER в main, прежде чем войти в цикл занятости, который никогда не завершается. Замыкание непостоянно заимствует материал, необходимый обработчику прерывания для своей работы, предотвращая его использование в других контекстах. Поскольку main никогда не завершается, выделенные в стеке переменные внутри него фактически равны 'static, поэтому не должно быть проблем со ссылками на них в любой точке после установки замыкания. Сам обработчик прерываний (не показан) просто вызывает замыкание для выполнения своей работы. Чтобы обойти хранение замыкания (которое не Sized) в статическом состоянии, мне нужно сохранить ссылку на замыкание. UnsafeCell не обязательно требуется, но поскольку я использую FnMut(), его референты должны быть изменяемыми, что приводит к statics require immutable values при попытке установить default_handler во время создания static mut HANDLER.

Вопросы:

  1. После публикации этот код не компилируется. По какой-то причине присвоение let h: *mut FnMut() = self.h.get() говорит мне, что это expected an Fn<()> closure, found (dyn std::ops::FnMut() + 'a). Ну, я знаю, почему он нашел этот тип. Но почему он ожидал Fn<()>?

  2. В тесте call_handler, почему эта компиляция вообще? foo замыкание move s его захваченная переменная a. Как можно изменить его после определения замыкания? Когда я попробовал этот код с типом, который не реализует Copy, он терпит неудачу, как и ожидалось, но я откровенно удивлен, что эта черта имеет значение. Разве это не то, что foo сейчас владеет a?

Я понимаю, что есть потенциальные проблемы с изменением HANDLER.h в произвольных точках в коде, но я буду беспокоиться о том, чтобы решить их позже, после того, как появится жизнеспособное подтверждение концепции.

1 Ответ

2 голосов
/ 28 апреля 2019

Я нашел способ сделать то, что я хочу.Это крайне небезопасно для общего пользования, и необходимо изучить соответствующие механизмы, чтобы скрыть его недостаточную безопасность, и это может быть даже невозможно.Основная хитрость заключается в том, чтобы преобразовать изменяемый объект черты в динамический объект, используя приведение as и использование core::mem::transmute, чтобы изменить его время жизни на static.Вот код:

use core::cell::UnsafeCell;
use core::mem::transmute;

struct Handler {
    h: UnsafeCell<*const dyn FnMut()>,
}

impl Handler {
    unsafe fn replace(&self, f: &dyn FnMut()) {
        let f_static: &'static dyn FnMut() = transmute(f);
        *self.h.get() = f_static;
    }

    unsafe fn call(&self) {
        let f: &mut dyn FnMut() = &mut *(*self.h.get() as *mut dyn FnMut());
        f();
    }
}
unsafe impl Sync for Handler {}

fn default_handler() {}
static HANDLER: Handler = Handler {
    h: UnsafeCell::new(&default_handler),
};

fn main() {
    let mut x: u32 = 0;
    let closure = || x += 2;

    unsafe {
        HANDLER.replace(&closure);
        HANDLER.call();
    };
    println!("x: {}", x); // Prints 2
}

Закрытие, заключенное в Handler.h, находится внутри UnsafeCell, чтобы облегчить его замену во время выполнения (внутри и только внутри основного цикла).

...