亚欧色一区w666天堂,色情一区二区三区免费看,少妇特黄A片一区二区三区,亚洲人成网站999久久久综合,国产av熟女一区二区三区

  • 發布文章
  • 消息中心
點贊
收藏
評論
分享
原創

rust基礎學習

2025-09-08 02:21:33
1
0

1.rust所有權

語句和表達式:前者不返回值,而后者返回值。在rust中,每一個代碼塊的最后一行可以放一個表達式,這個表達式就作為這整個代碼塊的返回值。

fn if_test() {
    let condition = true;
    let x = {
        if condition {
            12
        } else {
            let y = 99;
            y + 1
        }
    };
    println!("{}", x);
}

不同語言的垃圾清理規則:

  • java:有gc收集器,當察覺到某個值沒有使用之后,自動回收內存
  • c++:開發者手動回收內存
  • rust:當擁有這個值的變量走出作用范圍后,內存就會自動收集(調用drop函數)

所有權,就是因為heap內存管理而產生的。rust中的所有權規則:

  • 每個值都對應一個變量,這個變量就是這個值的所有者(變量擁有這個值)
  • 每個值同時只能有一個所有者
  • 當所有者超出作用域的時候,這個值就會被清理
fn test1() {
    let s1 = String::from("hello");
    let s2 = s1;
    println!("{}", s1); // 報錯,因為"hello"的所有權被s2奪走,s1失效!
}
fn take_ownership(some_string: String) {
    println!("{}", some_string);
}

fn ownership_test() {
    /* 在這里例子中,s傳入take_ownership中,該函數的some_string也指向了這個s指向的值
    而String實現了drop strait,因此s從此失效。
    這種情況,在rust中,我們說s“移動”到了some_string中
     */
    let s = String::from("hello");
    
    take_ownership(s);
    // s已經失效了!報錯
    println!("{}", s);
}

上面的例子中,外部的String作為參數傳進來后自己就失效了,這在有的情況下是不太方便的。因此,我們使用引用。注意下面的代碼:

fn calculate_length(s: &String) -> usize {
    // &表示引用:允許你引用某些值而!!!不取得他的所有權!!!!
    // 我們把引用作為函數參數的行為叫做借用
    // 引用不能被修改,比如這里就不能使用: s.push_str("hello")
    s.len()
}

fn test() {
    let s = String::from("hello");
    let l = calculate_length(&s);
    println!("{}", s);  // 仍然有效!
}

引用不能被修改,那么可以不可以通過添加mut來讓他可以被修改呢?注意下面的代碼:

fn calculate_length_mut(s: &mut String) -> usize {
    s.push_str("hello");    // 可以修改!
    s.len()
}

fn mut_ref_test() {
    let mut s = String::from("hello");  // 被引用的變量一定要是mut的
    calculate_length_mut(&mut s);   // 同一個作用域里不能同時存在多個可變引用,但是可以存在多個不可變引用
}

字符串切片:字符串切片(&str)其實就是字符串的部分引用,是不可變的:

fn string_stripe_test() {
    let s = String::from("hello world");

    let hello = &s[..5];
    let world = &s[6..];

    let whole = &s[..];

    println!("{} {} is equal to: {}", hello, world, whole);
}

/*
這里有一個小建議:在定義函數的時候,完全使用字符串切片代替字符串引用,只有好處沒有壞處,如下:
fn some_function(s: &String),不好
fn some_function(s: &str),好!
為什么好呢?因為你如果傳進去的是字符串切片(比如let s = "hello"的s),直接傳進去就好了
如果傳進去的是String,那么就可以創建一個完整的字符串切片傳進去,沒有損失任何東西
比如 let my_string = String::from("hello")。這樣子傳進去:&my_string[..]
 */

2.結構體和枚舉

結構體:

struct User {
    username: String,
    email: String, 
    sign_in_count: u64,
    active: bool,
 }

 fn build_user(username: String, active: bool) -> User {
    User {
        email: String::from("aaa@email.com"),
        // 如果傳進來的參數名剛好和字段名相同,就可以簡寫
        username,
        sign_in_count: 111,
        // 這里也是簡寫
        active,
    }
}

// 如果想要基于某一個struct實例來創建一個新的實例,可以使用struct的更新語法,如下
fn struct_update_test() {
    let user1 = User {
        email: String::from("aa@email.com"),
        username: String::from("chr"),
        active: true,
        sign_in_count: 100,
    };

    let user2 = User {
        username: String::from("zyj"),
        // 這里直接更具user1來創建了user2,只是username不一樣
        ..user1
    };

    println!("{},{},{},{}", user2.username, user2.email, user2.active, user2.sign_in_count);
}

fn tuple_struct_test() {
    // 這個叫做tuple struct。適用于想要給一個tuple取名字,讓他不同于其他的tuple
    //(就算里邊的每個元素的類型都是一樣的。就像下邊的Color和Point一樣)
    struct Color(i32, i32, i32);
    struct Point(i32, i32, i32);

    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}


struct Rectangle {
    width: u32,
    length: u32,
}

// 為這個struct定義一個方法
impl Rectangle {
    fn area(&self) -> u32 {
        // 里邊使用&,表明我們這里不需要它的所有權(當然也可以去掉&,甚至是加上mut)
        // 在調用的時候,不需要使用(&rec).area()
        // 因為rust他會自動根據情況來添加&,&mut,或者是*
        self.length * self.width
    }

    // 關聯函數(他不是一個方法!)(其實就是java中的靜態函數,dddd)
    // 通常用于構造器
    // 函數里邊沒有self,說明就是一個關聯函數
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            length: size,
        }
    }
}

枚舉類型:

enum Message {
    // 枚舉類型,下面的Quit、Move稱為該枚舉類型的變體
    // 這個變體是可以附加信息的,比如這里的Write就附加了一個String字符串
    Quit,
    Move { x: u32, y: u32 },
    Write(String),
    ChangeColor(u32, u32, u32),
}

// 當然也可以為枚舉類型創建方法,一樣的
impl Message {
    fn function(&self) {
        println!("do nothing");
    }
}

fn test_1() {
    let q = Message::Quit;
    let m = Message::Move { x: 12, y: 245 };
    let w = Message::Write(String::from("hello"));
    let c = Message::ChangeColor(1, 2, 3);

    w.function();
}

/*
在其他編程語言中,幾乎都存在null這個東西。
而在rust中,不存在null。在標準庫中提供了一個類似的概念
Option<T>枚舉
enum Option<T> {
    Some(T),
    None,
}

這樣做,可以讓None引發的相關錯誤在編譯時期就被發現
 */

枚舉類型常常配合 match 使用:

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> i32 {
    // match 是一個表達式,因此會返回一個值
    // 注意match里邊是 fat arrow( => )
    // match的分支中還可以綁定枚舉類型的附加值!!!
    // 同時,這個match必須覆蓋到所有的匹配。比如對于Option枚舉來說,必須要有Some和None的分支
    // 當然,可以使用“_”來表示通配符
    let value = match coin {
        Coin::Penny => {
            println!("it is a penny!");
            1
        },
        Coin::Nickel => {
            println!("it is a Nickel!");
            5
        },
        Coin::Dime => {
            println!("it is a Dime!");
            10
        },
        Coin::Quarter => {
            println!("it is a Quarter!");
            25
        },
    };
    value
}

fn main() {
    test_1();
    let my_coin = Coin::Dime;
    let my_value = value_in_cents(my_coin);
}

3.常用容器

fn test() {
    let mut v = vec![1, 2, 3];
    // 使用for i in v默認會調用v的into_iter()方法,會得到v的所有權
    // 所以這里使用了可變引用(因為我們還想要改變里邊的數字)
    for i in &mut v {
        *i += 100;
    }
    let number = v[0];
    println!("{}", number);
}

use std::{collections::HashMap, vec};

fn test06() {
    let mut hm = HashMap::new();
    hm.insert(String::from("blue"), 10);
    hm.insert(String::from("yellow"), 15);


    // 另一種創建hashmap的方法:使用collect
    // collect可以把數據整合成很多集合類型,包括HashMap
    let teams = vec![String::from("blue"), String::from("yellow")];
    let scores = vec![12, 11];

    // 這里必須提前聲明這個變量的類型。因為collect方法可以整合成許多類型
    let zip_hashmap: HashMap<_, _> = teams.iter().zip(scores.iter()).collect();
}

fn test07() {
    // HashMap和所有權
    // 被添加進入hashmap的數據的所有權會被hashmap奪走
    let mut h = HashMap::new();
    let s1 = String::from("hello");
    let s2 = String::from("world");
    h.insert(s1, s2);
    // println!("{}, {}", s1, s2);     // 報錯,因為s1和s2的所有權已經被奪走了
    // h.insert(&s1, &s2) 這樣所有權不會被奪走
}


fn test08() {
    // 如何獲取Hashmap的值呢?使用get方法,同樣,返回一個Option
    let mut h = HashMap::new();
    h.insert(String::from("hello"), 11);
    h.insert(String::from("world"), 22);

    let s = String::from("wold");
    // 注意get方法需要接收的是一個引用類型!
    let score = h.get(&s);

    match score {
        Option::Some(number) => println!("{}", number),
        Option::None => println!("none!"),
    };


    // 如何對hashmap遍歷?注意&!!!!因為通常來說,我們遍歷了hashmap之后還要使用它
    // 你如果不加&,那么hashmap的所有權就被奪走了!反復強調!
    for (k, v) in &h {
        println!("{}: {}", k, v);
    }
}


fn test09() {
    // 如何修改hashmap中的值呢?
    let mut scores = HashMap::new();
    scores.insert(String::from("yellow"), 25);
    
    // 檢查blue這個key存不存在?返回一個Entry
    let e = scores.entry(String::from("blue"));
    // 這個or_insert方法會檢查這個entry對象有沒有value,如果沒有,則插入一個默認值
    // 最后返回這個value的可變引用
    e.or_insert(50);
    // 由于yellow已經存在了,所以就沒有插入
    scores.entry(String::from("yellow")).or_insert(100);

    println!("{:?}", scores);
}


fn test10() {
    // 這個or_insert方法,他會返回這個key所對應的value的可變引用。注意下面這個例子:
    let text = "hello world fan print fan fan sit";

    let mut compute = HashMap::new();

    for word in text.split_whitespace() {
        // 下面這個times的類型是 &mut i32,對i32的可變引用
        let times = compute.entry(word).or_insert(0);
        *times += 1;
    }

    println!("{:#?}", compute);
}

4.錯誤處理

在rust中的錯誤處理通常有兩種:

  • 可恢復的錯誤:返回一個Result<T, E>,將錯誤傳遞下去
  • 不可恢復的錯誤:使用panic!宏中斷

如下是打開文件的處理方式

fn test() {
    let f = File::open("hello.txt");
    let f = match f {
        // 這里的file和error變量都是通過模式匹配結構出來的臨時變量(枚舉變體攜帶的信息)
        Result::Ok(file) => file,
        Result::Err(error) => {
            panic!("error opening the ile: {:?}", error);
        }
    };
}

// 傳播錯誤,而不去解決它。看下面一個例子
fn read_username_from_file() -> Result<String, io::Error> { // 這里模仿其他方法,將該函數的執行結果返回
    let f = File::open("hello.txt");

    let mut f = match f {
        Result::Ok(file) => file,
        Result::Err(e) => return Result::Err(e),
    };
    
    let mut s = String::new();
    // 注意到,這里的read_to_string方法用到了自身的可變引用,因此上邊的f必須是mut的
    match f.read_to_string(&mut s) {
        // 注意,這里我們沒有用到Ok的這個附加值,因為我們要的是可變引用s
        Result::Ok(_) => Result::Ok(s),
        Result::Err(e) => Result::Err(e),
    }
}

// 使用 ? 來修改上面的函數
fn read_username_from_file_improved() -> Result<String, io::Error> {
    /*
    把?作用于一個Result,則有兩種可能:
    1. 如果Result是Ok,則Ok附帶的值就是整個表達式的值,程序繼續;
    2. 如果Result是Err,則Err就是整個函數!!!的返回值(相當于直接使用了return Err 
    */
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Result::Ok(s)


    // ? 只能用于返回值為Result的函數里邊!
}

5.trait

rust中的trait和java中的interface差不多(個人感覺)

// 定義一個Summary trait,實現這個trait的結構體必須實現trait里邊的方法
pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String, 
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

將trait作為函數參數:

pub fn notify(item: impl Summary) {
    println!("this item has implemented the trait: {}", item.summarize());
}

// 使用加號來表明這個類型需要實現多個trait
pub fn notify_1<T: Summary + Display>(item: T) {
    println!("this is more simple, {}", item.summarize());
}

// 但是多個參數都指定多個trait,這會導致函數簽名不好看
pub fn notify_2<T, U>(a: T, b: U) -> String
where 
    T: Summary + Display,
    U: Clone + Debug,
{
    format!("this way: {}", a.summarize())
}

6.生命周期

生命周期的主要目標就是:避免懸垂引用

函數參數結構體包含多個引用時,Rust 編譯器需要明確知道這些引用的存活關系,否則會報錯要求手動標注

生命周期的標注:描述了多個引用的生命周期之間的關系(因此標注單個引用的生命周期沒有任何意義),但是不會影響到生命周期

下面是一個典型的場景(函數的返回引用依賴于輸入參數)

// 錯誤版本
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

// 正確標注版本
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    ...
}

當結構體中包含引用時,同樣,結構體不能存活的比引用長:

struct BookShelf<'a> {
    books: &'a [String],
}

fn main() {
    let my_books = vec![
        String::from("rust programming"),
        String::from("cpp programming")
    ];

    let shelf = BookShelf {
        books: &my_books
    };
}

生命周期的省略規則:

  • 每個引用類型的輸入參數都有自己的生命周期
  • 如果只有一個輸入生命周期桉樹,那么該生命周期被賦給所有輸出生命周期參數
  • 如果有多個輸入生命周期參數,但是其中一個是 &self 或者 &mut self,則 self 的生命周期會被賦給所有輸出生命周期參數

經過上面的規則后,如果仍然存在沒有確定生命周期的引用,那么編譯器就會報錯

7.函數式編程

函數式編程的風格: 將一個函數作為參數傳入函數中; 將函數作為返回值進行返回; 將函數賦值給變量,晚點執行。。。

閉包: 是匿名函數 保存為變量、作為參數 可以在一個地方創建閉包,然后在另一個上下文調用閉包;來完成運算 可從其定義的作用域捕獲值

fn generate_workout(intensity: u32, random_number: u32) {

    // 這就是一個閉包,將一段函數賦值給一個變量
    // 閉包不需要標注參數類型,以及返回值類型
    let mut expensive_closure = Cacher::new(| num: u32 | {
        println!("calculating slowly....");
        thread::sleep(Duration::from_secs(3));
        num
    });

    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_closure.value(intensity));
        println!("Next, do {} situps!", expensive_closure.value(intensity));
    } else {
        if random_number == 3 {
            println!("take a break.");
        } else {
            println!("run for {} minutes!", expensive_closure.value(intensity));
        }
    }
}

// 使用struct來存儲閉包,可以達到如下效果:
// 當閉包需要被執行的時候,就執行,同時結構體可以將這個執行的值給保存下來
// 這樣,無論使用多少次閉包的值,閉包都只會被執行一次!
struct Cacher<T>
where
// 當需要指定fn的trait時,首先使用fn。如果閉包里邊的情況會需要實現FnOnce或者FnMut,編譯器會告訴你
    T: Fn(u32) -> u32,
{
    calculate: T,
    value: Option<u32>,
}

impl<T> Cacher<T>
where 
    T: Fn(u32) -> u32
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            calculation: calculation,
            value: Option::None
        }
    }

    fn value(&mut self, args: u32) -> u32 {
        match self.value {
            Option::Some(val) => val,
            Option::None => {
                let val = (self.calculation)(args);
                self.value = Option::Some(val);
                val
            }
        }
    }
}

迭代器相關:

  • iter():創建不可變引用迭代器
  • into_iter():取得容器所有權(使用for e in v默認調用into_iter())
  • into_iter():創建可變引用迭代器

例子:

fn iter_test() {
    let mut v = vec![String::from("hello"), String::from("hey")];

    let mut it1 = v.iter();
    let e1 = it1.next(); // e1的類型是Option<&String>

    // iter_mut方法,得到 &mut String
    let mut it3 = v.iter_mut();
    let e3 = it3.next();

    // 使用into_iter方法,創建的迭代器會取得所有權 String
    let mut it2 = v.into_iter();
    let e2 = it2.next();    // Option<String>
}

// map方法,collect方法常用
fn map_test() {
    let v1 = vec![1, 2, 3];
    let it1 = v1.iter();
    // 在iter上調用map方法會產生一個新的iter
    let it2 = it1.map(|val| {
        val + 1
    });
    // 在iter上調用collect方法會將所有元素放到一個集合里邊,collect方法是一個消耗性方法
    let v2: Vec<_> = it2.collect();
    for i in v2 {
        println!("{}", i);
    }
    // 原來的集合沒有變化
    for i in v1 {
        println!("{}", i);
    }

    // 通常像下面一樣使用鏈式法則
    // let v3: Vec<_> = v1.iter().map(|a| a + 1).collect();
}

迭代器的 filter、map、collect、next都是很常用的

struct Shoe {
    size: u32,
    style: String,
}

fn shoes_in_my_size(shoes: Vec[Shoe], my_size: u32) -> Vec<Shoe> {
    shoes.into_iter().filter(| shoe | {
        shoe.size == my_size
    }).collect()
}

8.智能指針

/**
 * Box<T>是最簡單的智能指針,它在stack上擁有一個指針,其指向heap上的數據
 * 沒有其他的額外功能了
 * 那我感覺Box<T>其實就是Cpp中的指向T類型的指針
 * 所以以后想要在rust中使用指針,直接使用Box就行
 */
fn box_test_1() {
    // 這里的10就是放在heap上的
    let a = Box::new(10);
    println!("a is: {}", a);
    // 當a離開作用域時,它會釋放這個10以及在stack上的指針
}

// 可以使用Box來實現rust中的鏈表:

enum List {
    // Cons中有一個指向List的指針
    Cons(i32, Box<List>),
    Nil,
}
// 用來遍歷一個鏈表!
fn read_a_list(l: &List) {
    let result = match l {
        // 這里的next是 &Box<List>
        List::Cons(value, next) => {
            println!("{}", value);
            read_a_list(next);  // 由于Box實現了Deref strait,他會自動隱式解引用 &Box<List> -> &List
        },
        List::Nil => {},
    };
}

deref trait:

fn deref_test_1() {
    let x = 14;
    let y = &x; // 這里y是一個指針(引用)
    assert_eq!(x, 14);
    assert_eq!(*y, 14); // 這里需要解引用才能獲得y所指向的值
}

fn deref_test_2() {
    let x = 14;
    let y = Box::new(x);
    assert_eq!(x, 14);
    assert_eq!(*y, 14); // Box實現了deref,因此我們可以使用*y來訪問它所指向的值
}

/**
 * 函數和方法的隱式解引用轉化
 * 當把某類型的引用傳遞給函數或者方法時,但他的類型與定義的參數類型不匹配,
 * 編譯器就會自動調用deref轉換
 * 如下一個例子(在編譯的時候發生,因此沒有額外的性能開銷):
 */

fn hello(name: &str) {
    println!("hello, {}", name);
}

fn deref_test_4() {
    let m = MyBox::new(String::from("rust"));


    // 這里 &MyBox<String>  -->  &String   -->   &str
    // 編譯器自動不斷地調用 deref方法,從而最終轉化為 &str
    // 不然就應該是這樣的: hello(&(*m)[..]),其中 *m 是String
    hello(&m);
}

Rc指針:

fn rc_test_1() {
    /**
     * 這里的要求是,a是一個鏈表,5--10--nil
     * 我們想要創建b鏈表: 3--a(5--10--nil),且不復制
     * c鏈表:4--a(5--10-nil)
     * 如果還要使用普通的Box來完成,就會發現,a如果被b鏈接了,它的所有權就被b拿走了
     * 再想要使用c鏈接a就不行了
     * 
     * 這種需要多個指針指向一個數據的情況,就需要使用Rc<T>這個指針!具體的使用如下:
     */
    let a = Rc::new(ListRc::Cons(5, Rc::new(ListRc::Cons(10, Rc::new(ListRc::Nil)))));
    println!("count after creating a = {}", Rc::strong_count(&a));

    // Rc::clone()  它只是增加引用,不會執行數據的深度拷貝操作
    // 相當于b多了一個3節點,然后連接上a
    let b = ListRc::Cons(3, Rc::clone(&a));
    println!("count after creating b = {}", Rc::strong_count(&a));
    {
        let c = ListRc::Cons(4, Rc::clone(&a));
        println!("count after creating c = {}", Rc::strong_count(&a));
    }
    println!("count after c dies  = {}", Rc::strong_count(&a));
}

fn my_rc_test() {
    // 以下示例中,s, pt1, pt2同時指向字符串hello
    let s = Rc::new(String::from("hello"));
    println!("{}", Rc::strong_count(&s));
    // let pt1 = Rc::new(s);
    // let pt2 = Rc::new(s); 報錯,因為s的所有權已經被pt1搶走了!
    let pt1 = Rc::clone(&s);
    println!("{}", Rc::strong_count(&s));
    let pt2 = Rc::clone(&s);
    println!("{}", Rc::strong_count(&s));
}

RefCell智能指針,可以:

  • 通過不可變引用修改內部數據
  • 在運行時借用檢查,如果違反規則就出發panic
  • 單線程專用
fn refCell_test() {
    let r = RefCell::new(String::from("hello"));
    {
        // 通過不可變引用修改內部數據
        let mut change = r.borrow_mut();
        change.push_str(", rust!");
    }
    // 輸出 hello, rust!
    println!("{}", r.borrow());
}

// 一個常用的場景是,搭配Rc指針,實現多個所有者共享可變數據
fn test1() {
    let account = Rc::new(RefCell::new(100));

    let chr = Rc::clone(&account);
    let zyj = Rc::clone(&account);

    *chr.borrow_mut() += 100;
    *zyj.borrow_mut() -= 10;

    // 輸出190
    println!("there are {} left in the account.", account.borrow());
}

RefCell 要求在任何時刻,只能存在:

  • 一個可變借用(borrow_mut)
  • 或者多個不可變借用(borrow)

9.并發

/**
 * Concurrent(并發): 程序不同部分獨立執行
 * Parallel(并行): 程序不同部分同時運行
 * 
 * 實現線程的方式:
 * 1. 調用OS的api創建線程,1:1(運行時較小,一個操作系統的線程對應一個語言中創建的線程)Rust
 * 2. 語言自己實現的線程,M:N(運行時更大)
 */
use std::thread::{self, spawn};
use std::time::Duration;

fn test_1() {
    // main程序結束后,不管這個spawned線程有沒有結束,整個程序結束
    // 因此這個線程大概率是執行不完的
    thread::spawn(|| {
        for i in 1..10 {
            println!("number {} from spawned thread.", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("number {} from main thread.", i);
        thread::sleep(Duration::from_millis(1));
    }
}

fn test_2() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("number {} from spawned thread.", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    // 調用handle的join方法會阻塞當前運行線程的執行,直到handle所表示的線程終結
    // 因此這里就會spawned線程1到10,再執行主線程的1到5
    handle.join().unwrap();

    for i in 1..5 {
        println!("number {} from main thread.", i);
        thread::sleep(Duration::from_millis(1));
    }
}

fn test3() {
    let v = vec![1, 2, 3];
    // 這里我在線程中使用到了閉包外部的數據 v,提示有誤
    // 因為v的壽命有可能比這個閉包還要短,這會導致問題
    // 所以現在可以使用move關鍵字來使得閉包獲得 v 的所有權
    let handle = thread::spawn(move || {
        for e in v.iter() {
            println!("{}", e);
        }
    });

    handle.join().expect("something wrong.");
}

消息傳遞:

use std::sync::mpsc;
fn test_4() {
    // 消息傳遞
    // 使用mpsc::channel創建一個通道,multiple producer and single consumer
    let (sender, receiver) = mpsc::channel();

    thread::spawn(move || {
        // 這個線程必須擁有通道發送端的所有權才能往通道里發送消息
        let value = String::from("hello!");
        sender.send(value).unwrap();
    });
    // 這里的recv方法會阻塞當前的線程,直到有消息傳入到通道
    // try_recv方法不會阻塞
    let received= receiver.recv().unwrap();
    println!("Got: {}", received);
}

fn test_5() {
    let (sender, receiver) = mpsc::channel();

    thread::spawn(move || {
        let values = vec![String::from("Cpp"),
         String::from("java"),
         String::from("rust"),
         String::from("C#")];

        for value in values.into_iter() {
            // 這里不能使用iter(),因為我們必須獲得它的所有權才能send
            sender.send(value).unwrap();
            thread::sleep(Duration::from_millis(500));
        }
    });

    // 更推薦使用這種方法來接收消息,底層用到了recv方法
    // 會阻塞!
    for received in receiver {
        println!("Got: {}", received);
    }
}

// 使用多個發送者
fn test_6() {
    let (sender, receiver) = mpsc::channel();
    // 克隆!
    let another_sender = mpsc::Sender::clone(&sender);

    thread::spawn(move || {
        let values = vec![String::from("Cpp"),
         String::from("java"),
         String::from("rust"),
         String::from("C#")];

        for value in values.into_iter() {
            sender.send(value).unwrap();
            thread::sleep(Duration::from_millis(500));
        }
    });

    thread::spawn(move || {
        let values = vec![String::from("###Cpp"),
         String::from("###java"),
         String::from("###rust"),
         String::from("###C#")];

        for value in values.into_iter() {
            another_sender.send(value).unwrap();
            thread::sleep(Duration::from_millis(500));
        }
    });

    // 更推薦使用這種方法來接收消息,底層用到了recv方法
    // 會阻塞!
    for received in receiver {
        println!("Got: {}", received);
    }
}

// 以上都是使用通信的方法實現并發

use std::sync::Mutex;

fn test_7() {
    // Mutex 是一個智能指針,互斥鎖
    let m = Mutex::new(5);
    {   
        // 訪問數據前,先使用lock來獲得鎖。這個方法是阻塞的
        // lock可能失敗,返回一個MetexGuard智能指針
        let mut num = m.lock().unwrap();
        *num += 12;
    }
    println!("{}", m.lock().unwrap());
}

use std::sync::Arc;
// 使用Arc來進行原子引用計數(和Rc一模一樣,只是用在多線程環境下)
fn test_8() {
    // let counter = Mutex::new(0);
    let counter = Arc::new(Mutex::new(0));
    let mut handles = Vec::new();

    for _ in 0..10 {
        // 多個線程同時訪問這個互斥鎖里邊的數據
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles.into_iter() {
        handle.join().unwrap();
    }

    println!("{}", counter.lock().unwrap());
}
0條評論
作者已關閉評論
陳****然
4文章數
0粉絲數
陳****然
4 文章 | 0 粉絲
陳****然
4文章數
0粉絲數
陳****然
4 文章 | 0 粉絲
原創

rust基礎學習

2025-09-08 02:21:33
1
0

1.rust所有權

語句和表達式:前者不返回值,而后者返回值。在rust中,每一個代碼塊的最后一行可以放一個表達式,這個表達式就作為這整個代碼塊的返回值。

fn if_test() {
    let condition = true;
    let x = {
        if condition {
            12
        } else {
            let y = 99;
            y + 1
        }
    };
    println!("{}", x);
}

不同語言的垃圾清理規則:

  • java:有gc收集器,當察覺到某個值沒有使用之后,自動回收內存
  • c++:開發者手動回收內存
  • rust:當擁有這個值的變量走出作用范圍后,內存就會自動收集(調用drop函數)

所有權,就是因為heap內存管理而產生的。rust中的所有權規則:

  • 每個值都對應一個變量,這個變量就是這個值的所有者(變量擁有這個值)
  • 每個值同時只能有一個所有者
  • 當所有者超出作用域的時候,這個值就會被清理
fn test1() {
    let s1 = String::from("hello");
    let s2 = s1;
    println!("{}", s1); // 報錯,因為"hello"的所有權被s2奪走,s1失效!
}
fn take_ownership(some_string: String) {
    println!("{}", some_string);
}

fn ownership_test() {
    /* 在這里例子中,s傳入take_ownership中,該函數的some_string也指向了這個s指向的值
    而String實現了drop strait,因此s從此失效。
    這種情況,在rust中,我們說s“移動”到了some_string中
     */
    let s = String::from("hello");
    
    take_ownership(s);
    // s已經失效了!報錯
    println!("{}", s);
}

上面的例子中,外部的String作為參數傳進來后自己就失效了,這在有的情況下是不太方便的。因此,我們使用引用。注意下面的代碼:

fn calculate_length(s: &String) -> usize {
    // &表示引用:允許你引用某些值而!!!不取得他的所有權!!!!
    // 我們把引用作為函數參數的行為叫做借用
    // 引用不能被修改,比如這里就不能使用: s.push_str("hello")
    s.len()
}

fn test() {
    let s = String::from("hello");
    let l = calculate_length(&s);
    println!("{}", s);  // 仍然有效!
}

引用不能被修改,那么可以不可以通過添加mut來讓他可以被修改呢?注意下面的代碼:

fn calculate_length_mut(s: &mut String) -> usize {
    s.push_str("hello");    // 可以修改!
    s.len()
}

fn mut_ref_test() {
    let mut s = String::from("hello");  // 被引用的變量一定要是mut的
    calculate_length_mut(&mut s);   // 同一個作用域里不能同時存在多個可變引用,但是可以存在多個不可變引用
}

字符串切片:字符串切片(&str)其實就是字符串的部分引用,是不可變的:

fn string_stripe_test() {
    let s = String::from("hello world");

    let hello = &s[..5];
    let world = &s[6..];

    let whole = &s[..];

    println!("{} {} is equal to: {}", hello, world, whole);
}

/*
這里有一個小建議:在定義函數的時候,完全使用字符串切片代替字符串引用,只有好處沒有壞處,如下:
fn some_function(s: &String),不好
fn some_function(s: &str),好!
為什么好呢?因為你如果傳進去的是字符串切片(比如let s = "hello"的s),直接傳進去就好了
如果傳進去的是String,那么就可以創建一個完整的字符串切片傳進去,沒有損失任何東西
比如 let my_string = String::from("hello")。這樣子傳進去:&my_string[..]
 */

2.結構體和枚舉

結構體:

struct User {
    username: String,
    email: String, 
    sign_in_count: u64,
    active: bool,
 }

 fn build_user(username: String, active: bool) -> User {
    User {
        email: String::from("aaa@email.com"),
        // 如果傳進來的參數名剛好和字段名相同,就可以簡寫
        username,
        sign_in_count: 111,
        // 這里也是簡寫
        active,
    }
}

// 如果想要基于某一個struct實例來創建一個新的實例,可以使用struct的更新語法,如下
fn struct_update_test() {
    let user1 = User {
        email: String::from("aa@email.com"),
        username: String::from("chr"),
        active: true,
        sign_in_count: 100,
    };

    let user2 = User {
        username: String::from("zyj"),
        // 這里直接更具user1來創建了user2,只是username不一樣
        ..user1
    };

    println!("{},{},{},{}", user2.username, user2.email, user2.active, user2.sign_in_count);
}

fn tuple_struct_test() {
    // 這個叫做tuple struct。適用于想要給一個tuple取名字,讓他不同于其他的tuple
    //(就算里邊的每個元素的類型都是一樣的。就像下邊的Color和Point一樣)
    struct Color(i32, i32, i32);
    struct Point(i32, i32, i32);

    let black = Color(0, 0, 0);
    let origin = Point(0, 0, 0);
}


struct Rectangle {
    width: u32,
    length: u32,
}

// 為這個struct定義一個方法
impl Rectangle {
    fn area(&self) -> u32 {
        // 里邊使用&,表明我們這里不需要它的所有權(當然也可以去掉&,甚至是加上mut)
        // 在調用的時候,不需要使用(&rec).area()
        // 因為rust他會自動根據情況來添加&,&mut,或者是*
        self.length * self.width
    }

    // 關聯函數(他不是一個方法!)(其實就是java中的靜態函數,dddd)
    // 通常用于構造器
    // 函數里邊沒有self,說明就是一個關聯函數
    fn square(size: u32) -> Rectangle {
        Rectangle {
            width: size,
            length: size,
        }
    }
}

枚舉類型:

enum Message {
    // 枚舉類型,下面的Quit、Move稱為該枚舉類型的變體
    // 這個變體是可以附加信息的,比如這里的Write就附加了一個String字符串
    Quit,
    Move { x: u32, y: u32 },
    Write(String),
    ChangeColor(u32, u32, u32),
}

// 當然也可以為枚舉類型創建方法,一樣的
impl Message {
    fn function(&self) {
        println!("do nothing");
    }
}

fn test_1() {
    let q = Message::Quit;
    let m = Message::Move { x: 12, y: 245 };
    let w = Message::Write(String::from("hello"));
    let c = Message::ChangeColor(1, 2, 3);

    w.function();
}

/*
在其他編程語言中,幾乎都存在null這個東西。
而在rust中,不存在null。在標準庫中提供了一個類似的概念
Option<T>枚舉
enum Option<T> {
    Some(T),
    None,
}

這樣做,可以讓None引發的相關錯誤在編譯時期就被發現
 */

枚舉類型常常配合 match 使用:

enum Coin {
    Penny,
    Nickel,
    Dime,
    Quarter,
}

fn value_in_cents(coin: Coin) -> i32 {
    // match 是一個表達式,因此會返回一個值
    // 注意match里邊是 fat arrow( => )
    // match的分支中還可以綁定枚舉類型的附加值!!!
    // 同時,這個match必須覆蓋到所有的匹配。比如對于Option枚舉來說,必須要有Some和None的分支
    // 當然,可以使用“_”來表示通配符
    let value = match coin {
        Coin::Penny => {
            println!("it is a penny!");
            1
        },
        Coin::Nickel => {
            println!("it is a Nickel!");
            5
        },
        Coin::Dime => {
            println!("it is a Dime!");
            10
        },
        Coin::Quarter => {
            println!("it is a Quarter!");
            25
        },
    };
    value
}

fn main() {
    test_1();
    let my_coin = Coin::Dime;
    let my_value = value_in_cents(my_coin);
}

3.常用容器

fn test() {
    let mut v = vec![1, 2, 3];
    // 使用for i in v默認會調用v的into_iter()方法,會得到v的所有權
    // 所以這里使用了可變引用(因為我們還想要改變里邊的數字)
    for i in &mut v {
        *i += 100;
    }
    let number = v[0];
    println!("{}", number);
}

use std::{collections::HashMap, vec};

fn test06() {
    let mut hm = HashMap::new();
    hm.insert(String::from("blue"), 10);
    hm.insert(String::from("yellow"), 15);


    // 另一種創建hashmap的方法:使用collect
    // collect可以把數據整合成很多集合類型,包括HashMap
    let teams = vec![String::from("blue"), String::from("yellow")];
    let scores = vec![12, 11];

    // 這里必須提前聲明這個變量的類型。因為collect方法可以整合成許多類型
    let zip_hashmap: HashMap<_, _> = teams.iter().zip(scores.iter()).collect();
}

fn test07() {
    // HashMap和所有權
    // 被添加進入hashmap的數據的所有權會被hashmap奪走
    let mut h = HashMap::new();
    let s1 = String::from("hello");
    let s2 = String::from("world");
    h.insert(s1, s2);
    // println!("{}, {}", s1, s2);     // 報錯,因為s1和s2的所有權已經被奪走了
    // h.insert(&s1, &s2) 這樣所有權不會被奪走
}


fn test08() {
    // 如何獲取Hashmap的值呢?使用get方法,同樣,返回一個Option
    let mut h = HashMap::new();
    h.insert(String::from("hello"), 11);
    h.insert(String::from("world"), 22);

    let s = String::from("wold");
    // 注意get方法需要接收的是一個引用類型!
    let score = h.get(&s);

    match score {
        Option::Some(number) => println!("{}", number),
        Option::None => println!("none!"),
    };


    // 如何對hashmap遍歷?注意&!!!!因為通常來說,我們遍歷了hashmap之后還要使用它
    // 你如果不加&,那么hashmap的所有權就被奪走了!反復強調!
    for (k, v) in &h {
        println!("{}: {}", k, v);
    }
}


fn test09() {
    // 如何修改hashmap中的值呢?
    let mut scores = HashMap::new();
    scores.insert(String::from("yellow"), 25);
    
    // 檢查blue這個key存不存在?返回一個Entry
    let e = scores.entry(String::from("blue"));
    // 這個or_insert方法會檢查這個entry對象有沒有value,如果沒有,則插入一個默認值
    // 最后返回這個value的可變引用
    e.or_insert(50);
    // 由于yellow已經存在了,所以就沒有插入
    scores.entry(String::from("yellow")).or_insert(100);

    println!("{:?}", scores);
}


fn test10() {
    // 這個or_insert方法,他會返回這個key所對應的value的可變引用。注意下面這個例子:
    let text = "hello world fan print fan fan sit";

    let mut compute = HashMap::new();

    for word in text.split_whitespace() {
        // 下面這個times的類型是 &mut i32,對i32的可變引用
        let times = compute.entry(word).or_insert(0);
        *times += 1;
    }

    println!("{:#?}", compute);
}

4.錯誤處理

在rust中的錯誤處理通常有兩種:

  • 可恢復的錯誤:返回一個Result<T, E>,將錯誤傳遞下去
  • 不可恢復的錯誤:使用panic!宏中斷

如下是打開文件的處理方式

fn test() {
    let f = File::open("hello.txt");
    let f = match f {
        // 這里的file和error變量都是通過模式匹配結構出來的臨時變量(枚舉變體攜帶的信息)
        Result::Ok(file) => file,
        Result::Err(error) => {
            panic!("error opening the ile: {:?}", error);
        }
    };
}

// 傳播錯誤,而不去解決它。看下面一個例子
fn read_username_from_file() -> Result<String, io::Error> { // 這里模仿其他方法,將該函數的執行結果返回
    let f = File::open("hello.txt");

    let mut f = match f {
        Result::Ok(file) => file,
        Result::Err(e) => return Result::Err(e),
    };
    
    let mut s = String::new();
    // 注意到,這里的read_to_string方法用到了自身的可變引用,因此上邊的f必須是mut的
    match f.read_to_string(&mut s) {
        // 注意,這里我們沒有用到Ok的這個附加值,因為我們要的是可變引用s
        Result::Ok(_) => Result::Ok(s),
        Result::Err(e) => Result::Err(e),
    }
}

// 使用 ? 來修改上面的函數
fn read_username_from_file_improved() -> Result<String, io::Error> {
    /*
    把?作用于一個Result,則有兩種可能:
    1. 如果Result是Ok,則Ok附帶的值就是整個表達式的值,程序繼續;
    2. 如果Result是Err,則Err就是整個函數!!!的返回值(相當于直接使用了return Err 
    */
    let mut f = File::open("hello.txt")?;
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Result::Ok(s)


    // ? 只能用于返回值為Result的函數里邊!
}

5.trait

rust中的trait和java中的interface差不多(個人感覺)

// 定義一個Summary trait,實現這個trait的結構體必須實現trait里邊的方法
pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String, 
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

將trait作為函數參數:

pub fn notify(item: impl Summary) {
    println!("this item has implemented the trait: {}", item.summarize());
}

// 使用加號來表明這個類型需要實現多個trait
pub fn notify_1<T: Summary + Display>(item: T) {
    println!("this is more simple, {}", item.summarize());
}

// 但是多個參數都指定多個trait,這會導致函數簽名不好看
pub fn notify_2<T, U>(a: T, b: U) -> String
where 
    T: Summary + Display,
    U: Clone + Debug,
{
    format!("this way: {}", a.summarize())
}

6.生命周期

生命周期的主要目標就是:避免懸垂引用

函數參數結構體包含多個引用時,Rust 編譯器需要明確知道這些引用的存活關系,否則會報錯要求手動標注

生命周期的標注:描述了多個引用的生命周期之間的關系(因此標注單個引用的生命周期沒有任何意義),但是不會影響到生命周期

下面是一個典型的場景(函數的返回引用依賴于輸入參數)

// 錯誤版本
fn longest(x: &str, y: &str) -> &str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}

// 正確標注版本
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    ...
}

當結構體中包含引用時,同樣,結構體不能存活的比引用長:

struct BookShelf<'a> {
    books: &'a [String],
}

fn main() {
    let my_books = vec![
        String::from("rust programming"),
        String::from("cpp programming")
    ];

    let shelf = BookShelf {
        books: &my_books
    };
}

生命周期的省略規則:

  • 每個引用類型的輸入參數都有自己的生命周期
  • 如果只有一個輸入生命周期桉樹,那么該生命周期被賦給所有輸出生命周期參數
  • 如果有多個輸入生命周期參數,但是其中一個是 &self 或者 &mut self,則 self 的生命周期會被賦給所有輸出生命周期參數

經過上面的規則后,如果仍然存在沒有確定生命周期的引用,那么編譯器就會報錯

7.函數式編程

函數式編程的風格: 將一個函數作為參數傳入函數中; 將函數作為返回值進行返回; 將函數賦值給變量,晚點執行。。。

閉包: 是匿名函數 保存為變量、作為參數 可以在一個地方創建閉包,然后在另一個上下文調用閉包;來完成運算 可從其定義的作用域捕獲值

fn generate_workout(intensity: u32, random_number: u32) {

    // 這就是一個閉包,將一段函數賦值給一個變量
    // 閉包不需要標注參數類型,以及返回值類型
    let mut expensive_closure = Cacher::new(| num: u32 | {
        println!("calculating slowly....");
        thread::sleep(Duration::from_secs(3));
        num
    });

    if intensity < 25 {
        println!("Today, do {} pushups!", expensive_closure.value(intensity));
        println!("Next, do {} situps!", expensive_closure.value(intensity));
    } else {
        if random_number == 3 {
            println!("take a break.");
        } else {
            println!("run for {} minutes!", expensive_closure.value(intensity));
        }
    }
}

// 使用struct來存儲閉包,可以達到如下效果:
// 當閉包需要被執行的時候,就執行,同時結構體可以將這個執行的值給保存下來
// 這樣,無論使用多少次閉包的值,閉包都只會被執行一次!
struct Cacher<T>
where
// 當需要指定fn的trait時,首先使用fn。如果閉包里邊的情況會需要實現FnOnce或者FnMut,編譯器會告訴你
    T: Fn(u32) -> u32,
{
    calculate: T,
    value: Option<u32>,
}

impl<T> Cacher<T>
where 
    T: Fn(u32) -> u32
{
    fn new(calculation: T) -> Cacher<T> {
        Cacher {
            calculation: calculation,
            value: Option::None
        }
    }

    fn value(&mut self, args: u32) -> u32 {
        match self.value {
            Option::Some(val) => val,
            Option::None => {
                let val = (self.calculation)(args);
                self.value = Option::Some(val);
                val
            }
        }
    }
}

迭代器相關:

  • iter():創建不可變引用迭代器
  • into_iter():取得容器所有權(使用for e in v默認調用into_iter())
  • into_iter():創建可變引用迭代器

例子:

fn iter_test() {
    let mut v = vec![String::from("hello"), String::from("hey")];

    let mut it1 = v.iter();
    let e1 = it1.next(); // e1的類型是Option<&String>

    // iter_mut方法,得到 &mut String
    let mut it3 = v.iter_mut();
    let e3 = it3.next();

    // 使用into_iter方法,創建的迭代器會取得所有權 String
    let mut it2 = v.into_iter();
    let e2 = it2.next();    // Option<String>
}

// map方法,collect方法常用
fn map_test() {
    let v1 = vec![1, 2, 3];
    let it1 = v1.iter();
    // 在iter上調用map方法會產生一個新的iter
    let it2 = it1.map(|val| {
        val + 1
    });
    // 在iter上調用collect方法會將所有元素放到一個集合里邊,collect方法是一個消耗性方法
    let v2: Vec<_> = it2.collect();
    for i in v2 {
        println!("{}", i);
    }
    // 原來的集合沒有變化
    for i in v1 {
        println!("{}", i);
    }

    // 通常像下面一樣使用鏈式法則
    // let v3: Vec<_> = v1.iter().map(|a| a + 1).collect();
}

迭代器的 filter、map、collect、next都是很常用的

struct Shoe {
    size: u32,
    style: String,
}

fn shoes_in_my_size(shoes: Vec[Shoe], my_size: u32) -> Vec<Shoe> {
    shoes.into_iter().filter(| shoe | {
        shoe.size == my_size
    }).collect()
}

8.智能指針

/**
 * Box<T>是最簡單的智能指針,它在stack上擁有一個指針,其指向heap上的數據
 * 沒有其他的額外功能了
 * 那我感覺Box<T>其實就是Cpp中的指向T類型的指針
 * 所以以后想要在rust中使用指針,直接使用Box就行
 */
fn box_test_1() {
    // 這里的10就是放在heap上的
    let a = Box::new(10);
    println!("a is: {}", a);
    // 當a離開作用域時,它會釋放這個10以及在stack上的指針
}

// 可以使用Box來實現rust中的鏈表:

enum List {
    // Cons中有一個指向List的指針
    Cons(i32, Box<List>),
    Nil,
}
// 用來遍歷一個鏈表!
fn read_a_list(l: &List) {
    let result = match l {
        // 這里的next是 &Box<List>
        List::Cons(value, next) => {
            println!("{}", value);
            read_a_list(next);  // 由于Box實現了Deref strait,他會自動隱式解引用 &Box<List> -> &List
        },
        List::Nil => {},
    };
}

deref trait:

fn deref_test_1() {
    let x = 14;
    let y = &x; // 這里y是一個指針(引用)
    assert_eq!(x, 14);
    assert_eq!(*y, 14); // 這里需要解引用才能獲得y所指向的值
}

fn deref_test_2() {
    let x = 14;
    let y = Box::new(x);
    assert_eq!(x, 14);
    assert_eq!(*y, 14); // Box實現了deref,因此我們可以使用*y來訪問它所指向的值
}

/**
 * 函數和方法的隱式解引用轉化
 * 當把某類型的引用傳遞給函數或者方法時,但他的類型與定義的參數類型不匹配,
 * 編譯器就會自動調用deref轉換
 * 如下一個例子(在編譯的時候發生,因此沒有額外的性能開銷):
 */

fn hello(name: &str) {
    println!("hello, {}", name);
}

fn deref_test_4() {
    let m = MyBox::new(String::from("rust"));


    // 這里 &MyBox<String>  -->  &String   -->   &str
    // 編譯器自動不斷地調用 deref方法,從而最終轉化為 &str
    // 不然就應該是這樣的: hello(&(*m)[..]),其中 *m 是String
    hello(&m);
}

Rc指針:

fn rc_test_1() {
    /**
     * 這里的要求是,a是一個鏈表,5--10--nil
     * 我們想要創建b鏈表: 3--a(5--10--nil),且不復制
     * c鏈表:4--a(5--10-nil)
     * 如果還要使用普通的Box來完成,就會發現,a如果被b鏈接了,它的所有權就被b拿走了
     * 再想要使用c鏈接a就不行了
     * 
     * 這種需要多個指針指向一個數據的情況,就需要使用Rc<T>這個指針!具體的使用如下:
     */
    let a = Rc::new(ListRc::Cons(5, Rc::new(ListRc::Cons(10, Rc::new(ListRc::Nil)))));
    println!("count after creating a = {}", Rc::strong_count(&a));

    // Rc::clone()  它只是增加引用,不會執行數據的深度拷貝操作
    // 相當于b多了一個3節點,然后連接上a
    let b = ListRc::Cons(3, Rc::clone(&a));
    println!("count after creating b = {}", Rc::strong_count(&a));
    {
        let c = ListRc::Cons(4, Rc::clone(&a));
        println!("count after creating c = {}", Rc::strong_count(&a));
    }
    println!("count after c dies  = {}", Rc::strong_count(&a));
}

fn my_rc_test() {
    // 以下示例中,s, pt1, pt2同時指向字符串hello
    let s = Rc::new(String::from("hello"));
    println!("{}", Rc::strong_count(&s));
    // let pt1 = Rc::new(s);
    // let pt2 = Rc::new(s); 報錯,因為s的所有權已經被pt1搶走了!
    let pt1 = Rc::clone(&s);
    println!("{}", Rc::strong_count(&s));
    let pt2 = Rc::clone(&s);
    println!("{}", Rc::strong_count(&s));
}

RefCell智能指針,可以:

  • 通過不可變引用修改內部數據
  • 在運行時借用檢查,如果違反規則就出發panic
  • 單線程專用
fn refCell_test() {
    let r = RefCell::new(String::from("hello"));
    {
        // 通過不可變引用修改內部數據
        let mut change = r.borrow_mut();
        change.push_str(", rust!");
    }
    // 輸出 hello, rust!
    println!("{}", r.borrow());
}

// 一個常用的場景是,搭配Rc指針,實現多個所有者共享可變數據
fn test1() {
    let account = Rc::new(RefCell::new(100));

    let chr = Rc::clone(&account);
    let zyj = Rc::clone(&account);

    *chr.borrow_mut() += 100;
    *zyj.borrow_mut() -= 10;

    // 輸出190
    println!("there are {} left in the account.", account.borrow());
}

RefCell 要求在任何時刻,只能存在:

  • 一個可變借用(borrow_mut)
  • 或者多個不可變借用(borrow)

9.并發

/**
 * Concurrent(并發): 程序不同部分獨立執行
 * Parallel(并行): 程序不同部分同時運行
 * 
 * 實現線程的方式:
 * 1. 調用OS的api創建線程,1:1(運行時較小,一個操作系統的線程對應一個語言中創建的線程)Rust
 * 2. 語言自己實現的線程,M:N(運行時更大)
 */
use std::thread::{self, spawn};
use std::time::Duration;

fn test_1() {
    // main程序結束后,不管這個spawned線程有沒有結束,整個程序結束
    // 因此這個線程大概率是執行不完的
    thread::spawn(|| {
        for i in 1..10 {
            println!("number {} from spawned thread.", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    for i in 1..5 {
        println!("number {} from main thread.", i);
        thread::sleep(Duration::from_millis(1));
    }
}

fn test_2() {
    let handle = thread::spawn(|| {
        for i in 1..10 {
            println!("number {} from spawned thread.", i);
            thread::sleep(Duration::from_millis(1));
        }
    });

    // 調用handle的join方法會阻塞當前運行線程的執行,直到handle所表示的線程終結
    // 因此這里就會spawned線程1到10,再執行主線程的1到5
    handle.join().unwrap();

    for i in 1..5 {
        println!("number {} from main thread.", i);
        thread::sleep(Duration::from_millis(1));
    }
}

fn test3() {
    let v = vec![1, 2, 3];
    // 這里我在線程中使用到了閉包外部的數據 v,提示有誤
    // 因為v的壽命有可能比這個閉包還要短,這會導致問題
    // 所以現在可以使用move關鍵字來使得閉包獲得 v 的所有權
    let handle = thread::spawn(move || {
        for e in v.iter() {
            println!("{}", e);
        }
    });

    handle.join().expect("something wrong.");
}

消息傳遞:

use std::sync::mpsc;
fn test_4() {
    // 消息傳遞
    // 使用mpsc::channel創建一個通道,multiple producer and single consumer
    let (sender, receiver) = mpsc::channel();

    thread::spawn(move || {
        // 這個線程必須擁有通道發送端的所有權才能往通道里發送消息
        let value = String::from("hello!");
        sender.send(value).unwrap();
    });
    // 這里的recv方法會阻塞當前的線程,直到有消息傳入到通道
    // try_recv方法不會阻塞
    let received= receiver.recv().unwrap();
    println!("Got: {}", received);
}

fn test_5() {
    let (sender, receiver) = mpsc::channel();

    thread::spawn(move || {
        let values = vec![String::from("Cpp"),
         String::from("java"),
         String::from("rust"),
         String::from("C#")];

        for value in values.into_iter() {
            // 這里不能使用iter(),因為我們必須獲得它的所有權才能send
            sender.send(value).unwrap();
            thread::sleep(Duration::from_millis(500));
        }
    });

    // 更推薦使用這種方法來接收消息,底層用到了recv方法
    // 會阻塞!
    for received in receiver {
        println!("Got: {}", received);
    }
}

// 使用多個發送者
fn test_6() {
    let (sender, receiver) = mpsc::channel();
    // 克隆!
    let another_sender = mpsc::Sender::clone(&sender);

    thread::spawn(move || {
        let values = vec![String::from("Cpp"),
         String::from("java"),
         String::from("rust"),
         String::from("C#")];

        for value in values.into_iter() {
            sender.send(value).unwrap();
            thread::sleep(Duration::from_millis(500));
        }
    });

    thread::spawn(move || {
        let values = vec![String::from("###Cpp"),
         String::from("###java"),
         String::from("###rust"),
         String::from("###C#")];

        for value in values.into_iter() {
            another_sender.send(value).unwrap();
            thread::sleep(Duration::from_millis(500));
        }
    });

    // 更推薦使用這種方法來接收消息,底層用到了recv方法
    // 會阻塞!
    for received in receiver {
        println!("Got: {}", received);
    }
}

// 以上都是使用通信的方法實現并發

use std::sync::Mutex;

fn test_7() {
    // Mutex 是一個智能指針,互斥鎖
    let m = Mutex::new(5);
    {   
        // 訪問數據前,先使用lock來獲得鎖。這個方法是阻塞的
        // lock可能失敗,返回一個MetexGuard智能指針
        let mut num = m.lock().unwrap();
        *num += 12;
    }
    println!("{}", m.lock().unwrap());
}

use std::sync::Arc;
// 使用Arc來進行原子引用計數(和Rc一模一樣,只是用在多線程環境下)
fn test_8() {
    // let counter = Mutex::new(0);
    let counter = Arc::new(Mutex::new(0));
    let mut handles = Vec::new();

    for _ in 0..10 {
        // 多個線程同時訪問這個互斥鎖里邊的數據
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles.into_iter() {
        handle.join().unwrap();
    }

    println!("{}", counter.lock().unwrap());
}
文章來自個人專欄
文章 | 訂閱
0條評論
作者已關閉評論
作者已關閉評論
0
0