I've been banging my head against the wall for a while on this one. The context is that I'm trying to build an embedded VM for 8-bit AVR processors, but the question is not specific to the AVR.
Here's a stripped down bit of my code:
#[repr(C, packed(1))]
#[derive(Debug)]
struct Chunk<T> {
chunktype: u8,
color: u8,
size: u16,
object: T
}
impl<T> Deref for Chunk<T> {
type Target = T;
fn deref(&self) -> &T {
&self.object
}
}
fn deref16(s: &Chunk<i16>) -> &i16 {
&(s.object)
}
fn deref8(s: &Chunk<i8>) -> &i8 {
&(s.object)
}
I half understand that a reference pointing to an unaligned address could be bad. (but couldn't the compiler just generate code that works around this problem, at a performance penalty of course?)
What I don't understand is why I only get the error on the implementation of Deref
and on deref16
, but the compiler is happy with deref8
??
On the AVR the behaviour is slightly different. It doesn't seem to be a problem for whatever size I put in a specialised function like defer16
above. Even something like fn deref(s: &Chunk<[u8; 5]>) -> &[u8; 5] { &(s.object) }
is OK.
But.. the generic implementation of Deref
still gives the same error.
It's an 8 bit machine, so I was expecting that alignment wouldn't be a problem at all.
Is the compiler unhappy about the generic Deref
because can't be sure what type T
will be, and unfortunately isn't smart enough to realise on the AVR everything is properly aligned?
AFAIK Rust doesn't allow you to pack integers < 8 bits like C does, right?
Also it confuses me why the deref8
version works on the playground, but deref16
doesn't.
It generates an error when a field might not be aligned to what it's type requires, since you specify packed(1)
that means your struct doesn't have any alignment requirement, so it's fields can't be aligned more strictly than align(1)
.
For u8
and [u8; N]
that's trivially true.
For i16
that depends on your target specification, the AVR chips (usually with a custom target specification) have a data-layout of something like
e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8
particularly the i16:8
means that 16 bit integers use the "abi" 8
(are aligned to 8-bits), converted to Rust terms that's an alignment of 1, which also is compatible with packed(1)
.
On the generic version, there is no way to tell the alignment of the arbitrary type, so it can't be "known to be the same".
Also
It's an 8 bit machine, so I was expecting that alignment wouldn't be a problem at all.
Is generally a fallacy, you're not programming for any specific machine, you're programming for the Rust Abstract Machine and on it references always have to be aligned to whatever the type specifies.
See also: