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