Maybe that's (in theory) the best solution, but it would bloat up my code even more. I'll consider doing it though or maybe find another solution.
But this got me startled. I checked the Rust reference on "Type Cast Expressions" and I don't find any explanation how type casts of wide-pointers work.
Consider the following example:
fn trns(slice: &[u16]) -> &[u8] {
let ptr = slice as *const [u16] as *const [u8];
unsafe { &*ptr }
}
fn main() {
let x = [1, 2, 3, 4, 5];
let y = trns(&x);
println!("{y:?}");
}
Output:
[1, 0, 2, 0, 3]
The output is, of course, dependent on endianess, and the example doesn't make much sense, but it should be sound because size_of::<u16>()
is never smaller than size_of::<u8>()
.
But is this example really guranteed to not be UB?
What if a future version of Rust stores the beginning and end-address for *const [u8]
but stores beginning and length for *const [u16]
? Then the above example would crash, unless we have some guarantees on what as *const [u16] as *const [u8]
really does (e.g. doing the conversion for us).
I didn't not find any explanation on how casting pointers really works in the case of slices, so I would like to state the hypothesis that maybe this is "technically" same unsound as using transmute
on references. (But maybe it is defined somewhere?)
To get back to the OP, …
… is there really a gurantee that if Bar
is #[repr(transparent)]
, we can safely do as *const [Bar] as *const [Foo]
? And if so, why?
I would assume we can rely on [Bar]
and [Foo]
having the same memory representation (but not even 100% sure on that), but does this imply that *const [Bar]
and *const [Foo]
have the same memory representation too? And even if that's the case, where is defined what the cast *const [Bar] as *const [Foo]
really does?
See also Rust's reference on its memory model.
If the hypothesis that this isn't well-defined holds, then the OP should use std::slice::from_raw_parts
:
And getting back to my transmutation, probably the right fix would be:
/// Implement [`Storable`] for variable-sized types that do not require
/// alignment and can be simply transmuted
macro_rules! impl_storable_transmute_varsize_trivial_cmp {
- ($type:ty, $owned:ty) => {
+ ($elem:ty, $type:ty, $owned:ty) => {
unsafe impl Storable for $type {
const CONST_BYTES_LEN: bool = false;
const TRIVIAL_CMP: bool = true;
type AlignedRef<'a> = &'a Self;
type BytesRef<'a> = &'a [u8];
fn to_bytes(&self) -> Self::BytesRef<'_> {
- unsafe { transmute::<&Self, &[u8]>(self) }
+ unsafe {
+ slice::from_raw_parts(self as *const Self as *const u8, size_of_val(self))
+ }
}
unsafe fn from_bytes_unchecked(bytes: &[u8]) -> Self::AlignedRef<'_> {
- transmute::<&[u8], &Self>(bytes)
+ slice::from_raw_parts(
+ bytes as *const [u8] as *const $elem,
+ bytes.len() / size_of::<$elem>(),
+ )
}
}
unsafe impl Storable for $owned {
const CONST_BYTES_LEN: bool = false;
const TRIVIAL_CMP: bool = true;
type AlignedRef<'a> = Owned<Self>;
type BytesRef<'a> = &'a [u8];
fn to_bytes(&self) -> Self::BytesRef<'_> {
- unsafe { transmute::<&$type, &[u8]>(&self) }
+ let slice: &$type = self;
+ unsafe {
+ slice::from_raw_parts(slice as *const $type as *const u8, size_of_val(slice))
+ }
}
unsafe fn from_bytes_unchecked(bytes: &[u8]) -> Self::AlignedRef<'_> {
- Owned(transmute::<&[u8], &$type>(bytes).to_owned())
+ Owned(
+ slice::from_raw_parts(
+ bytes as *const [u8] as *const $elem,
+ bytes.len() / size_of::<$elem>(),
+ )
+ .to_owned(),
+ )
}
}
};
}
-impl_storable_transmute_varsize_trivial_cmp!([bool], Vec<bool>);
-impl_storable_transmute_varsize_trivial_cmp!([i8], Vec<i8>);
-impl_storable_transmute_varsize_trivial_cmp!([u8], Vec<u8>);
-impl_storable_transmute_varsize_trivial_cmp!(str, String);
+impl_storable_transmute_varsize_trivial_cmp!(bool, [bool], Vec<bool>);
+impl_storable_transmute_varsize_trivial_cmp!(i8, [i8], Vec<i8>);
+impl_storable_transmute_varsize_trivial_cmp!(u8, [u8], Vec<u8>);
+//impl_storable_transmute_varsize_trivial_cmp!(/* what to put here? */, str, String); // this breaks now!
edit: I added / size_of::<$elem>()
after bytes.len()
Notice how the last macro call breaks now, which would force me to do what @alice proposed: