This is what I want:
pub trait BinStruct {
const SIZE: usize;
fn from_bytes(raw: [u8; Self::SIZE]) -> Self;
fn to_bytes(&self) -> [u8; Self::SIZE];
}
Following complier notice, I got this:
pub trait BinStruct<const SIZE: usize> {
const SIZE: usize;
fn from_bytes(raw: [u8; SIZE]) -> Self;
fn to_bytes(&self) -> [u8; SIZE];
}
impl<const SIZE: usize> BinStruct<SIZE> for SomeStruct {
const SIZE: usize = std::mem::size_of::<Self>();
fn from_bytes(raw: [u8; SIZE]) -> Self {
// ...
}
fn to_bytes(&self) -> [u8; SIZE] {
// ...
}
}
When it comes to calling, it finally like this:
<SomeStruct as BinStruct<SIZE = SomeStruct::SIZE>>::to_bytes(&some_value))
And there are also errors. So what is the correct way to implement the trait in the beginning?
vague
November 15, 2022, 3:34am
2
pub trait BinStruct<const SIZE: usize>: Sized {
const SIZE: usize = std::mem::size_of::<Self>();
type Arr = [u8; SIZE];
fn from_bytes(raw: Self::Arr) -> Self;
fn to_bytes(&self) -> Self::Arr;
}
playground
Are you sure you want to have SIZE as generic parameter? That is, are you sure that you may allow both BinStruct<1> and BinStruct<2> to be implemented for some type?
1 Like
Obviously not, I just don't know how to implement it correctly.
Miiao
November 15, 2022, 5:26am
5
My personal opinion is that it’s a definitely bad idea. I cannot imagine a situation in which such code would be useful, so I’d recommend just to use core::mem::size_of.
The usage is a generic version of num::from_le_bytes()/to_le_bytes(), converting between structs and its bytes representation.
Miiao
November 15, 2022, 6:01am
7
Well, I’m currently working on Raw type that contains an array of bytes and can be converted to [u8; SIZE] using Deref. However, I still don’t understand why do you need an associated constant.
I believe what you really want is this issue; there's at least one work-around(-ish) mentioned within.
Incidentally, you're going to have to take some extra care whatever you do. It particular, it can't just be an in-place type conversion.
[u8; N] has an alignment of 1 but generic structs do not
I.e. from_bytes can't always succeed as written
Padding is uninitialized memory, so you can't just transmute to [u8; SZ] either
It'd be something like transmute to [MaybeUninit<u8>, SZ] and write the padding bytes first
Something something deconstructors, aliasing memory, Send and Sync, lifetimes, something something
Reading the traits and runtime checks of bytemuck may be informative even if it doesn't do what you want.
2 Likes
vague
November 15, 2022, 8:35am
9
Of course we don't need the generic parameter in nightly Rust :
#![feature(associated_type_defaults)]
#![feature(generic_const_exprs)]
#![allow(incomplete_features)]
pub trait BinStruct: Sized {
const SIZE: usize = std::mem::size_of::<Self>();
type Arr = [u8; Self::SIZE] where [(); Self::SIZE]:;
fn from_bytes(raw: Self::Arr) -> Self where [(); Self::SIZE]:;
fn to_bytes(&self) -> Self::Arr where [(); Self::SIZE]:;
}
impl BinStruct for S {
fn from_bytes(raw: Self::Arr) -> Self { }
fn to_bytes(&self) -> Self::Arr { }
}
But we do need that in stable Rust and it's much more verbose to write implementations and conveys a misleading intention:
pub trait BinStruct<const SIZE: usize>: Sized {
const SIZE: usize = std::mem::size_of::<Self>();
fn from_bytes(raw: [u8; SIZE]) -> Self;
fn to_bytes(&self) -> [u8; SIZE];
}
impl BinStruct<{ std::mem::size_of::<S>() }> for S {
fn from_bytes(raw: [u8; Self::SIZE]) -> Self { }
fn to_bytes(&self) -> [u8; Self::SIZE] { }
}
system
Closed
February 13, 2023, 8:35am
10
This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.