Is using the type `[my_struct]` the correct way of

2019-08-13 07:44发布

问题:

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.

回答1:

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


回答2:

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.



标签: c struct rust ffi