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.