Hello, I want to use an enum to represent the elements of a multiplicative group (in the mathematical sense). For this enum, I want to define the Mul operator. To make the operation faster, I want to use the fact that the group is isomorphic to GF(2^2). That is, multiplication in this group is similar to bitwise-xor for two bits. Therefore, knowing the representation of the enum, I want my multiplication to be as simple as calling xor on two u8. Something like this
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub struct enum Pauli {
I = 0, // 00
X = 1, // 01
Y = 2, // 10
Z = 3, // 11
}
// Version 1
impl std::ops::Mul<Pauli> for Pauli {
type Output = Pauli;
fn mul(&self, rhs: Pauli) -> Self::Output {
use Pauli::*;
match (self, other) {
(I, p) | (p, I) => p,
(X, Y) | (Y, X) => Z,
(X, Z) | (Z, X) => Y,
(Y, Z) | (Z, Y) => X,
_ => I // In this case self == rhs.
}
}
}
// Version 2
impl std::ops::Mul<Pauli> for Pauli {
type Output = Pauli;
fn mul(self, rhs: Pauli) -> Self::Output {
// SAFETY: bitwise xor of two numbers chosen from 0, 1, 2 and 3
// always return a number in the same range.
unsafe {
std::mem::transmute::<Pauli>(
std::mem::transmute::<u8>(self)
^ std::mem::transmute::<u8>(rhs)
)
}
}
}
The first version is really easy to read, but I doubt the compiler will figure out the optimization. In the second version, I am imposing the optimization myself, but this part of the documentation of transmute makes my doubt.
transmute
is semantically equivalent to a bitwise move of one type into another. It copies the bits from the source value into the destination value, then forgets the original.
In that case, am I paying the cost of three copies?
Also, is there a better way to implement the multiplication?