rust學習七、列舉

正在战斗中發表於2024-11-11

列舉是十分特別的型別,是因為和大部分語言中看起來不太一樣。

最大的不同之一在於:允許每個成員具有不同的型別的屬性

注:本文內容根據<<The rust programmming Language>>有關章節編寫

如果沒有特別說明,"rust學習"系列基本上都是根據此書編寫,不再在後續的篇幅中說明

一、定義、賦值和列印

結論:

1.rust列舉型別相當之怪異
2.列舉的定義中,允許成員包含資料,也可以不包含資料。
3.當列舉型別中包含資料時,不同的列舉例項還可以包含不同的型別的資料。
4.列舉的成員只能包含(a-z, A-Z)、數字(0-9)、下劃線(_)以及 Unicode 非字母數字字元
不能以數字開頭
當每個列舉成員都可以不同的時候,我們把它理解成java的Object型別即可。
總之,rust的列舉型別極其怪,不同於於其它語言
列舉的定義中,列舉成員的命名方式為:首字母大寫(僅僅是建議)
5.列舉可以有方法,和struct結構類似,定義的方式也類似,放在列舉外,而不是在內部

示例

#[derive(Debug)]
enum simple_enum {
    Man,
    Women,
    Other
}

//b. 列舉型別中包含資料
#[derive(Debug)]
enum color {
    Red,
    Green = 3
}

//c.列舉型別中包含多個資料,且都是一樣的
#[derive(Debug)]
#[derive(PartialEq)]
enum grade {
    A(u32,String),
    B(u32,String),
    C(u32,String)
}

//列舉的方法,定義上了struct方法類似,都是impl,&self
impl grade {
    fn print(&self) {   
        //這個tmd也太麻煩了吧
        match self {
            grade::A(a,b) => println!("{}-{}",a,b),
            grade::B(a,b) => println!("{}-{}",a,b),
            grade::C(a,b) => println!("{}-{}",a,b)
        }
    }
}

#[derive(Debug)]
enum food<'a>{
    蔬菜([String;5]),
    禽肉,
    豬肉,
    牛羊肉,
    海鮮,
    大米,
    水果(&'a str,&'a str,&'a str)
}

fn main() {
    let a = simple_enum::Man;
    println!("{:?}",a);
    // 使用println! :?宏列印列舉的時候,變數名稱不能同列舉成員。否則會報錯
    let gr = color::Green;  
    println!("{:?}",gr);
    let r = color::Red;
    println!("{:?}",r);

    let grade_a = grade::A(10,String::from(""));
    let grade_a1=grade::A(9,String::from("差011"));
    grade_a.print();
    println!("{:?}",grade_a);
    if (grade_a == grade_a1){
        println!("相等");
    }
    else{
        println!("不相等");
    }

    let fruit= food::水果("蘋果","香蕉","梨子");
    println!("{:?}",fruit);
    print_first_fruit_member(fruit);
    let v = food::蔬菜(["白菜".to_string(),"蘿蔔".to_string(),"黃瓜".to_string(),"冬瓜".to_string(),"南瓜".to_string()]);
    println!("{:?}",v);
}

fn print_first_fruit_member<'a>(fruit: food<'a>) {
    if let food::水果(first, second, third) = fruit {
        println!("{},{},{}", first,second,third);
    } else {
        println!("The provided food is not a fruit.");
    }

}

上例中,food允許不同成員有不同的型別值。

二、特別的列舉-Option

按照作者的意思,Option<T>是一個很有用的枚
Option<T>{
Some(T),
None
}

特殊點在於:
1.定義於標準庫中
2.它甚至被包含在了 prelude 之中,你不需要將其顯式引入作用域
3.它的成員也是如此,可以不需要 Option:: 字首來直接使用 Some 和 None


有用是怎麼體現出來的?

1.首先Option具有許多方法,比如is_some()、is_none()、unwrap()等
2.Option<T>可以作為函式引數,比如fn some_function(option: Option<i32>)


以下是重點(基本上摘抄的):
那麼當有一個 Option<T> 的值時,如何從 Some 成員中取出 T 的值來使用它呢?
Option<T> 列舉擁有大量用於各種情況的方法:
你可以檢視它的文件。熟悉 Option<T> 的方法將對你的 Rust 之旅非常有用(原文)
示例
 fn main() {
    let some_number = Some(5); // some_number的型別是Option<i32>
    let some_string = Some("a string"); // some_string的型別是Option<&str>
    let absent_number: Option<i32> = None; // absent_number的型別是Option<i32>
    println!("some_number is {:?}", some_number);
    println!("some_string is {:?}", some_string);
    println!("absent_number is {:?}", absent_number);
    println!("absent_number is {:?}", absent_number.is_none());
    let name:String;
    name.push_str("abc");
 }

非常有用? 還沒有體會出來,先記著!

三、match和列舉

這裡主要討論如何匹配列舉的值,rust的解決方案是match。

1.match某種程度上可以看作rust中if-else的升級版,只不過match有時候可以讓我們少列印一些程式碼而已
match不像java的switch那麼傻瓜(傳統),需要在每個分支上break,否則會繼續執行下一個分支,雖然有那種情況存在,但是絕大部分時候,
我們並不需要break。
rust的match和新版本的java switch寫法更加一致,已經不需要break了,且不會傻乎乎地每個匹配過去

2.順便說一下,rust的創始人很喜歡下劃線。所以下劃分可以分割變數,也可以單獨作為一個變數,但是不建議這麼做
3.關於分支值的匹配
a.rust的分支匹配是窮盡的,意思就是編譯器如果發現你存在一些未匹配的分支,就會報錯。 我們記住這個就可以了,因為編譯器很貼心第處理了這個。
這一點,的確比許多編譯器好多了。
b.rust可以使用多種方式匹配某個分支值,包括單個值、多個值、所有值
c.rust使用下劃線_來表示所有值,這樣我們就可以直接使用這個變數了。這個倒是比許多語言的default稍微省事一些。這個並不是重點
d._和other都可以表示所有值,能一起用,不會編譯錯誤,但是固定有一個分支不可達到
這意味著,rust的編譯器目前雖然很貼心,但也不會浪費過多的時間去判斷是否具有相同的分支--因為它允許有相同的分支,但是
它的匹配機制使得只會有其中一個分支被執行,其它相同的並會被處理
e.當某個分支值滿足之後,匹配程式碼就結束了,不會像傳統的java switch那麼傻叉繼續執行後面的。這個符合大部分人的思維

示例

/**
 * 年代列舉
 */
#[derive(Debug)] 
enum Age{Year1970,OtherYear}

/**
 * 硬幣列舉
 */
enum Coin{一分(Age),二分,五分,一角,貳角,五角,一元}
/**
 * 文明列舉
 */
#[derive(Debug)] 
enum Civil{中國(Age),歐洲,美國,非洲}

fn main(){
    let coin = Coin::五分;
    let one =Coin::一元;
    match_branch_with_multiple_method(coin);
    match_branch_with_multiple_method(one);

    let civil = Civil::中國(Age::Year1970);
    let _ = Civil::歐洲;   //奇特的無意義的變數名稱_,可以用,但是不建議。因為你無法直接傳遞,轉賦也不方便,也不利於維護
    //let bad_=_;  //這樣會報錯
    //match_and_use_branch_value(_) //這樣會報錯
    let _歐洲 = Civil::歐洲;
    match_and_use_branch_value(civil);
    match_and_use_branch_value(_歐洲);
   
}

fn match_branch_with_multiple_method(coin:Coin){
    //演示如何匹配多值分支的方式:單個值、多個值、所有值
    match coin{
        Coin::一分(Age::Year1970) => println!("一分"),  //匹配分支特定值
        Coin::一分(_) => println!("一分"),   //匹配分支所有值
        Coin::一分(Age::OtherYear) |   Coin::一分(Age::Year1970)  => println!("一分"), //匹配分支多個值
        Coin::二分 => println!("二分"),
        Coin::五分 => println!("五分"),
        Coin::一角 => println!("一角"),
        Coin::五角 => println!("五角"),
        other => println!("一元 of other"),
        _=> println!("一元") 
    };
}

fn  match_and_use_branch_value(civil:Civil){
    //這種方式,可以利用類似匹配元組元素的方式,直接捕獲分支值,並使用
    match civil {
        Civil::中國(age) => println!("有小聰明的中國:{:?},偶爾有一些亮色能拯救他們",age),  //自動從civil中捕獲age值
        Civil::歐洲 => println!("骯脹但又聰明的歐洲"),
        _ => println!("{:?}",civil)   //如果把這個匹配去掉,那麼會引發編譯錯誤: patterns `Civil::美國` and `Civil::非洲` not covered
    };
}

如上例,match對列舉的匹配是窮盡的,這意味著一個值沒有匹配,編譯器就報錯,這對於工程師倒是挺友好的!

四、語法糖if let

示例

#[derive(Debug)] 
enum Age{Year1970,OtherYear}

/**
 * 硬幣列舉
 */
enum Coin{一分(Age),二分,五分,一角,貳角,五角,一元}

fn main(){
    let coin = Coin::一分(Age::Year1970);
    match coin{
        Coin::一分(Age::Year1970) => println!("70特有硬幣?"),
        _=>println!("不是一分硬幣")
    }

    //rust 的語法糖
    if let Coin::一分(Age::Year1970) = coin{
        println!("70特有硬幣?");
    }else{  
        println!("不是一分硬幣");
    }
    
    //不帶 else的if let
    if let Coin::一分(Age::OtherYear)=coin{
        println!("不是70特有硬幣");
    }

}

語法糖這個東西沒有什麼可說的,就是圖方便而已。

如果沒有ide和程式碼助手,那麼的確還是不錯的! 但你如果有ide和程式碼助手,那麼這些語法糖的主要作用就是困擾。

五、小結

總而言之,rust的列舉和大部分語言看起來並沒有什麼本質的上的區別,但的確又有一些區別。

rust發明人的宗旨:讓語言儘量用起來方便一些。 看起來好像得到了體現。 就我個人而言,不是太苟同!

rust的編譯器的大作用,在這裡有了體現!

相關文章