【问题标题】:Generic implementation across the variants of an enum跨枚举变体的通用实现
【发布时间】:2021-10-31 19:58:13
【问题描述】:

我有一个枚举,其变体具有相同的结构(这些变体可以转换角色)并且出现在大型数据结构中。

我需要通过交换变体的角色来进行某些计算(一个相当长的程序)。 由于数据量大,我应该避免直接对数据进行这种排列。

为了最大限度地减少代码长度并使未来的演变更加可靠,我决定以通用方式进行编程。

到目前为止,我已经尝试过使用宏。 以下代码是我跨变体进行的通用实现的示例:(playground)

// enum with same variants shapes
#[derive(Debug, Clone)]
enum Choice{ 
    A{ val: f32, }, 
    B{ val: f32, }, 
}
use Choice::{A,B,};

// code to be implemented
trait MyTrait<const P: usize> { // P is a permutation parameter
    fn eval(&self) -> f32;
    fn zoom(&mut self);
    fn transmute(self) -> Self;
}

// variant permutation macros
macro_rules! choice_a { 
    (0 | val: $a:ident) => (A{val: $a}); 
    (1 | val: $a:ident) => (B{val: $a});
    (0 | val: ref $a:ident) => (A{val: ref $a}); 
    (1 | val: ref $a:ident) => (B{val: ref $a});
    (0 | val: ref mut $a:ident) => (A{val: ref mut $a}); 
    (1 | val: ref mut $a:ident) => (B{val: ref mut $a});
    (0 | val: $a:expr) => (A{val: $a}); 
    (1 | val: $a:expr) => (B{val: $a});
}
macro_rules! choice_b { 
    (0 | val: $a:ident) => (B{ val: $a}); 
    (1 | val: $a:ident) => (A{ val: $a});
    (0 | val: ref $a:ident) => (B{ val: ref $a}); 
    (1 | val: ref $a:ident) => (A{ val: ref $a});
    (0 | val: ref mut $a:ident) => (B{ val: ref mut $a}); 
    (1 | val: ref mut $a:ident) => (A{ val: ref mut $a});
    (0 | val: $a:expr) => (B{ val: $a}); 
    (1 | val: $a:expr) => (A{ val: $a}); 
}

// macro for generic implementation
macro_rules! mytrait {
    ($p:tt) => (
        impl MyTrait<$p> for Choice {
            fn eval(&self) -> f32 {
                match *self {
                    choice_a!{$p | val: ref va} => { va.powi(2) },
                    choice_b!{$p | val: ref va} => { va.sqrt() },
                }
            }
            fn zoom(&mut self) {
                match *self {
                    choice_a!{$p | val: ref mut va} => { *va *= 2.0; },
                    choice_b!{$p | val: ref mut va} => { *va /= 2.0; },
                }
            }
            fn transmute(self) -> Self {
                match self {
                    choice_a!{$p | val: va} => { choice_b!{$p | val: va + 1.0} },
                    choice_b!{$p | val: va} => { choice_a!{$p | val: va - 1.0} },
                }
            }
        }
    );
}
mytrait!(0); // no permutation case
mytrait!(1); // permutation case

fn main() {
    
    for x in vec![Choice::A{ val: 16.0}, Choice::B{ val: 16.0}] {
        let mut y = x.clone();
        println!("Variants roles are not permuted:");
        println!("y = {:?}", y);
        println!("eval y = {:?}", MyTrait::<0>::eval(&y));
        MyTrait::<0>::zoom(&mut y);
        println!("zoomed y = {:?}", y);
        let y = MyTrait::<0>::transmute(y);
        println!("transmuted zoomed y = {:?}", y);
        println!("-------------------------");
        println!("Variants roles are permuted:");
        let mut z = x;
        println!("z = {:?}", z);
        println!("eval z = {:?}", MyTrait::<1>::eval(&z));
        MyTrait::<1>::zoom(&mut z);
        println!("zoomed z = {:?}", z);
        let z = MyTrait::<1>::transmute(z);
        println!("transmuted zoomed z = {:?}", z);
        println!("=====================================");
    }
    
}

结果:

Variants roles are not permuted:
y = A { val: 16.0 }
eval y = 256.0
zoomed y = A { val: 32.0 }
transmuted zoomed y = B { val: 33.0 }
-------------------------
Variants roles are permuted:
z = A { val: 16.0 }
eval z = 4.0
zoomed z = A { val: 8.0 }
transmuted zoomed z = B { val: 7.0 }
=====================================
Variants roles are not permuted:
y = B { val: 16.0 }
eval y = 4.0
zoomed y = B { val: 8.0 }
transmuted zoomed y = A { val: 7.0 }
-------------------------
Variants roles are permuted:
z = B { val: 16.0 }
eval z = 256.0
zoomed z = B { val: 32.0 }
transmuted zoomed z = A { val: 33.0 }
=====================================

我的代码有效,但并不完全令人满意。 确实,我的 choice_a!choice_b! 宏不是进化的,它们的使用是僵化的,并且会产生整个模式或枚举的完整定义。 如果这些宏只产生枚举的名称会更好。 例如,最好能写成 choice_a!($p) { val: va - 1.0} 而不是 choice_a!{$p | val: va - 1.0},最好能写成 choice_b!($p){ val: ref mut va} 而不是 choice_b!{$ p | val: ref mut va}.

可以这样做吗? 除了宏还有其他方法可以跨变体进行这种通用实现吗?

【问题讨论】:

    标签: generics rust enums implementation variant


    【解决方案1】:

    不确定我是否在评论问题试图解决的问题(因此,如果我不正常,请忽略此答案)。但是,完全避免使用宏并尝试类似的方法不是更容易(或至少更具可读性)吗?

    
    // enum with same variants shapes
    #[derive(Debug, Clone)]
    enum Choice{
        A{ val: f32, },
        B{ val: f32, },
    }
    use Choice::{A,B,};
    
    // code to be implemented
    trait MyTrait<const P: bool> { // P is a permutation parameter
    fn eval(&self) -> f32;
        fn zoom(&mut self);
        fn transmute(self) -> Self;
    }
    
    impl Choice {
    
        fn as_val(&self) -> &f32 {
            match self {
                Choice::A { val } => val,
                Choice::B { val } => val,
            }
        }
    
        fn as_val_mut(&mut self) -> &mut f32 {
            match self {
                Choice::A { val } => val,
                Choice::B { val } => val,
            }
        }
    
        /// If P=false, primary choice is A, secondary B. If P=true, the values are switched.
        fn is_primary<const P: bool>(&self) -> bool {
            match self {
                Choice::A { .. } => !P,
                Choice::B { .. } => P,
            }
        }
    
        fn flip_type(self) -> Choice {
            match self {
                Choice::A { val } => Choice::B { val },
                Choice::B { val } => Choice::A { val }
            }
        }
    
    }
    
    impl <const P: bool> MyTrait<P> for Choice {
        fn eval(&self) -> f32 {
            if self.is_primary::<P>() {
                self.as_val().powi(2)
            } else {
                self.as_val().sqrt()
            }
        }
    
        fn zoom(&mut self) {
            if self.is_primary::<P>() {
                *self.as_val_mut() *= 2.0;
            } else {
                *self.as_val_mut() /= 2.0;
            }
        }
    
        fn transmute(self) -> Self {
            let is_primary = self.is_primary::<P>();
            let mut copy = self.flip_type();
            if is_primary {
                *copy.as_val_mut() += 1.0;
            } else {
                *copy.as_val_mut() -= 1.0;
            };
            copy
        }
    }
    
    
    fn main() {
    
        for x in vec![Choice::A{ val: 16.0}, Choice::B{ val: 16.0}] {
            let mut y = x.clone();
            println!("Variants roles are not permuted:");
            println!("y = {:?}", y);
            println!("eval y = {:?}", MyTrait::<false>::eval(&y));
            MyTrait::<false>::zoom(&mut y);
            println!("zoomed y = {:?}", y);
            let y = MyTrait::<false>::transmute(y);
            println!("transmuted zoomed y = {:?}", y);
            println!("-------------------------");
            println!("Variants roles are permuted:");
            let mut z = x;
            println!("z = {:?}", z);
            println!("eval z = {:?}", MyTrait::<true>::eval(&z));
            MyTrait::<true>::zoom(&mut z);
            println!("zoomed z = {:?}", z);
            let z = MyTrait::<true>::transmute(z);
            println!("transmuted zoomed z = {:?}", z);
            println!("=====================================");
        }
    
    }
    

    此代码为您提供相同的结果。 is_primary 检查应该编译为与原始匹配宏基本相同的内容,因此性能应该相似(但我可以检查您是否确定)。它稍长一些,但总的来说我觉得它更具可读性,而且新方法在实现 MyTrait 之外也很有用。

    【讨论】:

    • 感谢您的回答!我会考虑这个想法。现在在我的真实代码中,带有多个模式匹配和枚举定义实例的特征方法要长得多,并且我的枚举包含更多字段。所以我必须考虑它来适应它。话虽如此,我仍然很想知道是否有办法改进我的宏以进行模式处理或定义枚举。
    • 当然,我也对更好的宏解决方案感到好奇 :) 只是想提出这个问题,因为我个人花了很长时间才意识到在许多情况下我实际上可以这样做而不是宏/样板,并且由于编译器优化和 const 泛型,它在性能方面通常是“免费的”。
    • 定义“宏规则!b { (0 | $($t:tt) *) => (B{$($t) *}); (1 | $($t:tt) *) => (A{$($t) *}); }" 工作正常。游乐场:play.rust-lang.org/…
    【解决方案2】:

    用简单的宏改进回答:(playground)

    // enum with same variants shapes
    #[derive(Debug, Clone)]
    enum Choice{ 
        A{ val: f32, }, 
        B{ val: f32, }, 
    }
    use Choice::{A,B,};
    
    // variant permutation macros; space before * is required!
    macro_rules! a { 
        (0 | $($t:tt) *) => (A{$($t) *}); 
        (1 | $($t:tt) *) => (B{$($t) *});
    }
    macro_rules! b { 
        (0 | $($t:tt) *) => (B{$($t) *}); 
        (1 | $($t:tt) *) => (A{$($t) *});
    }
    
    // code to be implemented
    trait MyTrait<const P: usize> { // P is a permutation parameter
        fn eval(&self) -> f32;
        fn zoom(&mut self);
        fn transmute(self) -> Self;
    }
    
    // macro for generic implementation
    macro_rules! mytrait {
        ($p:tt) => (
            impl MyTrait<$p> for Choice {
                fn eval(&self) -> f32 {
                    match *self {
                        a!{$p | val: ref va} => { va.powi(2) },
                        b!{$p | val: ref va} => { va.sqrt() },
                    }
                }
                fn zoom(&mut self) {
                    match *self {
                        a!{$p | val: ref mut va} => { *va *= 2.0; },
                        b!{$p | val: ref mut va} => { *va /= 2.0; },
                    }
                }
                fn transmute(self) -> Self {
                    match self {
                        a!{$p | val: va} => { b!{$p | val: va + 1.0} },
                        b!{$p | val: va} => { a!{$p | val: va - 1.0} },
                    }
                }
            }
        );
    }
    mytrait!(0); // no permutation case
    mytrait!(1); // permutation case
    

    上一个答案:

    type Choice = (Variant,Data) 替换 enum Choice{ A{ val: f32, }, B{ val: f32, }, } >enum Variant{ A, B, } 和 struct Data { val: f32 }(playground)

    // variant label
    #[derive(Debug, Clone, Copy,)]
    enum Variant{ A, B, }
    use Variant::{A,B,};
    // data fields
    #[derive(Debug, Clone,)]
    struct Data { val: f32 }
    // enum like type
    type Choice = (Variant,Data);
    
    
    macro_rules! a { (0) => (A); (1) => (B); }
    macro_rules! b { (0) => (B); (1) => (A); }
    
    // code to be implemented
    trait MyTrait<const P: usize> { // P is a permutation parameter
        fn eval(&self) -> f32;
        fn zoom(&mut self);
        fn transmute(self) -> Self;
    }
    
    // macro for generic implementation
    macro_rules! mytrait {
        ($p:tt) => (
            impl MyTrait<$p> for Choice {
                fn eval(&self) -> f32 {
                    match *self {
                        (a!($p), Data{val: ref v}) => { v.powi(2) },
                        (b!($p), Data{val: ref v}) => { v.sqrt() },
                    }
                }
                fn zoom(&mut self) {
                    match *self {
                        (a!($p), Data{val: ref mut v}) => { *v *= 2.0; },
                        (b!($p), Data{val: ref mut v}) => { *v /= 2.0; },
                    }
                }
                fn transmute(self) -> Self {
                    match self {
                        (a!($p), Data{val: v}) => (b![$p], Data{val: v + 1.0}),
                        (b!($p), Data{val: v}) => (a![$p], Data{val: v - 1.0}),
                    }
                }
            }
        );
    }
    mytrait!(0); // no permutation case
    mytrait!(1); // permutation case
    

    【讨论】:

      猜你喜欢
      • 2020-02-29
      • 2019-05-24
      • 1970-01-01
      • 2021-12-16
      • 2014-11-10
      • 2019-11-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多