2

Background: I'm trying to avoid the use of Mutex/RefCell/Option dance in an interrupt handler for an embedded system. I do not want to use the heap (and I don't think it should be necessary -- but feel free to show me wrong). I can't use std. I've looked at cortex-m-rtfm and it's neat, but pretty invasive. And anyway, this is a bit of a learning exercise. If it works out, I would prefer to use closures to handle interrupts since it feels closer to bare Rust. I am a total Rust newbie -- I've been working with it for about a week. I have tried a lot of different variants of this as I've read through documentation, re-read the Rust book, blog posts, etc.,. I cannot figure what I'm doing wrong here.

Here's the sample code. Questions to follow:

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()`

Explanation: Hopefully, my intentions are obvious: I'll set up the closure for HANDLER in main, before going into a busy-loop that never exits. The closure will mutably borrow the stuff the interrupt handler needs for its operation, preventing its use in other contexts. Since main never exits, stack-allocated variables within it are effectively 'static, so there shouldn't be a problem with referencing them at any point after the closure is set. The interrupt handler itself (not shown) will simply call the closure to do its work. To work around the storage of a closure (which is not Sized) in a static, I need to store a reference to the closure. UnsafeCell isn't necessarily required, but since I'm using FnMut() its referents need to be mutable, which runs into statics require immutable values when trying to set up default_handler during the creation of a static mut HANDLER.

Questions:

  1. As posted, this code doesn't compile. For some reason, the assignment let h: *mut FnMut() = self.h.get() tells me that it expected an Fn<()> closure, found (dyn std::ops::FnMut() + 'a). Well, I know why it found that type. But why is it expecting Fn<()>?

  2. In the call_handler test, why is this compiling at all? The foo closure moves its captured variable a. How is it possible to mutate it after the closure definition? When I've tried this code with a type that doesn't implement Copy, it fails as expected, but I'm frankly surprised that trait matters. Isn't the point that foo owns a now?

I realize there are potential issues with changing HANDLER.h at arbitrary points in the code, but I will worry about solving those later, after there's a viable proof-of-concept.

Brian Cully
  • 526
  • 2
  • 11
  • To answer question 2: You're right that Copy matters. Types that are Copy are always copied instead of moved. So `foo` now owns a _copy_ of `a` even though `a` was "moved". – turbulencetoo Apr 23 '19 at 20:51
  • Please include the full error message in your question. Error messages are always relevant, and often include valuable details, and this case is no exception. The impl mentioned in the error message [can be found here](https://doc.rust-lang.org/std/ops/trait.FnMut.html#impl-FnMut%3CA%3E). (I don't have time for a full answer, but the link should give a clue why the compiler is expecting `Fn()`.) – Sven Marnach Apr 23 '19 at 21:31
  • I've added the compiler errors. – Brian Cully Apr 23 '19 at 22:14
  • @Brian as I said before, the impl mentioned in the error message is linked above, and you can see that it requires the referent to be `Fn`, which `dyn FnMut` isn't, so no surprises there. Objects of type `&'a dyn FnMut()` can't be called without triggering undefined behaviour, so there is no easy fix for your code. – Sven Marnach Apr 24 '19 at 19:01
  • What I don't understand is *why* `Fn<()>` is expected: the type of variable `h` is explicitly `*mut FnMut()`. Where is the compiler pulling `Fn<()>` from? Additionally, isn't any reference to an `FnMut()` always `&'a dyn FnMut()`? i.e., a reference to an object implementing FnMut with an arbitrary lifetime of `a`? How is calling that undefined? – Brian Cully Apr 25 '19 at 01:05
  • You declared `h` to be `*mut FnMut()`, but the right-hand side expression is of type `*mut &dyn FnMut()`. A trait object reference like `&dyn MyTrait` does not implement the trait `MyTrait` by default. You need and explicit impl for this, and such an impl is linked in my comment above. The impl requires that the referent is `Fn()`, since the `where` clause says so. – Sven Marnach Apr 25 '19 at 18:04
  • I must be missing something basic here. I don't get why `&dyn MyTrait` wouldn't implement `MyTrait`. Isn't that rather the point of trait objects? How is it possible to call `write_str` on something you got in your argument list as `&dyn core::fmt::Write`? I did manage to figure out why `Fn()` was expected: I let off a `mut` qualifier on the type signature for `h`. Adding that in turned it into the expected `FnMut()`, but left me with needing to assign a mutable in a static. – Brian Cully Apr 26 '19 at 18:44
  • @BrianCully The trait object reference type `&dyn MyTrait` does not implement `MyTrait`, [as you can see by building this playground example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a7c09ff4693d99d705ba865997c1b633). You still can call methods of the trait taking `self` by reference on trait objects due to [Rust's method call semantics](https://stackoverflow.com/a/28552082), but this does not mean the reference type itself implements the trait. – Sven Marnach Apr 26 '19 at 20:24
  • I don't have time to think deeply about this, but I still can't see an easy way of fixing the code without introducing undefined behaviour. Maybe you are better off using `Arc>`? – Sven Marnach Apr 26 '19 at 20:28
  • Thanks for your help so far, I appreciate it. Since the purpose of this exercise is to call a closure through an interrupt handler called potentially many millions of times per second, I was hoping to avoid as much run time overhead as possible and guarantee the exclusivity of the reference in other ways, and the indirection of calling the closure will probably be bad enough. Your comment about `self` being useful on trait objects did get me thinking: would it be possible to do something like `(*h).call_mut()`, invoking the behavior via `self`? – Brian Cully Apr 27 '19 at 01:12
  • @BrianCully You need to @mention me for me to get notifications of your replies. Otherwise I only see your comments if I remember to come back to this question. I don't fully understand the question in your last comment. For what it's worth, if you want to call the function from an interrupt handler, I believe your are better off using a plain function pointer rather than an `FnMut` trait object. You can still access mutable statics from inside the handler if this is what you need to do, but you will have to deal with that inside the handler. – Sven Marnach Apr 29 '19 at 07:23

1 Answers1

2

I have found a way to do what I want. It is grossly unsafe for general use, and appropriate mechanisms to hide its lack of safety have to be investigated, and may not even be possible. The main trick is to convert the mutable trait object into a dynamic one using an as cast, and using core::mem::transmute to change its lifetime to static. Here's the code:

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
}

The closure wrapped by Handler.h is inside an UnsafeCell to facilitate its replacement at runtime (inside, and only inside, the main loop).

Brian Cully
  • 526
  • 2
  • 11