Unboxed

Struct Unboxed 

Source
#[non_exhaustive]
pub struct Unboxed<RType: Sized, CType: Sized> { /* private fields */ }
Expand description

Unboxed is used to model values that are passed by reference, but where the memory allocation is handled by C. This approach allows the C code to allocate space for the value on the stack or in other structs, often avoiding unnecessary heap allocations.

The two type parameters, RType and CType, must share the same alignment, and RType must not be larger than CType. Functions in this type will cause a runtime panic in debug builds if these requirements are violated.

If the fields of the struct are meant to be accessible to C, RType and CType may be the same type, trivially ensuring the alignment and size requirements are met.

Define your C and Rust types, then a type alias parameterizing Unboxed:

#[repr(C)]
struct ComplexInt {
    re: i64,
    im: i64,
}
type UnboxedComplexInt = Unboxed<ComplexInt, ComplexInt>;

Then call static methods on that type alias.

§Opaque CType

It is not a requirement that the fields of the types match. In fact, a common use of this type is with an “opaque” C type that only contains a “reserved” field large enough to contain the Rust type. There is no constant way to determine the space required for a Rust value, but it is possible to make a conservative guess, possibly leaving some unused space. The suggested C type is represented in Rust as

struct CType([u64; N]);

for some N large enough to contain the Rust type on the required platforms. In C, this type would be defined as

struct ctype_t {
    _reserved size_t[N];
}

for the same N. The types must also have the same alignment; typically using size_t accomplishes this.

§Constructors

This type provides two functions useful for initialization of a CType given a value of type RType: to_out_param takes an “output argument” pointing to an uninitialized value, and initializes it; while return_val returns a struct value that can be used to initialize a C variable. Both function similarly, so choose the one that makes the most sense for your API. For example, a constructor which can also return an error may prefer to put the error in the return value and use to_out_param.

§Safety

C allows uninitialized values, while Rust does not. Be careful in the documentation for the C API to ensure that values are properly initialized before they are used.

Implementations§

Source§

impl<RType: Sized, CType: Sized> Unboxed<RType, CType>

Source

pub unsafe fn take(cval: CType) -> RType

Take a CType and return an owned value.

This approach is uncommon in C APIs. It leaves behind a value in the C allocation which could be used accidentally, resulting in a use-after-free error. Prefer Unboxed::take_ptr unless the type is Copy.

§Safety
  • cval must be a valid CType value
Source

pub unsafe fn take_ptr_nonnull(cptr: *mut CType) -> RType

Take a pointer to a CType and return an owned value.

This is intended for C API functions that take a value by reference (pointer), but still “take ownership” of the value. It leaves behind an invalid value, where any non-padding bytes of the Rust type are zeroed. This makes use-after-free errors in the C code more likely to crash instead of silently working. Which is about as good as it gets in C.

§Safety

Do not pass a pointer to a Rust value to this function:

let rust_value = RustType::take_ptr_nonnull(&mut c_value); // BAD!

This creates undefined behavior as Rust will assume c_value is still initialized. Use Unboxed::take in this situation.

  • cptr must not be NULL and must point to a valid CType value (see Unboxed::take_ptr for a version allowing NULL)
  • The memory pointed to by cptr is uninitialized when this function returns.
Examples found in repository?
examples/bytebuf.rs (line 40)
39pub unsafe extern "C" fn byte_buffer_free(bb: *mut byte_buffer_t) {
40    let bb = unsafe { UnboxedByteBuffer::take_ptr_nonnull(bb) };
41    drop(bb); // just to be explicit
42}
43
44/// Checksum a byte_buffer_t's contents by XOR'ing all bytes together.
45#[no_mangle]
46pub unsafe extern "C" fn byte_buffer_checksum(bb: *const byte_buffer_t) -> u8 {
47    unsafe {
48        UnboxedByteBuffer::with_ref_nonnull(bb, |bb| {
49            // ok, not the most exciting "checksum"!
50            bb.0.iter().copied().reduce(|a, b| a ^ b).unwrap_or(0)
51        })
52    }
53}
54
55/// Add a byte to the byte buffer.
56#[no_mangle]
57pub unsafe extern "C" fn byte_buffer_push(bb: *mut byte_buffer_t, b: u8) {
58    unsafe { UnboxedByteBuffer::with_ref_mut_nonnull(bb, |bb| bb.0.push(b)) }
59}
60
61/// Combine two byte buffers, returning a new byte buffer containing the bytes
62/// from both inputs.  This function consumes its inputs and they must not be
63/// used after it returns.
64#[no_mangle]
65pub unsafe extern "C" fn byte_buffer_combine(
66    bb1: *mut byte_buffer_t,
67    bb2: *mut byte_buffer_t,
68) -> byte_buffer_t {
69    let mut bb1 = unsafe { UnboxedByteBuffer::take_ptr_nonnull(bb1) };
70    let bb2 = unsafe { UnboxedByteBuffer::take_ptr_nonnull(bb2) };
71
72    // modify bb1 in place (but it's not in the caller's location anymore)
73    bb1.0.extend(&bb2.0[..]);
74    unsafe { UnboxedByteBuffer::return_val(bb1) }
75}
Source

pub unsafe fn with_ref_nonnull<T, F: FnOnce(&RType) -> T>( cptr: *const CType, f: F, ) -> T

Call the contained function with a shared reference to the value.

§Safety
  • cptr must not be NULL and must point to a valid CType value (see Unboxed::with_ref for a version allowing NULL).
  • no other thread may mutate the value pointed to by cptr until the function returns.
  • ownership of the value remains with the caller.
Examples found in repository?
examples/bytebuf.rs (lines 48-51)
46pub unsafe extern "C" fn byte_buffer_checksum(bb: *const byte_buffer_t) -> u8 {
47    unsafe {
48        UnboxedByteBuffer::with_ref_nonnull(bb, |bb| {
49            // ok, not the most exciting "checksum"!
50            bb.0.iter().copied().reduce(|a, b| a ^ b).unwrap_or(0)
51        })
52    }
53}
Source

pub unsafe fn with_ref_mut_nonnull<T, F: FnOnce(&mut RType) -> T>( cptr: *mut CType, f: F, ) -> T

Call the contained function with an exclusive reference to the data type.

§Safety
  • cptr must not be NULL and must point to a valid CType value (see Unboxed::with_ref_mut for a version allowing NULL).
  • No other thread may access the value pointed to by cptr until the function returns.
  • Ownership of the value remains with the caller.
Examples found in repository?
examples/bytebuf.rs (line 58)
57pub unsafe extern "C" fn byte_buffer_push(bb: *mut byte_buffer_t, b: u8) {
58    unsafe { UnboxedByteBuffer::with_ref_mut_nonnull(bb, |bb| bb.0.push(b)) }
59}
Source

pub unsafe fn return_val(rval: RType) -> CType

Return a CType containing rval, moving rval in the process.

§Safety
  • The caller must ensure that the value is eventually freed.
Examples found in repository?
examples/bytebuf.rs (line 28)
27pub unsafe extern "C" fn byte_buffer_new() -> byte_buffer_t {
28    unsafe { UnboxedByteBuffer::return_val(ByteBuffer(Vec::new())) }
29}
30
31/// Initialize the given byte_buffer_t to an empty value.
32#[no_mangle]
33pub unsafe extern "C" fn byte_buffer_init(bb: *mut byte_buffer_t) {
34    unsafe { UnboxedByteBuffer::to_out_param_nonnull(ByteBuffer(Vec::new()), bb) }
35}
36
37/// Free a byte_buffer_t.
38#[no_mangle]
39pub unsafe extern "C" fn byte_buffer_free(bb: *mut byte_buffer_t) {
40    let bb = unsafe { UnboxedByteBuffer::take_ptr_nonnull(bb) };
41    drop(bb); // just to be explicit
42}
43
44/// Checksum a byte_buffer_t's contents by XOR'ing all bytes together.
45#[no_mangle]
46pub unsafe extern "C" fn byte_buffer_checksum(bb: *const byte_buffer_t) -> u8 {
47    unsafe {
48        UnboxedByteBuffer::with_ref_nonnull(bb, |bb| {
49            // ok, not the most exciting "checksum"!
50            bb.0.iter().copied().reduce(|a, b| a ^ b).unwrap_or(0)
51        })
52    }
53}
54
55/// Add a byte to the byte buffer.
56#[no_mangle]
57pub unsafe extern "C" fn byte_buffer_push(bb: *mut byte_buffer_t, b: u8) {
58    unsafe { UnboxedByteBuffer::with_ref_mut_nonnull(bb, |bb| bb.0.push(b)) }
59}
60
61/// Combine two byte buffers, returning a new byte buffer containing the bytes
62/// from both inputs.  This function consumes its inputs and they must not be
63/// used after it returns.
64#[no_mangle]
65pub unsafe extern "C" fn byte_buffer_combine(
66    bb1: *mut byte_buffer_t,
67    bb2: *mut byte_buffer_t,
68) -> byte_buffer_t {
69    let mut bb1 = unsafe { UnboxedByteBuffer::take_ptr_nonnull(bb1) };
70    let bb2 = unsafe { UnboxedByteBuffer::take_ptr_nonnull(bb2) };
71
72    // modify bb1 in place (but it's not in the caller's location anymore)
73    bb1.0.extend(&bb2.0[..]);
74    unsafe { UnboxedByteBuffer::return_val(bb1) }
75}
Source

pub unsafe fn to_out_param(rval: RType, arg_out: *mut CType)

Initialize the value pointed to arg_out with rval, “moving” rval into the pointer.

If the pointer is NULL, rval is dropped. Use Unboxed::to_out_param_nonnull to panic in this situation.

§Safety
  • The caller must ensure that the value is eventually freed.
  • If not NULL, arg_out must point to valid, properly aligned memory for CType.
Source

pub unsafe fn to_out_param_nonnull(rval: RType, arg_out: *mut CType)

Initialize the value pointed to arg_out with rval, “moving” rval into the pointer.

If the pointer is NULL, this method will panic.

§Safety
  • The caller must ensure that the value is eventually freed.
  • arg_out must not be NULL and must point to valid, properly aligned memory for CType.
Examples found in repository?
examples/bytebuf.rs (line 34)
33pub unsafe extern "C" fn byte_buffer_init(bb: *mut byte_buffer_t) {
34    unsafe { UnboxedByteBuffer::to_out_param_nonnull(ByteBuffer(Vec::new()), bb) }
35}
Source§

impl<RType: Sized + Default, CType: Sized> Unboxed<RType, CType>

Source

pub unsafe fn with_ref<T, F: FnOnce(&RType) -> T>(cptr: *const CType, f: F) -> T

Call the contained function with a shared reference to the value.

If the given pointer is NULL, the contained function is called with a reference to RType’s default value, which is subsequently dropped.

§Safety
  • If not NULL, cptr must point to a valid CType value.
  • No other thread may mutate the value pointed to by cptr until the function returns.
  • Ownership of the value remains with the caller.
Source

pub unsafe fn with_ref_mut<T, F: FnOnce(&mut RType) -> T>( cptr: *mut CType, f: F, ) -> T

Call the contained function with an exclusive reference to the data type.

If the given pointer is NULL, the contained function is called with a reference to RType’s default value, which is subsequently dropped.

§Safety
  • If not NULL, cptr must point to a valid CType value.
  • No other thread may access the value pointed to by cptr until the function returns.
  • Ownership of the value remains with the caller.
Source

pub unsafe fn take_ptr(cptr: *mut CType) -> RType

Take a pointer to a CType and return an owned value.

This is similar to Unboxed::take_ptr_nonnull, but if given a NULL pointer will return the default value.

§Safety
  • If not NULL, cptr must point to a valid CType value.
  • The memory pointed to by cptr is uninitialized when this function returns.

Auto Trait Implementations§

§

impl<RType, CType> Freeze for Unboxed<RType, CType>

§

impl<RType, CType> RefUnwindSafe for Unboxed<RType, CType>
where RType: RefUnwindSafe, CType: RefUnwindSafe,

§

impl<RType, CType> Send for Unboxed<RType, CType>
where RType: Send, CType: Send,

§

impl<RType, CType> Sync for Unboxed<RType, CType>
where RType: Sync, CType: Sync,

§

impl<RType, CType> Unpin for Unboxed<RType, CType>
where RType: Unpin, CType: Unpin,

§

impl<RType, CType> UnwindSafe for Unboxed<RType, CType>
where RType: UnwindSafe, CType: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.