I wrote the following code to turn a slice of u8 with length divisible by 4 into a slice of Offset, where Offset is a repr(transparent) wrapper around [u8; 4]:
#[repr(transparent)]
pub struct Offset([u8; 4]);
// (Not strictly related to my question, but left in to help explain the motivation here.)
impl Into<u32> for Offset {
fn into(self) -> u32 {
u32::from_be_bytes(self.0)
}
}
fn offsets_from_bytes<'a>(bytes: &'a [u8]) -> &'a [Offset] {
assert!(bytes.len() % 4 == 0);
let data = bytes.as_ptr() as *const Offset;
let len = bytes.len() / 4;
unsafe { std::slice::from_raw_parts(data, len) }
}
My thinking was that offsets_from_bytes is sound because of the repr(transparent), the length check, and the fact that [u8; 4] has the same alignment as u8 and there are no padding bytes anywhere. However, the std::slice::from_raw_parts documentation says, in part:
data must point to len consecutive properly initialized values of type T .
which is not true in this case. So, is the function sound as written? If not, how can I rewrite it soundly?
If you can be confident about the alignment being the same, I would also think that it's sound.
For what it's worth, that line was added in this PR, and the concern is initialization in particular. Earlier documentation comes from here; to my reading, anyway,
data must be valid for reads for len * mem::size_of::<T>() many bytes, and it must be properly aligned.
is the key part.
Perhaps the results of a PR to tweak the wording would give you a firmer answer
Other references:
I think this is a specific instance of this UCG issue. The conclusion there is that a [[T; N]; M] is not necessarily a [T; N * M], because [T; N] may have an alignment greater than T. But no other objections were raised, and I agree that if the alignment and size match, it's implied by...
I figure this is covered by the UCG's note about [T; N] and T having the same alignment when T is repr(C) the alignment of [T; N] when T is repr(C), which I interpret as including the scalar primitive types. (I actually didn't know that align_of::<[T; N]>() == align_of::<T>() was ever not guaranteed. TIL!)
[edit: the specific language is
If the element type is repr(C) the layout of the array is guaranteed to be the same as the layout of a C array with the same element type.
which I'm not really sure how to interpret. It's still hard for me to imagine how this wouldn't be sound.]
I might do this, yeah.
Thanks for the UCG pointers—I keep forgetting to look there if I can't find something on doc.rust-lang.org.
Yes, I know. I prefer to just implement Into<u32> in this specific case; u32::from(offset) doesn't "feel right" to me and doesn't correspond to how I expect/want people to use the code.