我正在建模一个API,其中方法重载将是一个很好的选择.我天真的尝试失败了:
// fn attempt_1(_x: i32) {}
// fn attempt_1(_x: f32) {}
// Error: duplicate definition of value `attempt_1`
Run Code Online (Sandbox Code Playgroud)
然后我添加了一个枚举并完成了:
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
}
Run Code Online (Sandbox Code Playgroud)
做了一些快速搜索,我发现了 一些 关于重载的引用,所有这些引用似乎都以"我们不会允许这个,但尝试一下"来结束.所以我尝试过:
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
}
Run Code Online (Sandbox Code Playgroud)
这是我最接近方法重载的方法吗?有更干净的方式吗?
Vla*_*eev 10
是的,有,你几乎已经得到它了.特征是要走的路,但你不需要特质对象,使用泛型:
#[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);
}
Run Code Online (Sandbox Code Playgroud)
看到它在这里工作.
这是另一种删除enum
. 这是 Vladimir 答案的迭代。
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);
}
Run Code Online (Sandbox Code Playgroud)
函数重载是可能的! (嗯,有点……)
这个Rust Playground 示例有更详细的示例,并显示了结构变体的用法,这可能更适合参数文档。
对于更严重的灵活重载,您想要拥有任意数量的任何类型的参数集,您可以利用将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 !!
}
Run Code Online (Sandbox Code Playgroud)
首先,将不同的参数组合集定义为枚举:
// The variants should consist of unambiguous sets of types.
enum FooParam {
Bar(i32, i32),
Baz(f32),
Qux(&'static str, i32, f64),
}
Run Code Online (Sandbox Code Playgroud)
现在,转换代码;可以编写一个宏来完成繁琐的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)
}
}
Run Code Online (Sandbox Code Playgroud)
最后,使用泛型方法实现该结构:
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)
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
T
注意:需要指定绑定的特征。
此外,变体需要由编译器不会发现歧义的类型组合组成 - 这也是其他语言(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<...>.
Run Code Online (Sandbox Code Playgroud)