I'm a rust newbie making a wrapper crate for a small C library that uses geometric 2D points. To the C lib, the points are passed as pointers to length-2 arrays [x, y]. The data is not mutated by the c library.
// my library:
mod ffi {
#[link(name = "thelib")]
extern "C" { pub fn c_function(p1: *const f64, p2: *const f64) -> f64; }
}
What is a way of writing the API for this, in a way that lets the user re-use his own Point type, and is still performant and idiomatic (I believe this is a case where I have to pick two...)
A caller wouldn't want to take his 10000 points and create an array of 10000 with a type from my library, if it's avoidable (future additions to my API will include methods that take long arrays of data).
For my first attempt, I simply made a struct in my library with [repr(C)]
and passed a reference to the c method, e.g.
// my library:
[repr(C)]
struct Point { x: f64, y: f64 }
impl Point {
pub fn wrapper_method(&self, p: &Self) -> f64 {
unsafe { ffi::c_function(&p.x, &self.x) }
}
}
This works, but isn't very ergonomic: I'm imposing my Point type on the caller.
The scenario I'm trying to solve is that different calling code will have different point types, but they will normally be similar to one of these:
// calling code:
struct ExamplePointStruct { x: f64, y:f64 }
// or
struct ExamplePointTuple(f64, f64);
So for my next attempt I'm trying this: I make a trait and a struct
// my library:
#[derive(Debug)]
#[repr(C)]
struct Coords { x: f64, y: f64 }
trait Point: Sized {
fn coords(&self) -> &Coords;
fn wrapper_method(&self, p: &Self) -> f64 {
let c1 = p.coords();
let c2 = self.coords();
unsafe { ffi::c_function(&c1.x, &c2.x) }
}
}
Theoretically, the calling code should now have the option to create the Coords struct to pass my library, or if they are concious about performance it should be possible to just fool the compiler that their two-floats-in-a-row are in fact the coords, through some voodoo such as
// calling code:
#[repr(C)]
struct ExamplePointStruct { x: f64, y: f64 }
// Safe/slower:
impl Point for ExamplePointStruct {
fn coords(&self) -> &Coords {
&Coords{ x: self.x, y: self.y }
}
}
// Unsafe/faster:
impl Point for ExamplePointStruct {
fn coords(&self) -> &Coords {
unsafe { mem::transmute::<&ExamplePointStruct, &Coords>(&self) } // ??
}
}
Does any of this make sense at all? Is this a very peculiar special case I'm doing (trying to accomodate the callers' unknown type, for which I think I can guess the layout only)? Does the trait-and-type thing look reasonable? Are there other more idiomatic ways to do this e.g. with some conversion trait?
Thanks for any pointers or references...