This library offers [ForeignVec
], a zero-cost abstraction to store either [Vec<T>
]
or an immutable region aligned with T
allocated by an external allocator.
The primary use-case of this library is when you have an in-memory representation in both Rust and other languages and you have a specification to share (immutable) vectors across language boundaries at zero cost, via FFI.
In this scenario, you may want to still offer all the benefits of Rust's Vec
when it comes to mutable access, while providing a read-only access when the
data came from a foreign interface. In other words, given
- an in-memory format
- an FFI specification to share immutable memory regions at zero cost at language boundaries
then, [ForeignVec
] offers an interface to
- allow zero-cost immutable access via
core::ops::Deref<T>
toVec<T>
or the foreign vector - allow access to
&mut Vec<T>
when it is allocated by Rust
The crucial point here is "zero-cost immutable access". The usual idiom
here is to have an enum
with two variants, Native(Vec<T>)
and another.
However, such enum incurs a significant (+50%
) cost when deferring the enum
into &[T]
.
The complete test:
use foreign_vec::ForeignVec;
// say that we have a foreign struct allocated by an external allocator (e.g. C++)
// owning an immutable memory region
#[repr(C)]
struct Foreign {
ptr: *const i32,
length: usize,
// this is usually created at the FFI boundary; `capacity` is usually "hidden" in that
// it could contain a C++ `shared_ptr` or something else describing the region
// "on the other side".
capacity: usize,
}
// whose drop calls an external function that deallocates the region
impl Drop for Foreign {
fn drop(&mut self) {
// mocking an external deallocation
unsafe { Vec::from_raw_parts(self.ptr as *mut i32, self.length, self.capacity) };
}
}
// The type that we use on the library uses `foreign_vec`
// this could be a generic over `T` when the in-memory format supports multiple types.
type MyForeignVec = ForeignVec<Foreign, i32>;
#[test]
fn test_vec() {
// we can use it with `Vec`:
let expected: &[i32] = &[1, 2];
// when we have a vector, we can use `.into()`
let vec = expected.to_vec();
let mut vec: MyForeignVec = vec.into();
// deref works as expected
assert_eq!(&*vec, expected);
// debug works as expected
assert_eq!(format!("{:?}", vec), "[1, 2]");
// you can retrieve a mut vec (since it is allocated by Rust)
assert_eq!(vec.get_vec(), Some(&mut vec![1, 2]));
// this calls `Vec::drop`, as usual
drop(vec)
}
// this is just `Vec::into_raw_parts`, which is only available in unstable channels
fn into_raw_parts<T>(vec: Vec<T>) -> (*mut T, usize, usize) {
let r = (vec.as_ptr() as *mut T, vec.len(), vec.capacity());
std::mem::forget(vec);
r
}
#[test]
fn test_foreign() {
// on an externally allocated pointer (here from Rust, but a foreign call would do the same)
let expected: &[i32] = &[1, 2];
let a = expected.to_vec();
let (ptr, length, capacity) = into_raw_parts(a);
// this is usually created at the FFI boundary; `capacity` is usually "hidden" in that
// it could contain a C++ `shared_ptr` instead.
let a = Foreign {
ptr,
length,
capacity,
};
// create a `MyForeignVec` from a foreign that implements `Deref`.
let mut vec = unsafe { MyForeignVec::from_foreign(a.ptr, a.length, a) };
assert_eq!(&*vec, expected);
assert_eq!(vec.get_vec(), None);
// this calls `Foreign::drop`, which calls the foreign function
drop(vec);
}