将函数重载(通过特征)引入范围。

4

我试图重载类的成员函数(类似于在C++中所做的)。因此,我阅读到在Rust中必须使用特性来实现这一点。以下是一些示例代码(请注意,这只是为了演示这个想法):

/* my_class.rs */
pub struct MyClass {
    pub a: i32,
    pub b: i32,
}

pub trait Func<T> {
    fn func(&self, t: T) -> Self;
}

impl Func<i32> for MyClass {
    fn func(&self, t: i32) -> MyClass {
        MyClass { a: self.a + t, b: self.b }
    }
}

impl Func<&str> for MyClass {
    fn func(&self, t: &str) -> MyClass {
        MyClass { a: self.a, b: self.b + t.parse::<i32>().unwrap() }
    }
}

/* main.rs */
mod my_class;
use crate::my_class::MyClass;
use crate::my_class::Func;

fn main() {
    let m1 = MyClass {a: 10, b:20}.func(5);
    let m2 = MyClass {a: 10, b:20}.func("-8");
    println!("a={}, b={}", m1.a, m1.b);
    println!("a={}, b={}", m2.a, m2.b);
}

首先,这是重载类成员函数的正确方式吗?似乎有点麻烦,因为每个函数重载都需要添加样板代码 pub trait Func<T>

其次,有没有一种方法可以不必在每个trait中编写use crate:: my_class :: Func;语句?也就是说,在导入MyClass时如何将MyClass的所有函数(通过impl MyClassimpl Func<T> for MyClass定义)引入作用域?

2个回答

10
如果你想要模拟完整的函数重载,那么traits是可行的方法。如果你不想导入它们,你可以将所有相关的traits放在与结构体相同的模块中,然后使用use crate::my_class::*导入所有的traits。
但是,请注意以下几点原因,C++/Java风格的函数重载并不是一个好主意:
1. 它容易引起混淆。当然,你可以举出一些不容易混淆的例子,但是很多重载函数根据其参数类型执行截然不同的操作,在这种情况下,为什么不直接写一个新的函数呢?在你的例子中,.func(5)会将5添加到a中,而.func("5")会将5添加到b中,这非常令人费解。
2. 它给调用者带来了不必要的负担。想象一下,你正在编写一个函数foo,该函数接受可以传递给func的一些T类型。你该如何绑定它?代码可能看起来像这样:
fn foo<T>
where
    MyClass: Func<T>
{ unimplemented!() }

这已经有点丑了。现在想象一下你有一个MyClass2,它有一个重载函数,也接受一个类似于int的值(可以解析为i32&str)。你的限制现在看起来像这样:

fn foo<T>
where
    MyClass: Func<T>,
    MyClass2: Func<T>
{ unimplemented!() }

虽然它们在概念上是相同的int类型值的限制,但当添加更多的泛型和重载时,它只会变得越来越丑陋。

  1. 它不能扩展到多个参数。比如你想要一个函数,它接收一个值加到a上,再加上一个值加到b上。现在你需要4个实现:
fn func(&self, t1: i32, t2: i32) -> Self;
fn func(&self, t1: i32, t2: &str) -> Self;
fn func(&self, t1: &str, t2: i32) -> Self;
fn func(&self, t1: &str, t2: &str) -> Self;

如果你想支持i64怎么办?现在你需要九种实现。如果你想添加第三个参数呢?现在你需要27种实现。
所有这些问题都源于概念上约束实际上是在参数而不是函数上。因此,编写与概念相匹配的代码,并将特征限制放在参数而不是函数上。它可以防止混淆的过载操作,消除调用者的负担,并防止实现指数级增长。最重要的是,即使方法没有导入特性,也可以使用。考虑以下内容:
/* my_class.rs */
pub struct MyClass {
    pub a: i32,
    pub b: i32,
}

pub trait IntLike {
    fn to_i32(self) -> i32;
}

impl IntLike for i32 {
    fn to_i32(self) -> i32 { self }
}

impl IntLike for &str {
    fn to_i32(self) -> i32 { self.parse().unwrap() }
}

impl MyClass {
    pub fn func<T: IntLike>(&self, t: T) -> Self {
        Self { a: self.a + t.to_i32(), b: self.b }
    }
}

并且

/* main.rs */
mod my_class;
// don't even need to import the trait
use crate::my_class::MyClass;

fn main() {
    let m1 = MyClass {a: 10, b:20}.func(5);
    let m2 = MyClass {a: 10, b:20}.func("-8");
    println!("a={}, b={}", m1.a, m1.b);
    println!("a={}, b={}", m2.a, m2.b);
}

这不是更好吗?


附言

  1. 它会让你重复编写相同的代码。如果你有一个函数,可以找到一个整数的质因子分解,而且你希望它适用于任何类似int的值,则必须为每种参数类型复制/粘贴质因数分解代码,仅更改将参数转换为i32的一行代码。您可以将共享代码重构为单独的函数,然后只需从重载函数调用该函数,但是这不是将参数约束为特征的字面上意思吗?
  2. 它不可扩展。假设有人正在编写一个带有BigInt类型的板条箱,并且他们希望它与您的函数配合使用。他们必须导入您的特征,然后复制/粘贴您的实现,并更改一行以将其BigInt转换为i32。这不仅很丑陋,而且如果您的实现引用任何私有方法或属性,实际上不可能。作为压轴大甜点,如果您更改了实现(及其20个重载)以修复错误,则外部包的开发人员现在需要手动添加错误修复。其他开发人员不应该关注您的内部情况。一个IntLike特征将使其他开发人员只需处理转换为i32的逻辑,然后让您处理其余部分。
  3. 它违反了语言的设计。Rust书上的特征章节的标题是“特征:定义共享行为”。它们主要是为此而设计的:共享行为。将它们用于函数重载只是一个副作用,这是Rust中特征实现方式的结果,因此会引起许多重要问题。当您约束参数而不是函数时,您可以自己组合约束以及标准库中众多预先实现的特征(例如DebugClone)。事实上,大多数时候,您甚至都不需要制作自己的特征,因为已经有一个特征了。

简而言之,惯用的C ++是可怕的Rust。


5
“是否这是重载类成员函数的正确方式?”这个问题有些误导性。你已经编写的代码是可行的,而且很可能是当前 Rust 中最优雅的解决方案。
但可能有一个比重载更好的方式,我猜你最好使用更标准的特性来实现。
我假设 API 的意图是调用 `.func("5")` 与调用 `.func(5)` 相同。
如果是这种情况,你可能想要定义一个表示“可以传递给 func 的东西”的特征。例如,让我们称其为 IntLike,并为“看起来像整数的东西”实现它:
trait IntLike {
  fn to_int(self) -> i32;  // consume self, return an i32
}

impl IntLike for i32 {
  fn to_int(self) -> i32 { self }
}

impl IntLike for &'static str {
  fn to_int(self) {
    self.parse::<i32>().unwrap()
  }
}

然后您可以更改函数以接受任何符合IntLike的内容:

impl MyClass {
  fn func(&self, i: impl IntLike) {
    let i: i32 = i.to_int();
    // rest of the function body
  }
}

这样做有多个优点:
  • 您只需编写逻辑一次。错误的机会更少,实现之间的差异几乎为零
  • 您代码的使用者可以更轻松地让他们的自定义数据类型与您的函数配合使用(只需在其类型上实现IntLike
  • 它更符合惯用语法,经验丰富的Rust程序员将更直观地了解其工作原理
并且由于Rust 的单态特化泛型,它同样快速。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接