Get access to item behind blocking_mutex::Mutex

I have a global struct that I want to protect behind a blocking mutex. However, I am unable to access it.
Here is a MWE:

#![no_std]
#![no_main]
#![feature(type_alias_impl_trait)]

use embassy_executor::Spawner;
use panic_probe as _;

// ------------------------------ buffer------------------------------

use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
use embassy_sync::blocking_mutex::Mutex;

pub struct Buffer {
    buffer: [u8; 16],
}

impl Buffer {
    pub const fn new() -> Self {
        Self { buffer: [0 as u8; 16], }
    }

    pub fn test(&mut self) {
        self.buffer[0] = 0;
    }
}

static BUFFER: Mutex<CriticalSectionRawMutex, Buffer> = Mutex::new(Buffer::new());


fn test() {
    BUFFER.lock(|b| { b.test() });
}

// ------------------------------ main -------------------------------

#[embassy_executor::main]
async fn main(_spawner: Spawner) {
    test();
}

I get:

error[E0596]: cannot borrow `*b` as mutable, as it is behind a `&` reference
  --> src/main.rs:32:23
   |
32 |     BUFFER.lock(|b| { b.test() });
   |                  -    ^ `b` is a `&` reference, so the data it refers to cannot be borrowed as mutable
   |                  |
   |                  consider changing this binding's type to be: `&mut Buffer`

I then though that I needed to have &mut b as closure argument, but this did not work.
I also tried to use BUFFer.get_mut(), and I also tried to add mut to the declaration of the Buffer inside the Mutex, but none of these worked.

It seems I somehow don't get how this Mutex is supposed to be used.

Any hint is greatly appreciated!

This library's Mutex seems different from std's in this regard, as it only offers shared access to the locked data. This corresponds to how the no-op implementation would allow re-entrant locking, and both critical-section and no-op implementation have a respective borrow method that also allows you to get multiple references.

The only thing this mutex does seem to make sure is to give you exclusively access between threads, i. e. no other thread has simultaneous access, and thus is reflected in the Sync implementation only requiring T: Send.

This means that if you want fully exclusive and mutable access, you can combine this mutex with a RefCell, which is something the crate even does itself e. g. in the implementation details of their async Mutex.

So yeah.. just try Mutex<CriticalSectionRawMutex, core::cell::RefCell<Buffer>> and BUFFER.lock(|b| b.borrow_mut().test()) I guess.

Or alternatively, you can do

pub struct Buffer {
    buffer: [core::cell::Cell<u8>; 16],
}

and work with a test(&self) method which is callable from the closure passed to BUFFER.lock.


Edit: Looking at the critical section docs with in critical_section - Rust

Nesting critical sections is allowed. The inner critical sections are mostly no-ops since they’re already protected by the outer one.

It looks like the implementation would allow re-entrant locking, too, so the additional use of RefCell or Cell is strictly necessary in this case, too, not just a limitation of a generic API.

Also this makes sense, since the critical section is a global thing and you might want to be able to access multiple mutexes together - there is no existing per-mutex level of tracking which one is locked in place, so it makes sense you need to bring your own in the form of RefCell or limit yourself to short unproblematic provably non-reentrant mutations via Cell.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.