Async lifetime guarantees

Consider the following code:

let processor = |state: &mut u64, add: u64| { state += add; };
let mut state = 0u64;
let handler = Box::new({ move |add: u64| { processor(&mut state, add) } });

While this works as is, processor cannot be made async because the generated Future might live longer than state. While there are a few ways to make this work (move state into processor, use an Arc, or use a static reference), none are contextually ideal to my use case.

I can enforce the guarantee that state will outlive the Future at runtime, but I need to convince the compiler of this. Unsafe is permitted, and I am using a custom async executor.

If you're sure state lives long enough, you can mem::transmute lifetime of &mut state.

Alternatively, you can cast &mut state to *mut _ raw pointer and unsafely dereference it when needed (raw pointers are easy to pass around, but you also have to manually ensure it's a unique reference, without mutable aliasing).

1 Like

Here's some code that I use for my async networking program that takes advantage of raw pointers, but only if from a heap-pinned location. Maybe you can do something similar if you need to pass the array around:

/// This allows sending around a pointer to a heap-pinned packet in memory. The mutable pointer must be directly input
/// from a ```Pin<Box<RawInboundType>>``` to help ensure safety
pub struct StageDriverPacket {
    ptr: *mut RawInboundPacket
}

unsafe impl Send for StageDriverPacket {}
// There is no need for Sync, because that internal algorithm necessarily implies the existence of at most one active user
// of the packet at a time
//unsafe impl Sync for StageDriverPacket {}

impl StageDriverPacket {
    /// Creates a transmittable packet in memory with a constant pointee
    pub fn new(ptr: &mut Pin<Box<RawInboundPacket>>) -> Self {
        Self { ptr: *ptr.as_mut() as *mut RawInboundPacket }
    }

    /// Creates a packet which can be transmitted through memory. The calling function must
    /// guarantee that the pointee is initialized properly, otherwise this will be an invalid
    /// packet
    pub fn new_from_maybe_uninit(ptr: &Pin<Box<MaybeUninit<RawInboundPacket>>>) -> Self {
        unsafe { Self { ptr: &mut ptr.assume_init() as *mut RawInboundPacket } }
    }
}

impl Deref for StageDriverPacket {
    type Target = RawInboundPacket;

    fn deref(&self) -> &Self::Target {
        unsafe { &*self.ptr }
    }
}

impl DerefMut for StageDriverPacket {
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { &mut *self.ptr }
    }
}

/// Clone is only allowed when the previous packet is being transferred to another stage
impl Clone for StageDriverPacket {
    fn clone(&self) -> Self {
        Self {ptr: self.ptr}
    }
}

StageDriverPacket can outlive the &mut Pin<Box<RawInboundPacket>> it was created from, so new needs to be unsafe.

3 Likes

Good point. will flagging the function as unsafe tell the compiler to do anything magical? Because otherwise, I don't think it matters, because the surrounding logic of the program ensures that a StageDriverPacket wont be floating around in the program when the heap-pinned structure drops from RAM

It tells nothing to the compiler (aside from the fact that callers should use an unsafe block), it is just for the benefit of humans reading, using and modifying the code... such as your future self :wink:

Respecting the "all code that can trigger UB if mishandled is marked unsafe" rule means that, reciprocally, all code which is not marked unsafe cannot cause UB, which is a very pleasant guarantee to have when doing casual refactoring.

1 Like

The self does not exist. It is neither inside nor outside of Schrodinger's box. It neither exists (+) nor non-exists (-). It is beyond duality (2). It is thus transcendental (1)

3 Likes

I ended up finding a safe solution, but now I have a problem I'm not sure can be solved.

This is the with in the error.

error[E0271]: type mismatch resolving `for<'r> <for<'_> fn(&mut bool, actors::Context, message::Message) -> impl core::future::future
::Future {system::system_actor_processor} as std::ops::FnOnce<(&'r mut bool, actors::Context, message::Message)>>::Output == _`
   --> src\system.rs:366:14
    |
366 |             .with(false, system_actor_processor)
    |              ^^^^ expected bound lifetime parameter, found concrete lifetime
    |
    = note: required because of the requirements on the impl of `actors::Processor<bool, impl core::future::future::Future>` for `for
<'_> fn(&mut bool, actors::Context, message::Message) -> impl core::future::future::Future {system::system_actor_processor}`

Note, with is a public-facing API, it shouldn't be unsafe, and should be as ergonomic as possible.