Is it possible to borrow something in delay?

I'm implementing wrapper for UEFI protocol which uses FFI heavily. but I have trouble implementing Drop in a way that doesn't massively disrupt other code base.
I have struct that represents a region of memory, PciMappedRegion.
The address number it stores is mapped that it's usable by PCI device.

/// Represents a region of memory mapped by PCI Root Bridge I/O protocol.
/// The region will be unmapped automatically when it is dropped.
///
/// # Lifetime
/// `'p` is the lifetime for Protocol.
/// `'r` is the lifetime for Mapped Region.
/// Protocol must outlive the mapped region
/// as unmap function can only be accessed through the protocol.
#[derive(Debug)]
pub struct PciMappedRegion<'p, 'r>
where
    'p: 'r,
{
    region: PciRegion,
    _lifetime_holder: &'r (),
    key: *const c_void,
    proto: &'p PciRootBridgeIoProtocol,
}

/// Represents a region of memory in PCI root bridge memory space.
/// CPU cannot use address in this struct to deference memory.
/// This is effectively the same as rust's slice type.
/// This type only exists to prevent users from accidentally dereferencing it.
#[derive(Debug, Copy, Clone)]
pub struct PciRegion {
    /// Starting address of the memory region
    pub device_address: u64,

    /// Byte length of the memory region.
    pub length: usize
}

Since I don't want the memory to be dropped before the PciRegion is removed, it has lifetime parameter 'r.
In order to free the mapped region, it needs call a method in a struct PciRootBridgeIoProtocol. so it also has lifetime parameter 'p.
Here, p must outlive r. I assumed that Protocol would live quite long.
The problem is, the mapped region holds immutable reference to the Protocol struct. But other parts of code needs to call methods in there as well. and some of those methods needs mutable reference to the protocol.
So it is not possible to use call those methods when the mapped region is alive.
and I can't make users work around this issue themselves because this is very critical to one use case:
One of methods of the PciRootBridgeIoProtocol is one that copies memory. it needs mutable reference to the protocol struct, and two regions of mapped memory region.
So it needs those mapped region alive, and it also needs mutable reference to the protocol which is just not possible.

Is it possible to borrow Protocol struct only when it's being dropped? The mapped region only needs it when it's being dropped, so I don't think holding its reference entire time is good idea.

btw I can't make methods in the Protocol struct not need reference to itself because original C code we are invoking requires it.

Would it make sense to use interior mutability on PciRootBridgeIoProtocol? After all &/&mut in Rust means shared/exclusive not necessarily immutable/mutable.

2 Likes

Sounds like you need to stop everything being under one roof. i.e. multiple structures instead of the single PciRootBridgeIoProtocol

1 Like

If you want to use references for this, you need interior mutability so you can get rid of the &mut _.

1 Like

I only used &mut on the Protocol struct because C binding already written by other developer had mutable pointer. I could modify it but I'm not sure of effect of it. The copy method for example modifies memory, which implies it'd probably need exclusive access.
I thought of using qcell but I don't think I can come up with good enough model that users can use it comfortably.
I avoided using RefCell because I didn't like runtime checking but after hearing from other people I think I can implement something that works first and then work on simplifying it.

The correct mapping of a mutable C pointer to Rust isn't &mut but *mut. Avoiding UB with FFI in rust is tricky, you must ensure that you never break the rules of references by for example creating two mutable references to the same object, and if you accept random pointers from C and turn them into &mut without checking you're very likely to do so.

RefCell is a simple option to check you don't have any unexpected reentrant code, it is literally just setting an extra int while it's in use so hardly a major performance concern in the majority of cases, but also be aware it's not safe to use across threads, so your FFI must also ensure that.

You probably want to use PhantomData here instead

2 Likes