There is a Rust library that provides three functions:
- a constructor for a vector of floats
- a function to push a number to the vector
- a function that sums over the vector
Like this:
#[no_mangle]
pub extern "C" fn initialize_vector() -> *mut Vec<f32> {
Box::into_raw(Box::new(Vec::<f32>::new()))
}
#[no_mangle]
pub extern "C" fn push_to_vector(ptr: *mut Vec<f32>, x: f32) -> *mut Vec<f32> {
if ptr.is_null() { return ptr; }
let vector_box = unsafe { Box::from_raw(ptr) };
let mut example_vector = *vector_box;
example_vector.push(x);
Box::into_raw(Box::new(example_vector))
}
#[no_mangle]
pub extern "C" fn sum_vector(ptr: *mut Vec<f32>) -> f32 {
if ptr.is_null() { return 0.0; }
let vector_box = unsafe { Box::from_raw(ptr) };
let example_vector = *vector_box;
return example_vector.iter().sum();
}
initialize_vector
creates a Vec
and wraps it in a Box
, returning the raw pointer to the caller. The caller is now responsible for appropriately freeing the allocation.
push_to_vector
takes ownership of the Box<Vec<f32>>
, moves the Vec
out of the Box
, freeing the memory allocated by Box
in the process. It then adds an item to the vector, creates a brand new Box
(with associated allocation) around the Vec
and returns it.
sum_vector
also takes ownership of the Box<Vec<f32>>
, moves the Vec
out of the Box
and frees the memory allocation. Since ownership of the Vec
doesn't leave the function, Rust will automatically free the memory associated with the Vec
when the function exits.
The C# code that uses the library could look like this:
using System;
using System.Runtime.InteropServices;
internal class Blabla
{
[DllImport("Example")]
unsafe static public extern IntPtr initialize_vector();
[DllImport("Example")]
unsafe static public extern IntPtr push_to_vector(IntPtr ptr, float x);
[DllImport("Example")]
unsafe static public extern float sum_vector(IntPtr ptr);
static public unsafe void Main()
{
IntPtr vec_ptr = initialize_vector();
vec_ptr = push_to_vector(vec_ptr, (float) 2.2);
vec_ptr = push_to_vector(vec_ptr, (float) 3.3);
float result = sum_vector(vec_ptr);
// is the memory allocated for the vector in Rust freed right now?
Console.WriteLine(string.Format("Result: {0}", result));
Console.ReadLine();
}
}
In general, it is recommended to use SafeHandle
to finalize pointers returned by DLL functions, see e.g. this question or this example. I understand that the purpose of SafeHandle
is mostly to call a destructor in certain cases, when something unplanned happens.
Since it is not necessary to free the Rust vector after the sum_vector
function has been called, is it still advisable to use SafeHandle
in the given situation, and if yes - how? Or can I just leave the code as it is and everything is fine?