How can I safely access a local variable from another thread?

Hello.

I'm developing a firmware for an embedded system with no std. The system can trigger interrupts and in the handler function I need to access some data from the main execution thread.

The reference to the data is set via statically allocated Option. It is guaranteed that when the Option has the reference, the reference is valid. The Option is guaranteed to be set to None before the reference becomes invalid.

However, I can't avoid errors by the borrow checker when the data is not statically allocated.
Is there any language feature that will guarantee resetting the Option before the reference becomes invalid, so that there are no errors by the borrow checker?
Othersise, is there any other way I can avoid the errors by the borrow checker?

Here is an example:

use std::cell::RefCell;
use std::sync::Mutex;
use std::thread;
use std::time::Duration;

static GLOBAL_A: A = A::new();

fn some_thread() {
    loop {
        if let Some(str) = *GLOBAL_A.val.lock().unwrap().borrow() {
            println!("THREAD: the string: {}", str);
        } else {
            println!("THREAD: no string.");
        }
        thread::sleep(Duration::from_millis(300));
    }
}

struct A<'a> {
    val: Mutex<RefCell<Option<&'a str>>>,
}

impl<'a> A<'a> {
    const fn new() -> Self {
        Self {
            val: Mutex::new(RefCell::new(None)),
        }
    }
}

struct B<'a> {
    aref: &'a A<'a>,
}

impl<'a> B<'a> {
    fn new(aref: &'a A<'a>) -> Self {
        Self { aref }
    }

    fn method(&self, s: &'a str, duration: Duration) {
        println!("B: string set: {}", s);
        *self.aref.val.lock().unwrap().borrow_mut() = Some(s);
        thread::sleep(duration);
        *self.aref.val.lock().unwrap().borrow_mut() = None;
        println!("B: string reset.");
    }
}

fn main() {
    thread::spawn(some_thread);

    let b = B::new(&GLOBAL_A);

    b.method("hello", Duration::from_millis(500));
    thread::sleep(Duration::from_millis(1000));
    b.method("bye", Duration:`Preformatted text`:from_millis(1000));
    thread::sleep(Duration::from_millis(1000));
    
    let some_str = String::from("dynamic str");
    // b.method(some_str.as_str(), Duration::from_millis(1000)); // some_str` does not live long enough
}

The output:

B: string set: hello
THREAD: the string: hello
THREAD: the string: hello
B: string reset.
THREAD: no string.
THREAD: no string.
THREAD: no string.
B: string set: bye
THREAD: the string: bye
THREAD: the string: bye
THREAD: the string: bye
THREAD: the string: bye
B: string reset.

At the end, when the data is locally allocated (from String), the borrow checker does not allow me to pass the reference to the data in the method function due to error[E0597]: some_str does not live long enough.
Is there any way I can make this code work without copying the data into statically allocated memory?

Thanks!

You can use std::thread::scope for this Rust Playground

2 Likes

Thank you for your response.
Indeed, it works as expected, I didn't know thread::scope existed so I learnt something new =)

However, looks like I provided an example code that is a little too far from the code I have in reality and it doesn't fully replicate the real code.

In my real use case I have to use a global variable to access the data since the interrupt function (in my example code represented as a thread some_thread) can't accept any arguments. So, if following my example, the some_thread function must have the following signature fn some_thread().

Again, sorry for the slightly incorrect question.

There's not going to be a safe way to do it with references because your static is an A<'static>. You can't borrow locals for 'static.

You could use raw pointers.

1 Like

You maybe want to have a look at rtic which gives you a safe interface do define shared resources between different interrupt handlers and the "idle" task.

1 Like