#cell #borrow #async #loops #no-alloc #from-fn

no-std loopcell

cell for multiple routes of access that are only used one-at-a-time in sequence

1 stable release

1.0.0 Aug 16, 2024

#1047 in Rust patterns

32 downloads per month

MPL-2.0 license

29KB
451 lines

loopcell

Cell type(s) designed for read-modify-write-style loops, avoiding the manual write step.

Useful in complex event-handling services with multiple streams of input, and in particular it fits well into the iterator paradigm as long as you discard the produced values before the next iteration. In many ways, it can function as a "runtime version" of a lending iterator.

It also works well in combination with qcell, ghost_cell, and similar crates, if you use LoopCell or LoopSyncCell for the ownership control types.

Quick Example

This is a quick example illustrating how to use LoopCell to create an iterator that reuses a shared buffer to process events. You can do something essentially identical with LoopSyncCell, but it will work better in the case an access needs to be transferred across threads (which is common when implementing futures).

use std::ops::{Deref, DerefMut};
pub struct Event {
    /// If `true`, then no more events should be processed after this.
    pub last: bool
} 

impl Event {
    /// Dummy function that writes to the buffer.
    pub fn serialize_into(self, buffer: &mut Vec<u8>) {
        buffer.extend(b"DUMMY VALUE\n");
    }
}

/// This illustrates the way this can be used as a pseudo-runtime-lending-iterator.
/// While this specific case could be implemented without the loopcell, in more complex
/// cases like having multiple iterators that all reuse the buffer or provide values that
/// reference the buffer, this is not possible because only one would be able to hold a
/// mutable reference at a time
fn write_events(events: impl Iterator<Item = Event>, mut process_event_bytes: impl FnMut(&mut [u8])) {
    let buffer = loopcell::LoopCell::new(Vec::<u8>::with_capacity(8192));
    // Iterator that returns `Some` of an access to the buffer, while it's refilled.
    let buffer_iter = core::iter::from_fn(|| buffer.access()).fuse();
    for (mut buffer_handle, next_event) in buffer_iter.zip(events) {
        let is_last_event = next_event.last; 
        {
            let mut buffer_access = WipeOnDrop::from(buffer_handle.as_mut());
            next_event.serialize_into(&mut buffer_access);
            process_event_bytes(&mut buffer_access);
        }
        if is_last_event {
            buffer_handle.consume();
        }
    }
}

fn main() {
    // Illustrate the way the loopcell enables iteration using conditional consumption of
    // the handles. 
    let mut counter = 0; 
    let mut increment_counter = |_bytes: &mut _| { counter += 1; };
    let events = [false, false, false, true].map(|last| Event { last });
    write_events(events.into_iter(), &mut increment_counter);
    assert_eq!(counter, 4);
}

/// Implementation detail that wipes a buffer with `clear()` on drop, but inherently preserves capacity.
pub struct WipeOnDrop<'b>(&'b mut Vec<u8>);

impl <'b> From<&'b mut Vec<u8>> for WipeOnDrop<'b> {
    fn from(v: &'b mut Vec<u8>) -> Self {
        Self(v)
    }
}

impl <'b> Deref for WipeOnDrop<'b> {
    type Target = Vec<u8>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl <'b> DerefMut for WipeOnDrop<'b> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl <'b> Drop for WipeOnDrop<'b> {
    fn drop(&mut self) {
        self.0.clear()
    }
}


Dependencies

~33KB