简体   繁体   中英

Is it possible to access elements of a struct using pointers in Rust?

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.

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. Useslice::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) }
    }
}

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);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM