#pointers #data #packing #fits #boxing #value #issue

yanked stowaway

A library for packing data into a pointer, if it fits, or boxing it otherwise

2.3.1 Sep 26, 2020
2.3.0 Aug 23, 2020
2.0.0 Apr 18, 2020
1.1.2 Feb 11, 2020
0.1.1 Jan 19, 2020

#7 in #boxing

22 downloads per month
Used in stowaway-derive

MPL-2.0 license

40KB
448 lines

stowaway

Due to ongoing soundness issues, this crate is considered permanently unsound until future notice. Do not use it in your projects.

Stowaway is a library for packing data into a pointer, if it fits, boxing it otherwise

Note about testing

The doctests for this library include non-default features, make sure to use --all-features when testing


lib.rs:

Stowaway is a library for efficiently storing values in a pointer. The main use case for stowaway is as a helpful way to interoperate with libraries that require opaque data to be passed via pointer, such as C libraries expecting a void*, std::RawWaker, and boost::context.

The central feature of this library is value packing in a Stowaway struct. If T is not larger than a pointer (that is, if sizeof(T) <= sizeof(*T)), then the value is packed directly into the bytes of an opaque pointer value (specifically, a *mut ()). This value can then be passed as the context pointer for C-like interfaces, and then converted back into a Stowaway on the other end.

Lifecycle

The basic lifecycle of a Stowaway value is as follows:

  • Create a Stowaway with Stowaway::new. This will pack the value into the space of a pointer, or box it if it's too big.
  • Convert that value into a pointer with Stowaway::into_raw.
  • Store that value somewhere, such as in a RawWaker, or as a void* in a C API. Recover the value somewhere else.
  • Convert the pointer back into a Stowaway with Stowaway::from_raw. This takes back ownership of the value, so make sure to only do it once, and discard the pointer afterwards.
  • Covert the Stowaway back into a T, or use it as a container with Deref / AsRef / DerefMut / AsMut.

The value must have the Stowable marker trait; this trait informs the type system that the type does not contain any uninitialized bytes which might be packed into the pointer value (or, if it does, to box it unconditionally).

Simple example:

use stowaway::{Stowaway, Stowable};

fn demo_lifecycle<T: Clone + std::fmt::Debug + Eq + Stowable>(value: T) {
    let cloned = value.clone();

    let stowed: Stowaway<T> = Stowaway::new(value);
    let storage = Stowaway::into_raw(stowed);
    // At this point, the storage pointer would be passed into a C API,
    // and recovered somewhere else
    let new_stowed: Stowaway<T> = unsafe { Stowaway::from_raw(storage) };
    let unstowed: T = Stowaway::into_inner(new_stowed);

    assert_eq!(unstowed, cloned);
}

// A small value, like a u16, is stored directly in the pointer. No
// allocations are performed in this example.
demo_lifecycle(137u16);

// A large value, like  `Vec` (which internally is a data pointer and a
// pair of usize) cannot fit in a pointer, and will therefore be boxed
// when stored in `Stowaway`
demo_lifecycle(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

C-like API Example

In this example, we create a fake "C like" API. We will store in this API a series of function pointers and context data pointers, which will then be called all at once.

use stowaway::Stowaway;

// Fake stdout
static mut stdout: String = String::new();

#[derive(Default)]
struct EventList {
    events: Vec<(fn(*mut ()), *mut())>
}

impl EventList {
    // our fake C API: add a function pointer and a *mut () to a list
    // of callbacks to run.
    fn add_event(&mut self, fptr: fn(*mut ()), ctx: *mut()) {
        self.events.push((fptr, ctx));
    }

    // For each function pointer added with add_event, call the function
    // with the context pointer.
    fn run_all_events(self) {
        self.events.into_iter().for_each(|(fptr, ctx)| {
            fptr(ctx);
        })
    }
}

let mut event_list = EventList::default();

// Add some events to the list

// Event 1: print a simple number. u16 should easily fit in a pointer,
// so this won't allocate anything
fn print_u16(value: *mut()) {
    unsafe {
        let value: Stowaway<u16> = unsafe { Stowaway::from_raw(value) };
        writeln!(&mut stdout, "A number: {}", *value).unwrap();
    }
}

event_list.add_event(print_u16, Stowaway::into_raw(Stowaway::new(10u16)));
event_list.add_event(print_u16, Stowaway::into_raw(Stowaway::new(20u16)));

// Event 2: Print a large array (`[u64; 8]`). This won't definitely won't fit
// in a pointer, so stowaway will automatically allocate it for us
fn print_big_array(value:  *mut()) {
    unsafe {
        let value: Stowaway<[u64; 8]> = unsafe { Stowaway::from_raw(value) };
        writeln!(&mut stdout, "8 large numbers: {:?}", *value).unwrap();
    }
}

let data: [u64; 8] = [1, 1, 2, 3, 5, 8, 13, 21];
event_list.add_event(print_big_array, Stowaway::into_raw(Stowaway::new(data)));

let data: [u64; 8] = [34, 55, 89, 144, 233, 377, 610, 987];
event_list.add_event(print_big_array, Stowaway::into_raw(Stowaway::new(data)));

// Execute all the events
event_list.run_all_events();

unsafe {
    assert_eq!(stdout,
        "A number: 10\n\
         A number: 20\n\
         8 large numbers: [1, 1, 2, 3, 5, 8, 13, 21]\n\
         8 large numbers: [34, 55, 89, 144, 233, 377, 610, 987]\n"
    );
}

Dependencies

~235KB