Hi!
I have to implement a data-structure that has to support two different bit-orders that have to be specified at compile time and I'm fighting the type system to make it work while being readable and not using any unstable feature.
I've tried the following approaches:
- Generic const enum: But
adt_const_params
is still an incomplete feature
#![feature(adt_const_params)]
#[derive(PartialEq, Eq)]
enum BitOrder {
M2L,
L2M,
}
struct Reader<const E: BitOrder>;
fn main() {
let r = Reader::<{BitOrder::M2L}>;
}
- Generic const bool: But less readable than an enum
struct Reader<const M2L: bool>;
- Just having two structs: But code duplication
struct ReaderM2L;
struct ReaderL2M;
- Like bit_vec, having a marker type:
trait BitOrder{}
struct M2L;
struct L2M;
impl BitOrder for M2L {}
impl BitOrder for L2M {}
struct Reader<E: BitOrder>(std::marker::PhantomData<E>);
fn main() {
let r = Reader::<M2L>;
}
For now #4 has been the best approach, but now I've found a problem.
Different bit orders implies different operations to do on drop
and drop
can't be specialized.
My current plan is to be able to implement a comparison function between M2L
and L2M
and this is the best I come up with:
/// Inner private trait used to remove the possibility that anyone could
/// implement [`BitOrder`] on other structs
#[allow(private_in_public)]
trait BitOrderCore {}
impl<T: BitOrderCore> BitOrder for T {}
/// Marker trait to require that something is either [`L2M`] or
/// [`M2L`]
pub trait BitOrder: BitOrderCore {}
/// Marker type that represents LSB to MSB bit order
pub struct L2M;
/// Marker type that represents MSB to LSB bit order
pub struct M2L;
impl BitOrderCore for L2M {}
impl BitOrderCore for M2L {}
/// A trait for marker types comparison
trait BitOrderCmpCore<O: BitOrder>: BitOrderCore {
fn inner_cmp() -> bool {
// this is needed because I don't know a way to tell the type system that
// this trait will have to be impelmented for every variant.
unimplemented!()
}
}
impl BitOrderCmpCore<L2M> for L2M {
fn inner_cmp() -> bool {
true
}
}
impl BitOrderCmpCore<M2L> for M2L {
fn inner_cmp() -> bool {
true
}
}
impl BitOrderCmpCore<L2M> for M2L {
fn inner_cmp() -> bool {
false
}
}
impl BitOrderCmpCore<M2L> for L2M {
fn inner_cmp() -> bool {
false
}
}
/// The user facing trait
pub trait BitOrderCmp: BitOrderCmpCore<M2L> + BitOrderCmpCore<L2M> {
fn cmp<A: BitOrder>() -> bool
where
Self: BitOrderCmpCore<A>
{
<Self as BitOrderCmpCore<A>>::inner_cmp()
}
}
This way I can implement the drop function like:
impl<B: BitOrderCmp> std::ops::Drop for Reader<B> {
fn drop(&mut self) {
if B::cmp::<M2L>() {
// M2L logic
} else {
// L2M logic
}
}
}
My question is: Is there a less cursed way to do this?
Thanks