【问题标题】:Using FnMut() closures in a static function在静态函数中使用 FnMut() 闭包
【发布时间】:2019-04-23 20:32:26
【问题描述】:

背景:我试图避免在嵌入式系统的中断处理程序中使用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[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()`

解释:希望我的意图很明显:我将在main 中为HANDLER 设置闭包,然后进入一个永不退出的繁忙循环。闭包会可变地借用中断处理程序操作所需的东西,阻止它在其他上下文中的使用。由于main 永远不会退出,因此其中的堆栈分配变量实际上是'static,因此在设置闭包后的任何时候引用它们都不会有问题。中断处理程序本身(未显示)将简单地调用闭包来完成它的工作。为了解决在静态中存储闭包(不是Sized)的问题,我需要存储对闭包的引用。 UnsafeCell 不一定是必需的,但由于我使用的是FnMut(),因此它的引用对象需要是可变的,在创建static mut HANDLER 期间尝试设置default_handler 时会遇到statics require immutable values

问题:

  1. 正如发布的那样,此代码无法编译。出于某种原因, 分配let h: *mut FnMut() = self.h.get() 告诉我它 expected an Fn<()> closure, found (dyn std::ops::FnMut() + 'a)。好吧,我知道它为什么会找到那种类型。但是为什么它期待Fn&lt;()&gt;

  2. call_handler 测试中,为什么要编译? foo 闭包moves 是其捕获的变量a。在闭包定义之后如何改变它?当我使用未实现 Copy 的类型尝试此代码时,它按预期失败,但坦率地说,我很惊讶 trait 很重要。现在foo拥有a的意义不就在于吗?

我意识到在代码中的任意位置更改 HANDLER.h 可能会出现问题,但我会担心在有可行的概念验证之后解决这些问题。

【问题讨论】:

  • 回答问题 2:您说得对,复制很重要。复制的类型总是被复制而不是移动。所以foo 现在拥有a副本,即使a 被“移动”了。
  • 请在您的问题中包含完整的错误消息。错误消息总是相关的,并且通常包含有价值的细节,这种情况也不例外。错误消息can be found here 中提到的impl。 (我没有时间给出完整的答案,但该链接应该提供一个线索,为什么编译器期待 Fn()。)
  • 我添加了编译器错误。
  • @Brian 正如我之前所说,错误消息中提到的 impl 链接在上面,您可以看到它要求引用对象是 Fn,而 dyn FnMut 不是,所以那里没有惊喜。 &amp;'a dyn FnMut() 类型的对象不能在不触发未定义行为的情况下被调用,因此您的代码没有简单的修复方法。
  • 我不明白的是为什么 Fn&lt;()&gt; 是预期的:变量h 的类型明确为*mut FnMut()。编译器从哪里拉出Fn&lt;()&gt;?此外,不是对FnMut() 的任何引用总是&amp;'a dyn FnMut() 吗?即,对实现 FnMut 且任意生命周期为 a 的对象的引用?如何调用未定义?

标签: rust embedded


【解决方案1】:

我找到了一种方法来做我想做的事。它对于一般使用来说是非常不安全的,必须调查隐藏其缺乏安全性的适当机制,甚至可能是不可能的。主要技巧是使用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 内,以便在运行时替换它(在主循环内部,并且仅在主循环内部)。

【讨论】:

    猜你喜欢
    • 2015-05-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-12
    • 1970-01-01
    • 2014-12-28
    • 1970-01-01
    相关资源
    最近更新 更多