如果你想要模拟完整的函数重载,那么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类型值的限制,但当添加更多的泛型和重载时,它只会变得越来越丑陋。
- 它不能扩展到多个参数。比如你想要一个函数,它接收一个值加到
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种实现。
所有这些问题都源于概念上约束实际上是在
参数而不是
函数上。因此,编写与概念相匹配的代码,并将特征限制放在参数而不是函数上。它可以防止混淆的过载操作,消除调用者的负担,并防止实现指数级增长。最重要的是,即使方法没有导入特性,也可以使用。考虑以下内容:
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 }
}
}
并且
mod my_class;
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);
}
这不是更好吗?
附言
- 它会让你重复编写相同的代码。如果你有一个函数,可以找到一个整数的质因子分解,而且你希望它适用于任何类似int的值,则必须为每种参数类型复制/粘贴质因数分解代码,仅更改将参数转换为i32的一行代码。您可以将共享代码重构为单独的函数,然后只需从重载函数调用该函数,但是这不是将参数约束为特征的字面上意思吗?
- 它不可扩展。假设有人正在编写一个带有
BigInt
类型的板条箱,并且他们希望它与您的函数配合使用。他们必须导入您的特征,然后复制/粘贴您的实现,并更改一行以将其BigInt
转换为i32
。这不仅很丑陋,而且如果您的实现引用任何私有方法或属性,实际上不可能。作为压轴大甜点,如果您更改了实现(及其20个重载)以修复错误,则外部包的开发人员现在需要手动添加错误修复。其他开发人员不应该关注您的内部情况。一个IntLike
特征将使其他开发人员只需处理转换为i32的逻辑,然后让您处理其余部分。
- 它违反了语言的设计。Rust书上的特征章节的标题是“特征:定义共享行为”。它们主要是为此而设计的:共享行为。将它们用于函数重载只是一个副作用,这是Rust中特征实现方式的结果,因此会引起许多重要问题。当您约束参数而不是函数时,您可以自己组合约束以及标准库中众多预先实现的特征(例如
Debug
和Clone
)。事实上,大多数时候,您甚至都不需要制作自己的特征,因为已经有一个特征了。
简而言之,惯用的C ++是可怕的Rust。