我如何近似方法重载?

时间:2014-08-12 13:26:50

标签: overloading rust

我正在建模一个API,其中方法重载是一个很好的选择。我天真的尝试失败了:

// fn attempt_1(x: int) {}
// fn attempt_1(x: f32) {}
// Error: duplicate definition of value `attempt_1`

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

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

fn attempt_2(x: IntOrFloat) {}

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

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

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

进行一些快速搜索,我found some references谈论重载,所有这些似乎都以“我们不会允许这样做,但是尝试一下特质“。所以我试过了:

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

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

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

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

fn attempt_3(x: &IntOrFloatTrait) {}

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

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

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

3 个答案:

答案 0 :(得分:8)

是的,有,你几乎已经得到它了。特征是要走的路,但你不需要特质对象,使用泛型:

#[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

答案 1 :(得分:1)

这是丢弃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);
}

答案 2 :(得分:1)

函数重载是可能的!!! (好吧,有点……)

Rust Playground example 有更详细的示例,并显示了结构变体的用法,这可能更适合作为参数文档。

对于更严重的灵活重载,您希望拥有任意数量的任何类型的参数集,您可以利用 From<T> 特性将元组转换为枚举变体,并拥有一个泛型将传递给它的元组转换为枚举类型的函数。

这样的代码是可能的:

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<T> 实现,但它可能会产生以下结果:

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 bound。

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

这种方法有多种可能性……如果有一个可用的装饰器,那就太棒了——或者是一个在应用于枚举时自动执行 From<T> 实现的装饰器。像这样:

// 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<...>.