C++方式的函數重載,即同一個函數名以及多個不同的形參類型和個數(不包括傳回值類型), 以Ad-hoc(臨時,随時,不用事先深思熟慮)的方式來實作函數的重載!功能非常強大, 同時也是惹禍根源之一!
Rust 隻允許通過預先定義和實作Trait的方式來近似模拟C++ ad-hoc 函數重載!比如Rust允許部分運算符重載,比如:std::ops::Add Trait , 隻要為你的自定義類型實作了這個Add Trait 那麼你的自定義類型就可以執行加法運算,如: a+b 。
use std::ops::Add;
#[derive(Debug,Clone,Copy, PartialEq, Eq)]
struct Complex {
real : i32,
imag: i32,
}
impl Add for Complex {
type Output = Complex;
fn add(self, other: Complex) -> Complex {
Complex {
real: self.real + other.real,
imag: self.imag + other.imag,
}
}
}
fn main() {
let c1 = Complex{ real: 3, imag:7};
let c2 = Complex{ real: 4, imag:6};
println!("{:?}", c1 +c2); //對+運算符的重載。
}
-
通過Rust Trait來模拟C++ ad-hoc函數重載
#[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;
//從表面上看,實作了同一個函數名和不同的參數類型。
//從本質來說,它隻是通過trait來實作的自動類型轉換而已,隻是文法糖。
//Rust官方也是通過trait來實作模拟函數重載的,包括運算符重載都是采用統一模式, 即定義和實作相應trait。
attempt_4(i);
attempt_4(f);
}
Rust 本質上禁止C++ ad-hoc 函數重載,因為坑太深!但是又通過trait來實作了一定的靈活性!如果再結合上泛型,那就會強大無比,而且更加安全可靠, 可謂嚴肅活潑!
我認為:所有權、生命周期、借用和Trait是Rust的靈魂特性。對于
Rust Trait
即可以幫你填平類型的差異,又可以幫你差異化定制,慢慢體會吧。
Rust 官方也是通過這種模式來模拟C++ ad hoc函數重載的!标準庫中很容易找到類似模式代碼。
#[derive(Debug)]
struct Foo {
value:u64
}
trait HasUIntValue {
fn as_u64(self) -> u64;
}
impl Foo {
fn add<T:HasUIntValue>(&mut self, value:T) {
self.value += value.as_u64();
}
}
impl HasUIntValue for i64 {
fn as_u64(self) -> u64 {
return self as u64;
}
}
impl HasUIntValue for f64 {
fn as_u64(self) -> u64 {
return self as u64;
}
}
fn test_add_with_int()
{
let mut x = Foo { value: 10 };
x.add(10i64);
assert!(x.value == 20);
println!("{:?}", x);
}
fn test_add_with_float()
{
let mut x = Foo { value: 10 };
x.add(10.0f64);
assert!(x.value == 20);
println!("{:?}", x);
}
fn main() {
test_add_with_int();
test_add_with_float();
}
萬變不離其宗,隻有明确實作了相應的才可能具有相應的能力,才允許調用相應的函數方法, 進而有效避免了C++ ad-hoc函數重載的不可控和不明确問題。比如第三方庫提供了某函數,但是我們自己又定了自己的重載版本,或者是另一個第三方庫也提供了不同的重載版本, 那麼當程式運作起來時,到底調用的是哪個函數呢?是以C++ ad hoc 函數重載非常強大同時坑也深!而Rust隻能通過預先定義和實作
Trait
的方式來拓展功能, 避免了随意性,更加明确!因為
Trait
Trait
肯定不允許随便改動的。
對于函數重載Rust是明确拒絕的!因為泛型就可以搞定了, 比如上面的代碼例子,隻是針對一個參數的函數重載模拟, 那麼對于多參數函數怎麼辦呢? 其實泛型就可以搞定了!真的不太需要C++ ad hoc 函數重載了,因為兩者本質上都是去解決用同一套算法處理多種資料類型的問題。
-
Variadic可變長參數
Rust現在不直接支援函數可變長參數,但可通過宏來實作可變長參數,
宏: println!, vec!
就是典型例子, 另一些例子,如:
macro_rules! sum {
($($args:expr),*) => {{
let result = 0;
$(
let result = result + $args;
)*
result
}}
}
macro_rules! print_all {
($($args:expr),*) => {{
$(
println!("{}", $args);
)*
}}
}
fn main() {
assert_eq!(sum!(1, 2, 3), 6);
print_all!(1, 2, "Hello");
}
其實函數可變長參數并不是緊迫需要,通過數組參數類型之類也可達到相同目的!隻不過通過Rust宏機制實作看着更規矩安全些吧。
- Reference
https://stackoverflow.com/questions/24857831/is-there-any-downside-to-overloading-functions-in-rust-using-a-trait-generic-f
https://stackoverflow.com/questions/24936872/how-do-i-use-parameter-overloading-or-optional-parameters-in-rust
https://stackoverflow.com/questions/25265527/how-can-i-approximate-method-overloading
https://doc.rust-lang.org/rust-by-example/macros/variadics.html
https://stackoverflow.com/questions/28951503/how-can-i-create-a-function-with-a-variable-number-of-arguments
《深入淺出Rust》 範長春著, 機械工業出版社
https://doc.rust-lang.org/book/ch19-06-macros.html
- Author
學習随筆,如有謬誤,望請海涵雅正,謝謝。
作者:心塵了
email: [email protected]