动态

详情 返回 返回

rust學習二十.6、RUST通用類型參數默認類型和運算符重載 - 动态 详情

一、前言

為通用類型賦予一個默認的類型,大部分的語言是沒有這個特性的,但是也有例外的,例如TypeScript(可能還有其它)。

例如TypeScript可以這樣使用:

class MyClass<T = number> {
    value: T;
    constructor(value: T) {
        this.value = value;
    }
    printValue(): void {
        console.log(`Value is ${this.value}`);
    }
}
const obj1 = new MyClass(42);  // 使用默認類型 number
const obj2 = new MyClass<string>("Hello");  // 使用指定類型 string

而運算符重載,則不少語言也支持,最典型的莫過於C++,C#.

但是rust的運算符重載是比較特別的一種,該怎麼説了?

rustc做了太多的工作,而且我覺得有點違背一些通用的設計規則。這是因為例子中的方法必須要求對象實現Copy,但是方法的參數又沒有帶&,會讓人誤會!

不喜歡有太多默認約定的設計,更喜歡每個東西都明明白白地定義。

二、通用類型參數默認類型

讀取來有點拗口,意思就是:

1.在有關對象(struc,特質等)或者方法中使用通用參數T

2.可以為T指定一個默認的類型,語法是T=xxx,其中xxx是某個具體類型

如果你不喜歡T,也可以換成任意合法的rust標識符.

就目前來看,通用參數的默認參數的作用有兩點:運算符重載+方便

三、運算符重載和其它作用

3.1、運算符重載

所謂運算符重載就是除了運算符最原始的功能(編譯器默認支持的)之外,還可以支持其它類型的運算數。

例如+通常用於整數、浮點數等的相加,但通過重載,其它類型對象實例也可以使用+。

以此類推,-*/等運算符號也可以。

不管怎麼説,這算是一個好東西!

只不過類型參數的默認類型好像就是為了運算符重載而存在。

3.2、其它作用

查了一些資料,據説可以結合條件編譯。其它作用就是無關緊要的。

條件編譯示例

// 定義一個特性標誌,用於條件編譯

#[cfg(feature = "use_f64")]
type DefaultNumType = f64;
#[cfg(not(feature = "use_f64"))]
type DefaultNumType = i32;
struct Point<T = DefaultNumType> {
    x: T,
    y: T,
}
impl<T> Point<T> {
    fn new(x: T, y: T) -> Self {
        Point { x, y }
    }
}

 

四、示例

4.1、示例代碼

由於例子涉及到Add,Sub兩個特質,所以先列出此二特質的定義:

pub trait Add<Rhs = Self> {
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
}
pub trait Sub<Rhs = Self> {
    type Output;
    fn sub(self, rhs: Rhs) -> Self::Output;
}

對書本上的例子稍微改造了下:

use std::ops::{Add,Sub};

#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}
/**
 * 這個使用默認類型,來自rust編程語言官方文檔的例子
 */
impl Add for Point {
    type Output = Point;
    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

/**
 * 實現相減運算符(從而實現Point的-重載),需要實現Sub trait
 */
impl Sub for Point {
    type Output = Point;
    /**
     * 需要特別注意的是兩個參數的定義
     * self -  沒有使用引用
     * other - 沒有要求引用
     * 這種不引用的方式,不同於一般的方法定義 
     */
    fn sub(self, other: Point) -> Point {
        Point {
            x: self.x - other.x,
            y: self.y - other.y,
        }
    }
}


fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    //使用重載的方式調用
    println!("{:?}+{:?}={:?}",p1,p2, p1 + p2);
    println!("{:?}-{:?}={:?}",p1,p2, p1 - p2);

    //不使用重載的方式調用
    let p3 = p1.add(p2).sub(p2);
    let p4 = (p1.sub(p2)).add(p2);
    println!("{:?}+{:?}-{:?}={:?}",p1,p2, p2,p3);
    println!("{:?}-{:?}+{:?}={:?}",p1,p2,p2, p4);

    let lml= Person {name: "lml".to_string()};
    let ww= Animal {name: "ww".to_string()};
    lml.attack(ww);
    println!("{:?}",lml);
}

// --------------------------------------------------------
// 以下的代碼是為了演示 參數不帶&是什麼情況

trait Fight {
    type Item;
    //fn attack(&self, other: &Self::Item);
    fn attack(self, other: Self::Item);
}
#[derive(Debug)]
struct Person {name: String}
#[derive(Debug)]
struct Animal {name: String}

impl Fight for Person {
    type Item = Animal;
    fn attack(self, other: Self::Item) {
        println!(
            "{}攻擊了{}",
            self.name,
            other.name
        );
    }    
}

這個例子做了三件事情:

1.重載+

2.重載-

3.如果不使用Copy特質會怎麼樣

特質Fight和結構體Person,Animal就是為了驗證第3點。

4.2、名詞解釋

在開始執行代碼前,先解釋兩個重要的內容

Rhs

Rhs是 "Right-Hand Side"(右側操作數)的縮寫

關鍵字self和Self

仔細看看,才發現是兩個,不是一個,要知道rust中是區分大小寫的。

self-全小寫,表示對象實例本身

Self-首字母大寫,其它小寫,表示類型本身

例如以下代碼中:

trait Fight {
    type Item;
    fn attack(&self, other: &Self::Item);
    fn defend<T>(&self, danger: &T)
    where T: Danger;
}

在方法attack中,第一個self表示具體對象實例,第二個Self則表示具體對象類型。

pub trait Add<Rhs = Self> {
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
}
pub trait Sub<Rhs = Self> {
    type Output;
    fn sub(self, rhs: Rhs) -> Self::Output;
}

現在代碼應該容易看了。

4.3、執行

看看輸出:

只要把示例中如下一部分:

trait Fight {
    type Item;
    //fn attack(&self, other: &Self::Item);
    fn attack(self, other: Self::Item);
}
#[derive(Debug)]
struct Person {name: String}
#[derive(Debug)]
struct Animal {name: String}


impl Fight for Person {
    type Item = Animal;
    fn attack(self, other: Self::Item) {
        println!(
            "{}攻擊了{}",
            self.name,
            other.name
        );
    }    
}
 

修改為:

trait Fight {
    type Item;
    fn attack(&self, other: &Self::Item);
    //fn attack(self, other: Self::Item);
}
#[derive(Debug)]
struct Person {name: String}
#[derive(Debug)]
struct Animal {name: String}


impl Fight for Person {
    type Item = Animal;
    fn attack(&self, other: &Self::Item) {
        println!(
            "{}攻擊了{}",
            self.name,
            other.name
        );
    }    
}

再把main的調用修改為:

lml.attack(&ww);

那麼就可以正確輸出:

為什麼在Point上沒有這個問題了?這是因為Point實現了Copy特質,看下面的代碼:#[derive(Debug, Copy, Clone, PartialEq)]

rust的Copy特質奇怪的作用:允許類型進行隱式的、按位的複製,適用於簡單數據類型,避免不必要的所有權移動,提升代碼效率和便利性。同時,強調其使用條件和限制,幫助用户正確理解和應用

什麼是按位複製?

也就是説,當賦值或作為函數參數傳遞時,不需要移動所有權,而是直接複製。不過,只有滿足某些條件的類型才能實現Copy,比如所有字段都實現了Copy,並且類型本身沒有實現Drop trait

所以,上例中,即使在方法中沒有定義為引用類型,它也不會報錯。而Person並沒有實現Copy特質,所以會發生這個問題。

五、示例2

以下的示例演示了一個只包含字符串切片的struct如何相加

use std::ops::Add;
#[derive(Debug,Clone,Copy)]
struct Name<'a>{
    name:&'a str
}

impl<'a> Add for Name<'a>{
    type Output = Name<'a>;
    fn add(self, other: Self) -> Self {
        let tmp=format!("{} {}",self.name,other.name);
        //Box::leak的作用是 將堆上的內存轉換為 'static 生命週期的引用
        let leaked_str: &'static str = Box::leak(tmp.into_boxed_str());
        Self{name: leaked_str }
    }
}

fn main() {
    let name1 = Name{name:"lu"};
    let name2 = Name{name:" mula"};
    let name3=name1+name2;
    println!("name1={:?},name2={:?}", name1,name2);
    println!("name3={:?}", name3);

    let name4=String::from("lu");
    let name5=String::from("lu");
    let name6=add(name4,name5);
    println!("name6={:?}", name6);
}

fn add(name1:String,name2:String)->String{
    format!("{} {}",name1,name2)
}

運行結果:

本例主要説明以下幾個問題:

1.字符串切片可以Copy.所以包含字符串切片的類型也可以實現+等重載

2.實現和字符串切片有關的內容太麻煩,導出是奇怪的生命週期符號

六、小結

1.rust通過定義通用類型參數的默認類型來實現運算符重載

2.但也不是什麼對象都可以重載,最好是能夠實現Copy特質的類型,否則可能失敗

Add a new 评论

Some HTML is okay.