Simon Doppler
Simon Doppler

Reputation: 2093

Dealing with lifetimes in global variables

I am currently writing a board support package for an embedded board and would like to set up a serial output over USB. I am basing my design on the hifive BSP.

The process to do this is in three steps:

  1. Set up the USB bus (UsbBusAllocator, which refers to a UsbPeripheral)
  2. Initialize a SerialPort instance, which refers to UsbBusAllocator
  3. Initialize a UsbDevice instance, which refers to UsbBusAllocator

To make my life simpler, I wrapped the SerialPort and UsbDevice in a SerialWrapper struct:

pub struct SerialWrapper<'a> {
    port: SerialPort<'a, Usbd<UsbPeripheral<'a>>>,
    dev: UsbDevice<'a, Usbd<UsbPeripheral<'a>>>,
}

impl<'a> SerialWrapper<'a> {
    pub fn new(bus: &'a UsbPort<'a>) -> Self {
        // create the wrapper ...
    }
}

I would like a way make the structure created by the SerialWrapper::new global.

I tried to use:

static mut STDOUT: Option<SerialWrapper<'a>> = None;

but I can't use this as lifetime 'a is not declared.

I thought about using MaybeUninit or PhantomData but both will still need to have SerialWrapper<'a> as type parameter and I will get the same issue.

My goal is to be able to use code similar to this:

struct A;

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

static mut STDOUT: Option<B> = None;

fn init_stdout() {
    let a = A {};

    unsafe {
        STDOUT = Some(B {s: &a});
    }
}

// access_stdout is the important function
// all the rest can be changed without issue
fn access_stdout() -> Result<(), ()> {
    unsafe {
        if let Some(_stdout) = STDOUT {
            // do stuff is system ready
            Ok(())
        } else {
            // do stuff is system not ready
            Err(())
        }
    }
}

fn main() {
    init_stdout();
    let _ = access_stdout();
}

Do you have any suggestions on how to proceed?

I don't mind having a solution requiring unsafe code, as long as I can have safe functions to access my serial port.

Upvotes: 1

Views: 795

Answers (1)

Ian S.
Ian S.

Reputation: 1951

Short answer: whenever you have a lifetime in the type of a static variable, that lifetime needs to be 'static.

Rust does not allow for dangling references, so if you having anything living for shorter than the static lifetime in a static variable, there's the possibility for dangling. I think your code will need a fair amount of refactoring to satisfy this requirement. I would recommend figuring out a way to store the data you need without references since that will make your life much easier. If it is absolutely imperative that you store references you'll need to figure out a way to leak the data to extend its lifetime to 'static.

I am not terribly familiar with embedded development, and I know that static mut has some use-cases there, however usage of that language feature is pretty universally frowned upon. static muts are wildly unsafe and even bypass some borrow checker mechanisms by allowing multiple mutable references at the same time. If you were to encode this properly in Rust's type system you'd probably want to make it just static STDOUT: SyncWrapperForUnsafeCell<Option<T>>, and then provide a safe interface (that might involve locking) for your wrapper with comments explaining why your current environment makes it safe. However if you think that a static mut is the appropriate option there I trust your judgement.

Upvotes: 1

Related Questions