I'm writing some embedded code for an RP2040 custom board, but am having issues with this block giving me a cryptic 'can't capture dynamic environment in a fn item' compiler error, despite using the exact syntax the compiler is suggesting.
This code block is supposed to be "listening" for UART signals on UART0 and then passing any received data into a message queue, MSG_Q.
Relevant code block:
use rp2040_hal::{
...
pac::interrupt,
...
};
#[interrupt]
fn UART0_IRQ() {
let mut buffer = [0u8; 64];
let _bytes_read = move || { uart_s.read_raw(&mut buffer); };
if _bytes_read.into().is_ok() {
unsafe {
let s: &str = core::str::from_utf8(&buffer).unwrap();
cortex_m::interrupt::free(|cs| MSG_Q.borrow(cs).borrow_mut().push(s.to_string()));
}
}
}
Compiler error:
error[E0434]: can't capture dynamic environment in a fn item
--> src\main.rs:150:38
|
150 | let _bytes_read = move || { uart_s.read_raw(&mut buffer); };
| ^^^^^^
|
= help: use the `|| { ... }` closure form instead
The problem is not with _bytes_read, but with the interrupt handler itself. uart_s is a local variable defined outside of it, so handler can't refer to it. How it is defined at all?
In Rust, the compiler will implement a closure by creating a struct that will hold onto any state it closes over and implementing the relevant Fn*() trait.
To help with ergonomics, when the closure doesn't capture anything it is assignable to the corresponding bare function (e.g. || println!("Hello, World") is assignable to a fn() variable). This works because everything is dictated by the function implementation and we don't need to carry any state around.
However, once your closure captures some state from the outside (uart_s in this case), we need some place to store that state so the closure can't be assigned to a fn() variable any more (a fn() is literally just a pointer to some machine code). That's what the "can't capture dynamic environment in a fn item" error is trying to tell you.
The typical solution is to make sure any state your callback needs is either stored as a global variable or to make sure your caller somehow passes the state in as an argument.
@Michael-F-Bryan I'm fairly new to Rust, so I'm trying to parse what you're saying, but when you say "state" are you just referring to a temporal snapshot of a variable and its current value?
Does this mean that uart_s needs to be defined before fn main() as a global in order to make it accessible inside fn UART0_IRQ() even though fn UART0_IRQ() is defined inside main()? Why does the compiler suggest a closure as a possible fix? This seems to have nothing to do with the core problem of the variable being out of scope.
I'd like to pass uart_s in as an argument, but I don't think that's possible with an interrupt function.