Reputation: 135
I'm trying to create a generic function that transmutes a slice of bytes into an integer.
fn i_from_slice<T>(slice: &[u8]) -> Option<T>
where
T: Sized,
{
match slice.len() {
std::mem::size_of::<T>() => {
let mut buf = [0; std::mem::size_of::<T>()];
buf.copy_from_slice(slice);
Some(unsafe { std::mem::transmute_copy(&buf) })
}
_ => None,
}
}
Rust won't let me do that:
error[E0532]: expected tuple struct/variant, found function `std::mem::size_of`
--> src/lib.rs:6:9
|
6 | std::mem::size_of::<T>() => {
| ^^^^^^^^^^^^^^^^^^^^^^ not a tuple struct/variant
error[E0277]: the size for values of type `T` cannot be known at compilation time
--> src/lib.rs:7:31
|
7 | let mut buf = [0; std::mem::size_of::<T>()];
| ^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
|
= help: the trait `std::marker::Sized` is not implemented for `T`
= note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
= help: consider adding a `where T: std::marker::Sized` bound
= note: required by `std::mem::size_of`
Is there a way that I can statically know the size of T
?
Upvotes: 3
Views: 1351
Reputation: 42739
If your T
is an integer, you don't need any unsafe code, since there is from_ne_bytes
.
If you absolutely want a generic function, you can add a trait:
use std::convert::TryInto;
trait FromBytes: Sized {
fn from_ne_bytes_(bytes: &[u8]) -> Option<Self>;
}
impl FromBytes for i32 {
fn from_ne_bytes_(bytes: &[u8]) -> Option<Self> {
bytes.try_into().map(i32::from_ne_bytes).ok()
}
}
// Etc. for the other numeric types.
fn main() {
let i1: i32 = i_from_slice(&[1, 2, 3, 4]).unwrap();
let i2 = i32::from_ne_bytes_(&[1, 2, 3, 4]).unwrap();
assert_eq!(i1, i2);
}
// This `unsafe` usage is invalid, but copied from the original post
// to compare the result with my implementation.
fn i_from_slice<T>(slice: &[u8]) -> Option<T> {
if slice.len() == std::mem::size_of::<T>() {
Some(unsafe { std::mem::transmute_copy(&slice[0]) })
} else {
None
}
}
Upvotes: 5
Reputation: 13942
Is there a way that i can know statically the size of T?
Yes, you do know the size at compile time. But the size can vary and is not a constant. Instead of using a fixed-size array, you can use a vector which is a contiguous growable array.
Also, Sized
is an opt-out marker trait. All type parameters have an implicit Sized
bound. You don't need to spell that fact out.
You need a match arm guard to use pattern matching the way you did, but it is more straightforward to use if-else expression here.
All in all, this works:
fn i_from_slice<T>(slice: &[u8]) -> Option<T> {
let n = std::mem::size_of::<T>();
if slice.len() == n {
let mut buf = vec![0; n];
buf.copy_from_slice(slice);
Some(unsafe { std::mem::transmute_copy(&buf) })
} else {
None
}
}
Upvotes: 2
Reputation: 23244
You don't need your intermediate buffer, you can call transmute_copy
directly on the input slice. Moreover, as pointed out by @BenjaminLindley in the comments, you need to make sure that you transmute from the first item in the slice and not the fat pointer that is the slice itself:
fn i_from_slice<T>(slice: &[u8]) -> Option<T> {
if slice.len() == std::mem::size_of::<T>() {
Some(unsafe { std::mem::transmute_copy(&slice[0]) })
} else {
None
}
}
Upvotes: 2