Handling Rust Vectors (Vec<T>1
) in Java
Rust Side
In Rust, a Vec<T>
is a dynamically-sized array that includes metadata such as capacity and length. Working with vectors across FFI boundaries requires us to manage these fields carefully on both sides.
Example Rust Vector:
#![allow(unused)] fn main() { #[no_mangle] extern "C" fn create_vector() -> *mut Vec<i32> { Box::into_raw(Box::new(vec![10, 20, 30])) } #[no_mangle] extern "C" fn vector_push(vec: *mut Vec<i32>, value: i32) { unsafe { if let Some(vec) = vec.as_mut() { vec.push(value); } } } #[no_mangle] extern "C" fn vector_get(vec: *const Vec<i32>, index: usize) -> i32 { unsafe { if let Some(vec) = vec.as_ref() { vec[index] } else { 0 // Or some error handling } } } #[no_mangle] extern "C" fn vector_len(vec: *const Vec<i32>) -> usize { unsafe { if let Some(vec) = vec.as_ref() { vec.len() } else { 0 } } } }
Explanation:
create_vector
: Initializes a Vec<i32>
and returns a raw pointer to allow Java to manage the vector.
vector_push
: Provides functionality for adding elements to the vector, with error handling in case of null pointers.
vector_get
and vector_len
: Fetch elements from the vector and get its length, making direct access possible from Java.
Java Side
To handle Vec<T>
in Java:
- Define a
StructLayout
that represents the memory layout for a Rust vector (data pointer, length, and capacity). - Use
MethodHandles
to call Rust functions to manipulate the vector.
Example Java Code:
// Define the layout for Vec<i32>
StructLayout vecLayout = MemoryLayout.structLayout(
ValueLayout.ADDRESS.withName("ptr"), // Data pointer
ValueLayout.JAVA_LONG.withName("cap"), // Capacity
ValueLayout.JAVA_LONG.withName("len") // Length
);
// MethodHandles to call Rust functions
MethodHandle vectorPush = linker.downcallHandle(
symbolLookup.lookup("vector_push").get(),
FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.JAVA_INT)
);
MethodHandle vectorGet = linker.downcallHandle(
symbolLookup.lookup("vector_get").get(),
FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_LONG)
);
MethodHandle vectorLen = linker.downcallHandle(
symbolLookup.lookup("vector_len").get(),
FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.ADDRESS)
);
Explanation:
vecLayout
: Defines the structure of the Vec<T>
memory, including data pointer, length, and capacity.
MethodHandles
(vectorPush
, vectorGet
, vectorLen
): Enable Java to interact with the vector’s core functions.
Allocating and Using the Vector
var arena = Arena.ofConfined();
MemorySegment vecSegment = arena.allocate(vecLayout);
vectorPush.invokeExact(vecSegment, 42); // Push 42 to vector
long len = (long) vectorLen.invokeExact(vecSegment); // Get vector length
int value = (int) vectorGet.invokeExact(vecSegment, 0L); // Get first element
Explanation:
Arena Allocation: Using an Arena
for safe memory management.
Push, Length, and Get: MethodHandle
invocations facilitate direct manipulation of the Rust vector from Java.