How do I write a generic function to create a vector of them from a vector of bytes?
I tried the below, but I am struggling to make it compile. Note that I do not assume a specific machine endianess, thus not performing a direct pointer transmutation.
use std::convert::TryInto;
pub unsafe trait NativeType: Sized + Copy {
type Bytes: AsRef<[u8]>;
fn from_le_bytes(bytes: Self::Bytes) -> Self;
}
unsafe impl NativeType for u32 {
type Bytes = [u8; std::mem::size_of::<Self>()];
#[inline]
fn from_le_bytes(bytes: Self::Bytes) -> Self {
Self::from_le_bytes(bytes)
}
}
unsafe impl NativeType for u64 {
type Bytes = [u8; std::mem::size_of::<Self>()];
#[inline]
fn from_le_bytes(bytes: Self::Bytes) -> Self {
Self::from_le_bytes(bytes)
}
}
fn read_buffer<'a, T: NativeType>(values: &'a [u8]) -> Vec<T>
where <T as NativeType>::Bytes: From<&'a[u8]> {
let chunks = values.chunks_exact(std::mem::size_of::<T>());
assert_eq!(chunks.remainder().len(), 0);
chunks.map(|chunk| {
let chunk: T::Bytes = chunk.try_into().unwrap();
T::from_le_bytes(chunk)
}).collect()
}
fn main() {
let a = vec![0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8];
read_buffer::<u32>(&a);
}
You are specifying the From trait bound in the interface, but later you use try_from() instead. Why?
First of all, in Rust, you can only use trait bounds that you specify upfront. Rust generics are not C++ templates. Second, arrays don't implement From<&[T]>, because what should that implementation do if the length of the slice is too short or too long?
Accordingly, if you use the correct trait bound, and add the Debug bound on the error type (which is needed because of unwrap()), then the code compiles.
Thank you for your help. That does indeed compile. However, it does introduce a non-trivial lifetime.
If a generic function calls read_buffer, the TryFrom<&'a [u8]> must now also be fulfilled on the parent function, which may not even have a lifetime. For example,
fn read_buffer<'a, T: NativeType>(values: &'a [u8]) -> Vec<T>
where
<T as NativeType>::Bytes: TryFrom<&'a [u8]>,
<<T as NativeType>::Bytes as TryFrom<&'a [u8]>>::Error: Debug,
{
let chunks = values.chunks_exact(std::mem::size_of::<T>());
assert_eq!(chunks.remainder().len(), 0);
chunks.map(|chunk| {
let chunk: T::Bytes = chunk.try_into().unwrap();
T::from_le_bytes(chunk)
}).collect()
}
fn read2<'a, T: NativeType>(length: usize) -> Vec<T>
where
<T as NativeType>::Bytes: TryFrom<&'a [u8]>,
<<T as NativeType>::Bytes as TryFrom<&'a [u8]>>::Error: Debug,
{
let values = vec![0u8; length];
read_buffer(&values)
}
fn main() {
let ints = read2::<u32>(10);
println!("{:?}", ints);
}
This won't compile in the example because values inside read2 can't fulfill the lifetime 'a. I get the idea that this is not needed because the bytes are being copied to over to T::Bytes, which lives in the stack, right?