Reputation: 321
Consider the following:
// Just a sequence of adjacent fields of same the type
#[repr(C)]
#[derive(Debug)]
struct S<T> {
a : T,
b : T,
c : T,
d : T,
}
impl<T : Sized> S<T> {
fn new(a : T, b : T, c : T, d : T) -> Self {
Self {
a,
b,
c,
d,
}
}
// reinterpret it as an array
fn as_slice(&self) -> &[T] {
unsafe { std::slice::from_raw_parts(self as *const Self as *const T, 4) }
}
}
fn main() {
let s = S::new(1, 2, 3, 4);
let a = s.as_slice();
println!("s :: {:?}\n\
a :: {:?}", s, a);
}
Upvotes: 3
Views: 1175
Reputation: 3424
Yes, it is safe and portable, except for very large T
(fix below). None of the points listed in the safety section of the documentation for std::slice::from_raw_parts are a concern here:
The data pointer is valid for mem::size_of::<T>() * 4
, which is the size of S<T>
, and is properly aligned.
&self
parameter, and it is properly aligned, because S<T>
has (at least) the alignment of T
.The data parameter definitely points to 4 consecutive initialized T
s, because S
is marked #[repr(C)]
which is defined such that in your struct, no padding would be introduced. (repr(Rust)
makes no such guarantee).
The memory referenced is not mutated during the lifetime of the reference, which is guaranteed by the borrow checker.
The total size of the slice must not be greater than isize::MAX
. The code does not check this, so it is technically a safety hole. To be sure, add a check to as_slice
, before the unsafe
:
assert!(std::mem::size_of::<S<T>>() <= isize::MAX as _);
The check will normally be optimized out.
Upvotes: 5