Lifetime of reference to member

Hi,

I have an object which holds an member and a second member requires a reference to the first member. Perhaps in this scenario I get a compile error because of the lifetime, but I don't understand why.

// External library
trait T {
    fn prepare(&mut self) {}
}


// Another external library but I created Object2 so I could change it
struct Object {}

struct Object2<'a> {
    o: &'a mut Object,  
}

// My code
struct Object3<'a> {
   o2: Option<Object2<'a>>,
   o: Object,
}

impl<'a> Object3<'a> {
    fn new(obj: Object) -> Self {
        Self {
            o: obj,
            o2: None,
        }
    }
}

impl<'a> T for Object3<'a> {
    fn prepare(&mut self) {
        self.o2 = Some(Object2 {o: &mut self.o});
    }
}
error: lifetime may not live long enough
  --> src/lib.rs:32:9
   |
30 | impl<'a> T for Object3<'a> {
   |      -- lifetime `'a` defined here
31 |     fn prepare(&mut self) {
   |                - let's call the lifetime of this reference `'1`
32 |         self.o2 = Some(Object2 {o: &mut self.o});
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ assignment requires that `'1` must outlive `'a`

You're trying to create a self-referential struct, which is a well-known pain point in Rust.
You should rethink your design to avoid self-references if possible, otherwise please include more details about your actual use case so we can suggest a better approach.

Hi @FZs thanks for your reply.

I am using the embedded-sdmmc library and I am extending it to be able to read multiple blocks at once. Currently the library can do that but then I have to pass a pointer large as multiple blocks to the function which is not possible on embedded. So the idea is to split the reading of blocks from SDCard.

  1. Prepare
  2. Loop { Read block and do something with it }
  3. Stop reading
    The prepare is done during creating of the object

Object 2:

pub struct SdCardMultiBlockRead<'a, SPI, CS, DELAYER>
where
    SPI: embedded_hal_async::spi::SpiDevice<u8>,
    CS: embedded_hal::digital::OutputPin,
    DELAYER: embedded_hal_async::delay::DelayNs,
{
    sd_card: &'a mut SdCard<SPI, CS, DELAYER>,
}

impl<'a, SPI, CS, DELAYER> SdCardMultiBlockRead<'a, SPI, CS, DELAYER>
where
    SPI: embedded_hal_async::spi::SpiDevice<u8>,
    CS: embedded_hal::digital::OutputPin,
    DELAYER: embedded_hal_async::delay::DelayNs,
{
    /// Create new object
    pub async fn new(
        sd_card: &'a mut SdCard<SPI, CS, DELAYER>,
        start_block_idx: BlockIdx,
    ) -> Result<Self, Error> {
        let mut inner = sd_card.inner.borrow_mut();
        inner.check_init().await?;
        inner.prepare_read(start_block_idx).await?;
        drop(inner);
        Ok(Self { sd_card })
    }

    /// Reading next block
    pub async fn read(&mut self, block: &mut [u8]) -> Result<(), Error> {
        let mut inner = self.sd_card.inner.borrow_mut();
        inner.read_data(block).await
    }

    /// Ending a multiblock read
    /// IMPORTANT: This function must be called before destroying this object!
    pub async fn stop(&mut self) -> Result<(), Error> {
        let mut inner = self.sd_card.inner.borrow_mut();
        inner.end_read().await
    }
}

I could use RefCell, but if it is somehow possible to do it with direct references would be preferred

Object 3:

struct BlockDevice<'a, SPI: SpiDevice, CS: OutputPin, DELAYER: DelayNs> {
    sd_card: SdCard<SPI, CS, DELAYER>,
    sd_card_multiblock_read: Option<SdCardMultiBlockRead<'a, SPI, CS, DELAYER>>,
    sd_card_multiblock_write: Option<SdCardMultiBlockWrite<'a, SPI, CS, DELAYER>>,
}

impl<'a, SPI: SpiDevice, CS: OutputPin, DELAYER: DelayNs> BlockDevice<'a, SPI, CS, DELAYER> {
    fn new(sd_card: SdCard<SPI, CS, DELAYER>) -> Self {
        Self {
            sd_card,
            sd_card_multiblock_read: None,
            sd_card_multiblock_write: None,
        }
    }
}

impl<'a, SPI: SpiDevice, CS: OutputPin, DELAYER: DelayNs> block_device::BlockDevice
    for BlockDevice<'a, SPI, CS, DELAYER>
{
   async fn prepare_multiblock_write(
        &mut self,
        lba: u32,
        blocks_count: u32,
    ) -> Result<(), BlockDeviceError> {
        self.sd_card_multiblock_write = Some(
            SdCardMultiBlockWrite::new(&mut self.sd_card, BlockIdx(lba), blocks_count)
                .await
                .map_err(|e| Self::sd_error_to_block_device_error(e))?,
        );
        Ok(())
    }
}

I see you are already using some async code. Async block/fn futures can themselves contain self-references (their let variables do it all the time), so perhaps you could express your self-references through one. The main problem with this is that once you have created an async block future it's hard to provide data externally to it.

But can I change anything from architectural perspective?

One possibility I found is using everywhere Options. But in this case I could loose the object if I do anything wrong

// External library
trait T {
    fn prepare(&mut self) {}
}


// Another external library but I created Object2 so I could change it
struct Object {}

struct Object2 {
    o: Option<Object>,  
}

impl Object2 {
    fn stop(&mut self) -> Option<Object> {
        self.o.take()
    }
}

// My code
struct Object3 {
   o2: Option<Object2>,
   o: Option<Object>,
}

impl Object3 {
    fn new(obj: Object) -> Self {
        Self {
            o: Some(obj),
            o2: None,
        }
    }
}

impl T for Object3 {
    fn prepare(&mut self) {
        self.o2 = Some(Object2 {o: self.o.take()});
    }
}

and a second member requires a reference to the first member.

..and then you move this structure, and reference becomes invalid. This is the reason why safe Rust doesn't support self-reference structures.

To have self-ref struct, you must:

  1. Use Pin to make sure struct is never moved: std::pin - Rust
  2. Use unsafe code to deref it :((

There is s crate that hides this complexity:

Thanks for that hint. I completely forgott about this case. This makes it clear why it is not possible.

I will checkout pin thanks!

I rewrote now my code so that o2 is now taking the ownership of Object and no need to store Object in Object3

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.