Phi*_*ZXX 4 overloading traits rust
我正在尝试重载类的成员函数(类似于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() }
}
}
Run Code Online (Sandbox Code Playgroud)
和
/* 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);
}
Run Code Online (Sandbox Code Playgroud)
首先,这是重载类成员函数的正确方法吗?这看起来有点麻烦,因为需要pub trait Func<T>为每个函数重载添加样板。
其次,有没有办法让我不必use crate::my_class::Func;为每个特征都写?也就是说,当我导入时,如何将MyClass(通过impl MyClass和定义的)的所有函数纳入范围?impl Func<T> for MyClassMyClass
Apl*_*123 10
如果你想模拟完整的函数重载,那么,特征就是正确的选择。如果您不想导入它们,您可以将所有相关特征放在与结构相同的模块中,然后使用use crate::my_class::*.
但不要。C++/Java 风格的函数重载不是一个好主意的原因有很多:
.func(5)在您的示例中,将添加5到a,而.func("5")添加5到是非常不直观的b。foo,该函数接受一些T可以传递给func. 你会如何绑定它?它看起来像这样:fn foo<T>
where
MyClass: Func<T>
{ unimplemented!() }
Run Code Online (Sandbox Code Playgroud)
这已经有点丑了。现在假设您有一个MyClass2具有重载函数的函数,该函数还接受类似 int 的值(i32或者&str可以解析为i32)。你的界限现在看起来像这样:
fn foo<T>
where
MyClass: Func<T>,
MyClass2: Func<T>
{ unimplemented!() }
Run Code Online (Sandbox Code Playgroud)
即使它们在概念上对类似 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;
Run Code Online (Sandbox Code Playgroud)
i64如果你也想支持怎么办?现在您有 9 个实现。如果你想添加第三个参数?现在你有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 }
}
}
Run Code Online (Sandbox Code Playgroud)
和
/* 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);
}
Run Code Online (Sandbox Code Playgroud)
那不是更好吗?
i32. 您可以将共享代码重构为一个单独的函数,然后从重载的函数中调用该函数,但这不是字面上限制特征参数的作用吗?BigInt,并且他们希望它能够与您的函数一起使用。他们必须导入您的特征,然后复制粘贴您的实现,并更改一行以将其转换BigInt为i32. 这不仅丑陋,而且如果您的实现引用任何私有方法或属性,这实际上是不可能的。最重要的是,如果您更改实现(及其 20 个重载)来修复错误,则外部包的开发人员现在需要手动添加错误修复。其他开发人员不必关心您的内部结构。特征IntLike将允许其他开发人员只处理转换为 的逻辑i32,然后让您处理其余的事情。Debug和Clone。事实上,大多数时候,你甚至不需要创造自己的特质,因为它已经有一个特质了。TL;DR 惯用的 C++ 是可怕的 Rust。
“这是重载类成员函数的正确方法吗”这个问题有点误导。您拥有的代码可以工作,并且可能是您在当前 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()
}
}
Run Code Online (Sandbox Code Playgroud)
然后您可以更改您的函数以接受以下任何内容IntLike:
impl MyClass {
fn func(&self, i: impl IntLike) {
let i: i32 = i.to_int();
// rest of the function body
}
}
Run Code Online (Sandbox Code Playgroud)
这有多个优点:
IntLike在其类型上实现)由于 Rust 的单态泛型,它的速度同样快