Making a Vec<f64> safe with extern

So, I've found myself in a bit of a sticky situation with a project I've been working on. I have a series of functions which pass around Vectors to do calculations on them, then hands the original vector along with the resulting vector to flutter to display.

When using 'pub extern "C" before the function, the compiler flags the structure Vector as not FFI safe.

example of code:

#[no_mangle]
pub extern "C" fn x_squared (x_vector: Vec<f64>, lower: f64, upper: f64) -> Vec<f64> {
    let y_vector: Vec<f64> = x_vector.into_iter().map(|x| x * x).collect();
    y_vector.into_iter().map(|y| if y >= lower && y <= upper  { y } else { std::f64::NAN } ).collect() 
}

#[no_mangle]
pub extern "C" fn coord_vector_maker (f: &dyn Fn(Vec<f64>, f64, f64) -> Vec<f64>, x_lower: f64, x_upper: f64, y_lower: f64, y_upper: f64, precision: f64) -> (Vec<f64>, Vec<f64>) {
    (x_vector_maker(x_lower, x_upper, precision), f(x_vector_maker(x_lower, x_upper, precision), y_lower, y_upper))
}

#[no_mangle]
pub extern "C" fn x_vector_maker (x_lower: f64, x_upper: f64, precision: f64) -> Vec<f64> {
    let mut x: f64 = x_lower;
    let x_bound_bredth: f64 = &x_upper.ceil() - &x_lower.floor(); 
    let capacity: usize = (x_bound_bredth * (1.0 / &precision) ).ceil() as usize;
    let mut x_vector: Vec<f64> = Vec::with_capacity(capacity);
    while x <= x_upper { 
        x_vector.push(x);
        x = x + precision;
    }
    x_vector.shrink_to_fit();
    x_vector
}

How do I fix this error (and any others that you might see)?

(Also, any suggestions on getting rid of that double while call would be fantastic)

That error means the memory layout of the Vec<T>(and also &dyn Fn) is unspecified so it's not possible to call those functions from C code. Why do you want your functions to be extern "C"?

1 Like

Because I want them to be able to be called from flutter, so that the things in the vector can be displayed there (as a graph in this particular case, as can probably be guessed).

How would one go about specifying the memory layout here, I was under the impression that I was pointing at allocated memory, but I think there's something about memory layouts I'm missing.

I suppose another important question would be how to deal with the &dyn Fn.

edit: a word

Since you're using C interface, you need a bit of C tricks here.

The Vec<T> is a struct which stores its buffer pointer, length, and capacity. It's memory layout is unspecified so we can't know their order in memory. But you can break it into parts and reassemble them. Raw pointers and usizes are C-safe types.

Trait objects are not a thing in C, but function pointers are. It's common in C world that callback function pointers takes extra void* argument for user provided context.

And also tuples are not a C-safe type. Most types in Rust, unless specified as #[repr(C)], doesn't have specified memory layout so we can apply more aggressive layout optimizations in future. Standard C trick to return multiple values is, to take some out-pointers and write values into them.

Last thing to note is the standard C tricks don't play well with Rust's guaranteed-safety paradigm. For example the caller may pass some arbitrary value including NULL to those out pointers, in that case writing to them results UB and all the nasty things can happen. In general, FFI is not safe. Recommended pattern is to write all your logics in safe Rust and call them from logic-less FFI adapter functions which are defined as pub unsafe extern "C".

use std::ffi::c_void;

#[repr(C)]
pub struct MyVec {
    ptr: *mut f64,
    length: usize,
    capacity: usize,
}

pub unsafe extern "C" fn coord_vector_maker(
    f: fn(MyVec, f64, f64, *mut c_void) -> MyVec, ctx: *mut c_void,
    ret1: *mut MyVec, ret2: *mut MyVec,
    x_lower: f64, x_upper: f64, y_lower: x64, y_upper: f64, precision: f64,
) {
    std::ptr::write(ret1, x_vector_maker(x_lower, x_upper, precision));
    std::ptr::write(ret2, f(x_vector_maker(x_lower, x_upper, precision), y_lower, y_upper, ctx));
}
2 Likes

Thank you so much, this FFI business is very tricky to get the hang of, but this makes a lot more sense. My only further question is with the #[no_mangle], or lack thereof in your code. Was this omitted for brevity or due to lack of necessity?
Context: The frontend will be asking for many other functions besides x_squared, and I want it to be easy to locate each specific function and apply it to this new structure.

Also, is there an equivalent of Vec::with_capacity(usize) for structures I'm defining?

Thanks in advance!

edit: added another sub-question.