Reputation:
I need an integral type that as a predefined limited range that includes 0, and want to implement like this:
#[repr(u8)]
pub enum X { A, B, C, D, E, F, G, H }
impl From<u8> for X {
fn from(x: u8) -> X {
unsafe { std::mem::transmute(x & 0b111) }
}
}
When I need the integer value, I would cast with as u8
. Arithmetic ops would be implemented by casting to u8
then converting back into the enum using from
. And because I limit the range with the bitand when converting from u8 to the enum, I'm always in range of the enum.
Some benefits I can see are that the range is known to the compiler so it can skip bounds checking, and enum optimizations such as representing Option<X>
as 1 byte.
A drawback I can see via assembly is that I incur and al, 7
every time I convert to enum, but I can live with that.
Is this a sound transmutation of u8
into the enum? What are other drawbacks of representing a limited range integer this way, if any?
Upvotes: 2
Views: 1151
Reputation: 58805
I don't think there is anything wrong with this transmutation, in that it is likely sound. However, I believe it is unnecessary.
If performance is critical for your application, you should test on your target arch, but I used the Rust playground to show the generated ASM (for whatever arch the playground runs on):
Your version:
#[repr(u8)]
#[derive(Debug)]
pub enum X { A, B, C, D, E, F, G, H }
impl From<u8> for X {
fn from(x: u8) -> X {
unsafe { std::mem::transmute(x & 0b111) }
}
}
#[no_mangle]
fn do_it_x(a: u8) -> X {
a.into()
}
Explicit match:
#[repr(u8)]
#[derive(Debug)]
pub enum Y { A, B, C, D, E, F, G, H }
impl From<u8> for Y {
fn from(y: u8) -> Y {
match y & 0b111 {
0 => Y::A,
1 => Y::B,
2 => Y::C,
3 => Y::D,
4 => Y::E,
5 => Y::F,
6 => Y::G,
7 => Y::H,
_ => unreachable!(),
}
}
}
#[no_mangle]
fn do_it_y(a: u8) -> Y {
a.into()
}
The resulting assembly (from the playground at least) is:
do_it_x:
pushq %rax
movb %dil, %al
movb %al, 7(%rsp)
movzbl %al, %edi
callq <T as core::convert::Into<U>>::into
movb %al, 6(%rsp)
movb 6(%rsp), %al
popq %rcx
retq
do_it_y:
pushq %rax
movb %dil, %al
movb %al, 7(%rsp)
movzbl %al, %edi
callq <T as core::convert::Into<U>>::into
movb %al, 6(%rsp)
movb 6(%rsp), %al
popq %rcx
retq
Upvotes: 3