Rust devs that are familar with interior mutability may want to skip to the case study chapter.

Rust is a relatively new systems programming language providing memory safety, fearless-concurrency and high performance. These, perhaps seemingly orthogonal, problems are solved with a strong static type system, meaning that memory safety and no-data-races are guaranteed in compile time. Though memory safety and fearless concurrency are important selling points for rust they are by no means the only static guarantees that can be provided. As long as one can represent a constraint in the Rust type system, the compiler will enforce it. A wonderful consequence is that for many bugs only detectable in run-time for many languages, Rust will fail at compile-time. Also, bugs only detectable by testing in many languages might be detected without writing tests in Rust.

Interior mutability

One of the rules Rust must follow in order to keep its guarantee of no undefined behaviour in safe code is making sure that if a &mut T exists, other references, mutable or immutable, must not exist. This rule is ordinarily enforced at compile-time, allowing rust to be both blazing fast and safe at the same time. We will refer to this as satisfying the scoped noalias model

The problem occurs when you really need to mutate the same memory from different places. For instance, when multiple modules need to write to a common buffer. The provided solution for this is using a wrapper type that provides interior mutability. Interior mutability (in the safe sense) means that instead of making sure the scoped noalias model is satisifed at compile-time, one does so at run-time. When implementing the interior mutable wrapper, use of unsafe is required, but by exposing a safe API the author has promised the borrow checker that everything will be all right.

Lucky for us, the Rust stdlib has implemented several convenient interior mutable wrappers. This gives application developers the possibility of using interior mutability without touching unsafe code. Even though the most basic interior mutability wrappers are only concerned with satisfying the scoped noalias model, the Mutex is an interior mutable wrapper which can also help solve concurrency related problems. What the rust stdlib and corelib has failed to do is creating good abstractions over interior mutability. The rest of this post will explain why such abstractions are useful for no_std libraries and applications.

Case Study: The TMCL interface

Detection of errors without writing tests or even doing ad-hoc testing sounds nice. When one is working with hardware that might be both expensive and time consuming to test on, it is more than nice. That was one of the reasons it was decided to use Rust in the control system for the UAV docking station prototype created at my previous workplace. The main features of the docking station was being able to move the UAV around, for instance to refuel. For this stepper motors were used, and to save some time it was decided to use trinamic stepper motor controllers. They keep track of things like limit switch states, step counting and expose high level commands called TMCL (trinamic motion control language).

The TMCM (trinamic controller) units comes with both an RS485 and a CAN interface. Unlike RS485, the CAN protocol is inherently distributed. Even so, the TMCM units assume there is a “master”, and they will only send a message as a response to a request from the master. Due to available hardware, a raspberry pi with a CAN extension board was used to implement the control logic, and as master. The TMCL CAN network is illustrated in the following figure.

The can network

Even though the Rust stdlib is available for raspberry pi and things like std::time were used in the application there were no real dependency on std for the tmcl interface, and so it was decided to make it compatible also with #[no_std] development. The design was based around the Interface trait representing something able to send TMCL requests and receive TMCL responses. The Interface trait was implemented as follows.

pub trait Interface {
    type Error;

    fn transmit_command<T: Instruction>(
        &mut self, 
        command: &Command<T>
    ) -> Result<(), Self::Error>;

    fn receive_reply(&mut self) -> Result<Reply, Self::Error>;
}

The attentive reader may already have noticed that to use an Interface, a &mut Interface is required. The nice thing about this is that it allows holding the &mut T until a reply has been received guaranteeing that no other module has been able to send in the meantime (remember the scoped noalias model). As seen in the previous figure, there are also several modules connected to the same interface. The not so nice thing about there only being one &mut T at the same time is that some means of deciding when different part of the code should hold the mutable reference is required.

Take 1: Passing around the mutable reference

The first attempt to solve this is based on passing a reference of the interface to the Module only when it is needed. The code for this is seen under.

pub struct Module {/* ... */}

impl Module {
    pub fn write_command<T, IF>(&self, interface: &mut IF, instruction: T) -> Result<T::Return, Error<IF::Error>>
    where T: Instruction, IF: Interface {
        /* ... */
    }
}

This works perferctly fine, and by adding one more layer of generics over std::ops::Deref<Target=IF> it will even work with generic references like MutexGuartd<IF>. There are a few downsides to doing this. If one has multiple TMCL units connected to different interfaces one must make sure that the correct interface is used for every command. One must also make sure to have a reference handy wherever a Module is used. And the perhaps biggest downside is the lack of a good way to extend this interface to support Futures instead of blocking, as the methods must be finished borrowing the interface when returning.

Take 2: Interior mutability to the rescue

Interior mutability certaintly solves the issue of several modules being able to mutate the same interface. Since this library must also work on #[no_std] targets, that have no notion of a Mutex or Arc a RefCell has to be used. If a RefCell has already issued a &mut a call to try_borrow_mut will return a Result::Err<_>, while a call to borrow_mut will panic. Since a Module should never be able to call into a different Module using the borrow_mut variant should be fine. The Module implemented with a shared interface wrapped in a RefCell looks like the following.

pub struct Module<'a, IF: Interface> {
    interface: &'a RefCell<IF>,
    // some fields omitted
}

impl Module {
    pub fn write_command<T>(&self, instruction: T) -> Result<T::Return, Error<IF::Error>>
    where T: Instruction {
        let mut interface = self.interface.borrow_mut();
        /* do work */
    }
}

This works fine for the stack allocated single threaded use case, but once one attempt to put a Module inside a thread one will experience problems. Namely RefCell is not Sync and that means that a reference to RefCell will not be Send, and as a consequence Module will not be Send either.

Take 3: Abstracting over interior mutability

The readers have probably already guessed the proposed solution from the headline. It is to abstract over interior mutable types. This allows no_std applications to use RefCell while threaded applications may use a Mutex. The Trait representing interior mutability used for the tmcl library is seen under.

/// A trait for obtaining a mutable reference on types that allow interior mutability.
trait InteriorMut<'a, T> {

    /// The reference type
    type RefMut: DerefMut<Target=T> + 'a;

    /// The error type
    type Error;

    /// Mutably borrows the internal value from an immutable reference.
    fn borrow_int_mut(&'a self) -> Result<Self::RefMut, Self::Error>;
}

impl<'a, T: 'a> InteriorMut<'a, T> for RefCell<T> {
    type RefMut = cell::RefMut<'a, T>;
    type Error = cell::BorrowMutError;

    fn borrow_int_mut(&'a self) -> Result<Self::RefMut, Self::Error> {
        RefCell::try_borrow_mut(self)
    }
}

#[cfg(feature="std")]
impl<'a, T: 'a> InteriorMut<'a, T> for std::sync::Mutex<T> {
    type RefMut = std::sync::MutexGuard<'a, T>;
    type Error = std::sync::PoisonError<std::sync::MutexGuard<'a, T>>;

    fn borrow_int_mut(&'a self) -> std::sync::LockResult<std::sync::MutexGuard<'a, T>> {
        self.lock()
    }
}

The module implementation will then look like the following:


pub struct Module<'a, IF: Interface + 'a, Cell: InteriorMut<'a, IF>, T: Deref<Target=Cell> + 'a> {
    interface: T,
    // some fields omitted
}


impl<'a, IF: Interface, Cell: InteriorMut<'a, IF>, T: Deref<Target=Cell>> Module<'a, IF, Cell, T> {
    pub fn new(interface: T, address: u8) -> Self {
        Module{ /* fields omitted */ }
    }
    
    pub fn write_command<Inst: Instruction + DirectInstruction>(&'a self, instruction: Inst) -> Result<Inst::Return, Error<IF::Error>> {
        let mut interface = self.interface.borrow_int_mut().or(Err(Error::InterfaceUnavailable))?;
	/* do work */
    }
}

This will allow the user of the library to call the new constructor on a module using any of the following combinations of wrapper types &RefCell<T>, Rc<RefCell<T>> or Arc<Mutex<T>> where T: Interface. The best choice depends on whether std and/or alloc is available.

This makes the library useful in all the following scenarios:

  • Raspberry pi or desktop computer using socketcan with or without threading.
  • Embedded computer with some simple allocator no_std.
  • Deeply embedded computer both without allocation and stdlib.

Pre-RFC: core::borrow::BorrowIntMut

It looks like defining an InteriorMutable trait and implementing it for the most common types works great. So the reader might wonder what the point might be of pushing this any further. One reason for this is that there are actually another set of interior mutable containers not considered here. Those that exist in custom embedded framework that offers interior mutability backed by for instance spin locks or critical sections. Due to the orphan rule, the application writer is not able to implement the InteriorMutable trait for the type in consideration. Also, the library (which InteriorMutabiility exists in) would obviously not be able to implement this trait on every interior mutable type in the world. This means that the interior mutable abstraction should exist in a crate every framework writer implementing interior mutable types is going to use. libcore certaintly fits this description, and as this feature is rather simple and fits well with existing concepts there might be a place for it in libcore.

I am considering writing an RFC to include a core::borrow::BorrowIntMut trait in the core library. The motivation will be the ability to create better libraries that work well for both embedded/no_std and threaded applications. Even though there might be more features one would want from a general interior mutable cell, there are advantages of making it less general as well. A BorrowIntMut trait would fit well with the other borrow traits, would be minimal but self contained and would serve a concrete and important purpose.

I have never written an RFC before and appreciate all feedback, including whether I should pursue this idea or not. Please leave a comment in the reddit post