1

In C, we can access individual elements of a struct via pointers. How do we do the same in Rust?

The code below shows how to access elements using pointers in C.

#include <stdio.h>
#include <stdlib.h>

typedef struct __attribute__ ((packed)) {
    int a;
    int b;
    int c;
} Data;

Data* new_data(const int a, const int b, const int c) {
     Data* data = malloc(sizeof(Data));
     data->a = a;
     data->b = b;
     data->c = c;
     return data;
}

int main(int argc, char* argv[]) {
    Data* data = new_data(23, 35, 12);

    // accessing elements via pointers
    printf("\n --- Using pointers ---\n");
    printf("a: %d\n", *((int*)data + 0));
    printf("b: %d\n", *((int*)data + 1));
    printf("c: %d\n", *((int*)data + 2));

    // using pointer magic
    printf("\n --- Using pointer magic ---\n");
    printf("b: %d\n", *((int*)((char*)data + sizeof(int))));
    printf("c: %d\n", *((int*)((char*)data + sizeof(int) * 2)));

    // accessing elements via name
    printf("\n --- Using names ---\n");
    printf("a: %d\n", data->a);
    printf("b: %d\n", data->b);
    printf("c: %d\n", data->c);

    free(data);
    return 0;
}

The above is compiled using gcc and I'm aware it's also platform specific, but it's not my concern.

The below is what I currently have in Rust.

struct Data<T> {
    el: Vec<T>
}

impl <T> Data<T> where T: Copy {
    fn new(a: T, b: T, c: T) -> Self {
        let mut s = Self { el: Vec::with_capacity(3) };
        s.el.push(a);
        s.el.push(b);
        s.el.push(c);
        return s;
    }

    fn get_a(&self) -> T { self.el[0] }
    fn get_b(&self) -> T { self.el[1] }
    fn get_c(&self) -> T { self.el[2] }
}

fn main() {
    let mut data = Data::new(23, 35, 12);
    println!("data capacity: {:?}", data.el.capacity());

    println!("a: {:?}", data.get_a());
    println!("b: {:?}", data.get_b());
    println!("c: {:?}", data.get_c());
}

I'd like to be able to use

struct Data<T> {
    a: T,
    b: T,
    c: T
}

and access each element via their index.

ToninGuy3n
  • 308
  • 1
  • 10
  • The first one you're doing in C is not strictly legal - in C. – Antti Haapala -- Слава Україні Oct 11 '20 at 17:45
  • Does this answer your question? [How to get pointer offset in bytes?](https://stackoverflow.com/questions/40310483/how-to-get-pointer-offset-in-bytes) – trent Oct 11 '20 at 17:55
  • Also see [How to get a pointer to a containing struct from a pointer to a member?](https://stackoverflow.com/q/38819569/3650362). For the C version, see [Get pointer to object from pointer to some member](https://stackoverflow.com/q/33870219/3650362). – trent Oct 11 '20 at 17:56
  • 1
    I just noticed `__attribute__((packed))`. AFAIK even in C you cannot get a usable `int*` to a member of a `packed` struct, because they are not guaranteed to be aligned. Technically I believe it is OK here because `malloc` returns a pointer with the maximum alignment, but Rust's system allocator has no such guarantee (and doing it on a stack variable would be wrong in either language). – trent Oct 11 '20 at 18:04
  • @trentcl. I'm currently reading about [layouts](https://doc.rust-lang.org/reference/type-layout.html) I've had a quick look at some of the links you've posted but I'll need time to digest them. – ToninGuy3n Oct 11 '20 at 18:15
  • 1
    Here are some other options you might consider, depending on what you don't like about what you currently have: [1](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0ad939dd7d5ecafad880c0c9714edf66), [2](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=47fe799a048abbc23ddafd8814812501), and [3](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b40721156f07d4bff8b499ba4b49e25d) – trent Oct 11 '20 at 18:43
  • @trentcl The third one seems to be the closest to what I'm looking for. Would it be compatible with [avx](https://doc.rust-lang.org/edition-guide/rust-2018/simd-for-faster-computing.html)? – ToninGuy3n Oct 11 '20 at 18:59
  • Actually, I think the 3rd one is enough to answer this question since it uses raw pointers and it allows access to elements by indexing. Don't worry about the avx compatiblity - that should be my homework. Can you post it so I can mark this question as answered? – ToninGuy3n Oct 11 '20 at 19:34

2 Answers2

3

In the general case, there is no way to do this correctly in Rust today. However, your particular struct avoids some of the worst problems, making it safe to borrow the entire struct as a borrowed slice (&[T]). To do so you need to do three things:

  1. Mark the struct repr(C), but not repr(packed)! Packed structs are unaligned, and references must always be properly aligned.

  2. Check that the size of the struct is no larger than isize::MAX.

  3. Use slice::from_raw_parts to borrow a &[T] from the &Data<T>.

For a point-by-point justification of why this is sound, see Is it legal to cast a struct to an array?

#[repr(C)]
struct Data<T> {
    pub a: T,
    pub b: T,
    pub c: T,
}

impl<T> Data<T>
where
    T: Copy,
{
    fn new(a: T, b: T, c: T) -> Self {
        Data { a, b, c }
    }

    // N.B. you could also implement `AsRef<[T]>` and/or `Borrow<[T]>`, which
    // are used frequently in generic code
    fn as_slice(&self) -> &[T] {
        assert!(std::mem::size_of::<Self>() <= isize::MAX as _);
        // This `unsafe` block was copied from Stack Overflow without proving
        // its use is correct in this context, so it's almost certainly wrong
        unsafe { std::slice::from_raw_parts(self as *const _ as *const T, 3) }
    }
}
trent
  • 25,033
  • 7
  • 51
  • 90
0

Here's a test function to add confidence that casting to a slice works.

unsafe fn test_slice_data_equiv<T: Clone>(t: &T) {
    let data = Data { a: t.clone(), b: t.clone(), c: t.clone() };
    let slice: [T; 3] = [ t.clone(), t.clone(), t.clone()];
    fn as_u8_ptr<U>(r: &U) -> * const u8 {
        r as * const U as * const u8
    }
    let data_ptr = as_u8_ptr(&data);
    let slice_ptr = as_u8_ptr(&slice);
    
    assert_eq!(as_u8_ptr(&data.a).offset_from(data_ptr),
               as_u8_ptr(&slice[0]).offset_from(slice_ptr),
               "Data.a != slice[0]");
    assert_eq!(as_u8_ptr(&data.b).offset_from(data_ptr),
               as_u8_ptr(&slice[1]).offset_from(slice_ptr),
               "Data.b != slice[1]");
    assert_eq!(as_u8_ptr(&data.c).offset_from(data_ptr),
               as_u8_ptr(&slice[2]).offset_from(slice_ptr),
               "Data.c != slice[2]");
}

#[test]
fn test_some_offsets() {
    unsafe {
        test_slice_data_equiv(&0_u32);
        test_slice_data_equiv(&0_u64);
    }
}
NovaDenizen
  • 5,089
  • 14
  • 28
  • That way only looks nice on the surface but uses `match`. I'm looking for ways to use pointers so that data could be used by some `simd` libraries, while also being able to access elements by name. If you're interested, here are some links of what I'm looking into; [1](https://doc.rust-lang.org/1.26.0/std/arch/index.html), [2](https://software.intel.com/sites/landingpage/IntrinsicsGuide/#expand=100,106&techs=AVX2). – ToninGuy3n Oct 12 '20 at 06:03
  • Ok, you wanted to access the elements as a slice. I was confused by "access each element via their index". – NovaDenizen Oct 12 '20 at 18:15