C file:
typedef struct point {
int x;
int y;
} point;
typedef struct points {
int count;
point *array_of_points;
} points;
Rust file:
#[derive(Debug)]
#[repr(C)]
pub struct point {
x: c_int,
y: c_int,
}
#[derive(Debug)]
#[repr(C)]
pub struct points {
count: c_int,
array_of_points: [point],
}
#[no_mangle]
pub fn do_something(all_points: &points) {
for i in 0..all_points.count {
let crr_point = &all_points.array_of_points[i as usize];
println!("{:?}", crr_point);
}
}
In my C file, I allocate a lot of struct point and add them to array_of_points
, then I call the do_something
function.
How do I get each single point in array_of_points
in Rust?
Is way I defined the array_of_points
array in Rust correct?
When I run it, this strange outcome appears:
point { x: 0, y: -952095696 }
point { x: 32674, y: 101 }
and so on.
That is undefined behaviour. In the Rust version of that type, the member array_of_points
, of type point*
, was translated to a Rust unsized slice [point]
, which is not equivalent nor compatible. By adding a member of type [point]
, you are suggesting that point
has a variable number of trailing point
objects directly after its first member count
. This also makes points
an unsized type (or dynamically sized type).
The memory layout of points
in C should be the following:
[ int, point* ]
|
-->[ point, point, ...] // dynamically allocated
But that Rust definition was making this:
[ int, point, point, ... ] // unknown compile time size
The member in points
needs to be defined with a raw pointer:
#[derive(Debug)]
#[repr(C)]
pub struct points {
count: c_int,
array_of_points: *mut point,
}
Then do_something
should either dereference the pointer by an offset to retrieve each point:
#[no_mangle]
pub fn do_something(all_points: &points) {
for i in 0..all_points.count {
unsafe {
let crr_point = &*all_points.array_of_points.offset(i as isize);
println!("{:?}", crr_point);
}
}
}
Or construct a proper Rust slice out of the given parts in points
:
#[no_mangle]
pub fn do_something(all_points: &points) {
let point_array = unsafe {
std::slice::from_raw_parts(all_points.array_of_points, all_points.count as usize)
};
for crr_point in point_array {
println!("{:?}", crr_point);
}
}
Note how you need unsafe
code in any of these cases.
See also:
- How can I index C arrays in Rust?
- Pass a C array to a Rust function
Note: This answer is a little off, it propose you to use an another data layout for your C code.
You could change your C structure to something like this:
typedef struct point {
int x;
int y;
} point;
typedef struct points {
size_t len;
point points[];
} points;
This is called a flexible array member, a very nice and unknown C feature, that allows you to only make one allocation. The typical use-case matches your case.
Also, even in C int
is not a suitable type to represent a size, you should use size_t
.
You should also use bindgen to handle FAM, it's provide useful function like as_slice()
.
Given the following C code:
typedef struct point {
int x;
int y;
} point;
typedef struct points {
size_t len;
point points[];
} points;
struct points *new_points(size_t len) {
struct points *points = malloc(sizeof *points + sizeof *points->points * len);
if (points) {
points->len = len;
}
return points;
}
It currently generate:
#[repr(C)]
#[derive(Default)]
pub struct __IncompleteArrayField<T>(::std::marker::PhantomData<T>);
impl<T> __IncompleteArrayField<T> {
#[inline]
pub fn new() -> Self {
__IncompleteArrayField(::std::marker::PhantomData)
}
#[inline]
pub unsafe fn as_ptr(&self) -> *const T {
::std::mem::transmute(self)
}
#[inline]
pub unsafe fn as_mut_ptr(&mut self) -> *mut T {
::std::mem::transmute(self)
}
#[inline]
pub unsafe fn as_slice(&self, len: usize) -> &[T] {
::std::slice::from_raw_parts(self.as_ptr(), len)
}
#[inline]
pub unsafe fn as_mut_slice(&mut self, len: usize) -> &mut [T] {
::std::slice::from_raw_parts_mut(self.as_mut_ptr(), len)
}
}
impl<T> ::std::fmt::Debug for __IncompleteArrayField<T> {
fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
fmt.write_str("__IncompleteArrayField")
}
}
impl<T> ::std::clone::Clone for __IncompleteArrayField<T> {
#[inline]
fn clone(&self) -> Self {
Self::new()
}
}
impl<T> ::std::marker::Copy for __IncompleteArrayField<T> {}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct point {
pub x: ::std::os::raw::c_int,
pub y: ::std::os::raw::c_int,
}
#[repr(C)]
#[derive(Debug)]
pub struct points {
pub len: usize,
pub points: __IncompleteArrayField<point>,
}
extern "C" {
pub fn new_points(len: usize) -> *mut points;
}
Some lines omitted
With this binding you can do in Rust side:
#[no_mangle]
pub fn print_points(points: &points) {
for point in unsafe { points.points.as_slice(points.len) } {
println!("{:?}", point);
}
}
as_ptr()
allow to avoid the overhead of creating a temporary slice, so do as you like.
And in the C side:
#include <stdlib.h>
typedef struct point {
int x;
int y;
} point;
typedef struct points {
size_t len;
point points[];
} points;
struct points *new_points(size_t len);
void print_points(struct points *points);
int main(void) {
struct points *points = new_points(42);
int x = 0;
for (size_t i = 0; i < points->len; i++, x++) {
points->points[i] = (struct point){ .x = x, .y = -x};
}
print_points(points);
}
However remember that nothing of this is guarantee, you could run into a complete undefined behavior, be careful.
#[derive(Debug)]
#[repr(C)]
pub struct points {
count: c_int,
array_of_points: [point],
}
You tell to the compiler that array_of_points
is a valid slice, but it's not so your code:
#[no_mangle]
pub fn do_something(all_points: &points) {
for i in 0..all_points.count {
let crr_point = &all_points.array_of_points[i as usize];
println!("{:?}", crr_point);
}
}
is completely undefined behavior. I don't think there is a way to create such thing in C side, I didn't find one.