Max
Max

Reputation: 2859

Creating a struct "view" over a buffer that includes dynamically sized arrays

In C I can use something like the following to create a struct "view" over data received over the network. I'm new to Rust. What would be the best way to do this in Rust?

#define JOB(_a_, _b_, _c_) \
struct { \
    int64 size; \
    int64 sizes[_a_]; \
    int16 destinationsCount[_a_]; \
    struct Address destinations[_b_]; \
    unsigned char data[_c_]; \
} __packed 

And use it like:

char * buf = ...;
JOB(1, 3, 1024) * job = (typeof(job))buf;
size_t size = sizeof(typeof(*job));

where the 1, 3 and 1024 are determined dynamically (which requires a GCC extension).

How can I best do this, ideally using the same struct "view" approach, in Rust? (The data is actually dramatically more complex than the above, and this is the nicest approach to work with.)

Here's what I've tried:

#[repr(C)]
struct Job<sizes, destinationsCount, destinations, data> {
    size: u64,
    sizes: sizes,
    destinationsCount: destinationsCount,
    destinations: destinations,
    program: data,
}
let job: Job<[u64;1], [u16;1], [Address;3], [u8;1024]>;

But of course as soon as I set the 1, 3 and 1024 dynamically it fails to compile.

Upvotes: 3

Views: 605

Answers (2)

Matthieu M.
Matthieu M.

Reputation: 299810

Functionally speaking you do not need a struct that is dynamically built, you can instead do with a view (though the struct could potentially be faster).

It is unclear to me whether you require ownership of the buffer or not, so I will assume borrowing here. The basic idea is exposed below; although of course you would want something a bit more sturdy...

struct JobViewer<'a> {
    nb_sizes: isize,
    nb_dests: isize,
    data_size: isize,
    data: &'a [u8],
}

impl<'a> JobViewer<'a> {
    fn new(nb_sizes: isize, nb_dests: isize, data_size: isize, data: &'a [u8])
        -> JobViewer<'a>
    {
        JobViewer {
            nb_sizes: nb_sizes, nb_dests: nb_dests, data_size: data_size, data: data
        }
    }

    fn size(&self) -> i64 {
        unsafe { *mem::transmute(self.raw()) }
    }

    fn sizes(&self) -> &[i64] {
        slice::from_raw_parts(
            unsafe { mem::transmute(self.raw().offset(8)) },
            self.nb_sizes
        )
    }

    fn destinations_count(&self) -> &[i16] {
        slice::from_raw_parts(
            unsafe { mem::transmute(self.raw().offset(8 + 8 * self.nb_sizes)) },
            self.nb_sizes
        )
    }

    fn destinations(&'b self) -> AddressViewer<'b> {
        AddressViewer::new(
            self.nb_dests,
            self.data[(8 + 8 * self.nb_sizes + 2 * self.nb_sizes)..]
        )
    }

    fn data(&self) -> &[u8] {
        self.data[
            (8 + 8 * self.nb_sizes + 2 * self.nb_sizes + ADDRESS_SIZE * self.nb_dests)..
        ]
    }

    unsafe fn raw(&self) -> *const u8 {
        let slice: raw::Slice<u8> = self.data;
        slice.data
    }
}

Note: as in your code, a change of endianness is NOT handled.

Upvotes: 4

eulerdisk
eulerdisk

Reputation: 4709

In Rust a struct can have only one dynamically sized field, the last one, and in general you can manipulate unsized types only via pointers or references.

See this.

Upvotes: -1

Related Questions