【问题标题】:How can I approximate method overloading?如何近似方法重载?
【发布时间】:2014-08-12 13:26:49
【问题描述】:

我正在为一个 API 建模,其中方法重载非常适合。我天真的尝试失败了:

// fn attempt_1(_x: i32) {}
// fn attempt_1(_x: f32) {}
// Error: duplicate definition of value `attempt_1`

然后我添加了一个枚举并完成了:

enum IntOrFloat {
    Int(i32),
    Float(f32),
}

fn attempt_2(_x: IntOrFloat) {}

fn main() {
    let i: i32 = 1;
    let f: f32 = 3.0;

    // Can't pass the value directly
    // attempt_2(i);
    // attempt_2(f);
    // Error: mismatched types: expected enum `IntOrFloat`

    attempt_2(IntOrFloat::Int(i));
    attempt_2(IntOrFloat::Float(f));
    // Ugly that the caller has to explicitly wrap the parameter
}

进行一些快速搜索,我有 found some references 谈论重载,所有这些似乎都以“我们不会允许这样做,但尝试使用特征”结束。所以我尝试了:

enum IntOrFloat {
    Int(i32),
    Float(f32),
}

trait IntOrFloatTrait {
    fn to_int_or_float(&self) -> IntOrFloat;
}

impl IntOrFloatTrait for i32 {
    fn to_int_or_float(&self) -> IntOrFloat {
        IntOrFloat::Int(*self)
    }
}

impl IntOrFloatTrait for f32 {
    fn to_int_or_float(&self) -> IntOrFloat {
        IntOrFloat::Float(*self)
    }
}

fn attempt_3(_x: &dyn IntOrFloatTrait) {}

fn main() {
    let i: i32 = 1;
    let f: f32 = 3.0;

    attempt_3(&i);
    attempt_3(&f);
    // Better, but the caller still has to explicitly take the reference
}

这是最接近方法重载的方法吗?有没有更清洁的方法?

【问题讨论】:

    标签: overloading rust


    【解决方案1】:

    是的,有,而且您几乎已经掌握了。特征是要走的路,但你不需要特征对象,使用泛型:

    #[derive(Debug)]
    enum IntOrFloat {
        Int(i32),
        Float(f32),
    }
    
    trait IntOrFloatTrait {
        fn to_int_or_float(&self) -> IntOrFloat;
    }
    
    impl IntOrFloatTrait for i32 {
        fn to_int_or_float(&self) -> IntOrFloat {
            IntOrFloat::Int(*self)
        }
    }
    
    impl IntOrFloatTrait for f32 {
        fn to_int_or_float(&self) -> IntOrFloat {
            IntOrFloat::Float(*self)
        }
    }
    
    fn attempt_4<T: IntOrFloatTrait>(x: T) {
        let v = x.to_int_or_float();
        println!("{:?}", v);
    }
    
    fn main() {
        let i: i32 = 1;
        let f: f32 = 3.0;
    
        attempt_4(i);
        attempt_4(f);
    }
    

    看到它工作here

    【讨论】:

      【解决方案2】:

      这是删除enum 的另一种方式。这是对弗拉基米尔的回答的迭代。

      trait Tr {
        fn go(&self) -> ();
      }
      
      impl Tr for i32 {
        fn go(&self) {
          println!("i32")
        }
      }
      
      impl Tr for f32 {
        fn go(&self) {
          println!("f32")
        }
      }
      
      fn attempt_1<T: Tr>(t: T) {
        t.go()
      }
      
      fn main() {
        attempt_1(1 as i32);
        attempt_1(1 as f32);
      }
      

      【讨论】:

      • attempt 实现为 trait 方法,移除顶层函数会更习惯吗? (我知道这不会直接回答 OP 的问题)
      • @MaxHeiber 也许。我不知道足够的锈说
      【解决方案3】:

      函数重载是可能的!!! (嗯,有点...)

      这个Rust Playground example 有一个更详细的示例,并显示了结构变体的用法,这对于参数文档可能更好。

      对于更严重的灵活重载,您希望拥有任意数量的任何类型的参数集,您可以利用 From&lt;T&gt; 特征将元组转换为枚举变体,并拥有一个通用函数将传入的元组转换为枚举类型。

      所以这样的代码是可能的:

      fn main() {
          let f = Foo { };
          f.do_something(3.14);               // One f32.
          f.do_something((1, 2));             // Two i32's...
          f.do_something(("Yay!", 42, 3.14)); // A str, i32, and f64 !!
      }
      

      首先,将不同的参数组合集合定义为一个枚举:

      // The variants should consist of unambiguous sets of types.
      enum FooParam {
          Bar(i32, i32),
          Baz(f32),
          Qux(&'static str, i32, f64),
      }
      

      现在,转换代码;可以编写一个宏来执行乏味的 From&lt;T&gt; 实现,但它可以产生以下结果:

      impl From<(i32, i32)> for FooParam {
          fn from(p: (i32, i32)) -> Self {
              FooParam::Bar(p.0, p.1)
          }
      }
      impl From<f32> for FooParam {
          fn from(p: f32) -> Self {
              FooParam::Baz(p)
          }
      }
      impl From<(&'static str, i32, f64)> for FooParam {
          fn from(p: (&'static str, i32, f64)) -> Self {
              FooParam::Qux(p.0, p.1, p.2)
          }
      }
      

      最后,用泛型方法实现结构体:

      struct Foo {}
      
      impl Foo {
          fn do_something<T: Into<FooParam>>(&self, t: T) {
              use FooParam::*;
              let fp = t.into();
              match fp {
                  Bar(a, b)    => print!("Bar: {:?}, {:?}\n", a, b),
                  Baz(a)       => print!("Baz: {:?}\n", a),
                  Qux(a, b, c) => {
                      print!("Qux: {:?}, {:?}, {:?}\n", a, b, c)
                  }
              }
          }
      }
      

      注意:T 上绑定的 trait 需要指定。

      此外,变体需要由编译器不会发现模棱两可的类型组合组成 - 这也是其他语言 (Java/C++) 中重载方法的期望。

      这种方法有可能......如果有一个可用的装饰器,那就太棒了 - 或者在应用到枚举时自动执行 From&lt;T&gt; 实现的编写器。像这样的:

      // THIS DOESN'T EXIST - so don't expect the following to work.
      // This is just an example of a macro that could be written to
      // help in using the above approach to function overloading.
      
      #[derive(ParameterOverloads)]
      enum FooParam {
          Bar(i32, i32),
          Baz(f32),
          Qux(&'static str, i32, f64),
      }
      
      // If this were written, it could eliminate the tedious
      // implementations of From<...>.
      

      【讨论】:

        【解决方案4】:

        建造者

        解决操作或配置有多个可选参数的情况的另一种方法是builder pattern。下面的示例与链接中的建议有所不同。通常,有一个单独的构建器类/结构来完成配置并在调用最终方法时返回配置的对象。

        这可以应用的最相关的情况之一是您需要一个带有可变数量可选参数的构造函数 - 因为 Rust 没有内置重载,我们不能有多个版本的 ___::new() .但是我们可以使用返回self 的方法链来获得类似的效果。 Playground link.

        fn main() {
            // Create.
            let mut bb = BattleBot::new("Berzerker".into());
            
            // Configure.
            bb.flame_thrower(true)
              .locomotion(TractorTreads)
              .power_source(Uranium);
            
            println!("{:#?}", bb);
        }
        

        每个配置方法都有一个类似的签名:

            fn power_source(&mut self, ps: PowerSource) -> &mut Self {
                self.power_source = ps;
                self
            }
        

        也可以编写这些方法来使用self 并返回self 的非引用副本或克隆。

        这种方法也可以应用于动作。例如,我们可以有一个 Command 对象,该对象可以使用链式方法进行调整,然后在调用 .exec() 时执行命令。

        将同样的想法应用于我们想要采用可变数量参数的“重载”方法,我们稍微修改我们的期望并让该方法采用可以使用构建器模式配置的对象。

            let mut params = DrawParams::new();
        
            graphics.draw_obj(params.model_path("./planes/X15.m3d")
                                    .skin("./skins/x15.sk")
                                    .location(23.64, 77.43, 88.89)
                                    .rotate_x(25.03)
                                    .effect(MotionBlur));
        

        或者,我们可以决定拥有一个具有多种配置调整方法的GraphicsObject 结构,然后在调用.draw() 时执行绘图。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-01-17
          • 2015-11-18
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多