I have a Message
struct that contains some payload and some metadata:
struct Message<T: Serialise> {
id: u8,
payload: T,
checksum: u8
}
The payload can be one of a fixed number of structs with differing sizes, and the specific struct used is dependent on the id field. The payload implements my Serialise
trait that returns a u8
array sized for the struct.
trait Serialise {
//half working at the moment - need to provide the struct size as a literal.
fn serialise<const COUNT: usize>() -> [u8; COUNT];
}
I would like to also implement Serialise
for my message struct (and just have it call the payload serialise()
method), so that a single call to Message.serialise()
will produce a u8
array with length equal to the payload size the metadata size.
It's my understanding that the size of the payload and the message should be known at compile time, and I should be able to create an array with the appropriate size using const generics. I suspect that at the moment I haven't clearly defined that anything that implements Serialise
will have a static size.
Assuming this is possible, how would I go about producing an array at compile time based on the size of my two structs?
CodePudding user response:
What you are asking for heavily depends on generic const expressions, which are not stabilized yet.
Even then, I think getting the size of a struct into a const generic is not a trivial task, if it is even possible.
I tried, and this is how far I've gotten:
#![feature(generic_const_exprs)]
#[derive(Debug)]
struct Message<T> {
id: u8,
payload: T,
checksum: u8,
}
trait Serialise {
const COUNT: usize;
fn serialise(&self) -> [u8; Self::COUNT];
}
// Dummy payload: u32
impl Serialise for u32 {
const COUNT: usize = 4;
fn serialise(&self) -> [u8; 4] {
self.to_be_bytes()
}
}
impl<T> Serialise for Message<T>
where
T: Serialise,
{
const COUNT: usize = T::COUNT 2;
fn serialise(&self) -> [u8; Self::COUNT] {
let mut content = [0u8; Self::COUNT];
content[0] = self.id;
content[Self::COUNT - 1] = self.checksum;
//content[1..Self::COUNT - 1].copy_from_slice(&self.payload.serialise());
content
}
}
fn main() {
let msg = Message::<u32> {
id: 10,
payload: 12345,
checksum: 32,
};
println!("msg: {:?}", msg);
let msg_serialized = msg.serialise();
println!("serialized: {:?}", msg_serialized);
}
msg: Message { id: 10, payload: 12345, checksum: 32 }
serialized: [10, 0, 0, 0, 0, 32]
But when add the line that generates the payload part of the message serialization, I get:
error: unconstrained generic constant
--> src\main.rs:32:67
|
32 | content[1..Self::COUNT - 1].copy_from_slice(&self.payload.serialise());
| ^^^^^^^^^
|
= help: try adding a `where` bound using this expression: `where [(); Self::COUNT]:`
note: required by a bound in `Serialise::serialise`
--> src\main.rs:12:33
|
12 | fn serialise(&self) -> [u8; Self::COUNT];
| ^^^^^^^^^^^ required by this bound in `Serialise::serialise`
Which doesn't make much sense and is most likely a bug with the generic_const_exprs
feature. Bugs like this are expected while the feature is not stabilized yet.
The compiler even warns you about it:
warning: the feature `generic_const_exprs` is incomplete and may not be safe to use and/or cause compiler crashes
--> src\main.rs:1:12
|
1 | #![feature(generic_const_exprs)]
| ^^^^^^^^^^^^^^^^^^^
|
= note: see issue #76560 <https://github.com/rust-lang/rust/issues/76560> for more information
= note: `#[warn(incomplete_features)]` on by default
I personally would solve it by using a Vec
instead:
#[derive(Debug)]
struct Message<T> {
id: u8,
payload: T,
checksum: u8,
}
trait Serialise {
fn serialise(&self) -> Vec<u8>;
}
// Dummy payload: u32
impl Serialise for u32 {
fn serialise(&self) -> Vec<u8> {
self.to_be_bytes().to_vec()
}
}
impl<T> Serialise for Message<T>
where
T: Serialise,
{
fn serialise(&self) -> Vec<u8> {
let mut result = Vec::new();
result.push(self.id);
result.extend_from_slice(&self.payload.serialise());
result.push(self.checksum);
result
}
}
fn main() {
let msg = Message::<u32> {
id: 10,
payload: 12345,
checksum: 32,
};
println!("msg: {:?}", msg);
let msg_serialized = msg.serialise();
println!("serialized: {:?}", msg_serialized);
}
msg: Message { id: 10, payload: 12345, checksum: 32 }
serialized: [10, 0, 0, 48, 57, 32]
There is no real gain in using an array over a vector here.