[Rust] 重點摘要

程式語言:Rust
官網
Rust Playground(線上撰寫)

簡介:Rust 的特色介紹

學習資源
變數可被遮蔽
線上測試範例
fn main() {
    let x = 5;
    println!("x 的數值為:{}", x);
    let x = "five";
    println!("x 的數值為:{}", x);
}
變數宣告
使用前必須有值
線上測試範例
fn main() {
    // // type annotations needed
    let x;
    println!("x 的數值為:{}", x);
    
    // borrow of possibly-uninitialized variable: `x`
    let x;
    println!("x 的數值為:{}", x);
    x=2;
    
    // OK
    let x;
    x=2;
    println!("x 的數值為:{}", x);
}
宣告時,需指定變數是否可變
線上測試範例
fn main() {
    // 不可變
    let x = 5;
    println!("x 的數值為:{}", x);
    // cannot assign twice to immutable variable `x`
    x = 6;
    println!("x 的數值為:{}", x);
    
    // 可變
    let mut x = 5;
    println!("x 的數值為:{}", x);
    x = 6;
    println!("x 的數值為:{}", x);
}
  • Rust 中每個數值都會有一個變數作為它的擁有者(owner)。
  • 同時間只能有一個擁有者。
  • 當擁有者離開作用域時,數值就會被丟棄。
若資料在堆積(Heap)上,Rust 永遠不會自動將你的資料建立「深拷貝」。
因此任何自動的拷貝動作都可以被視為是對執行效能影響很小的,但可手動 clone
若只在堆疊(Stack)上的資料,會自動拷貝,因擁有 Copy Trait,如下型別
  • 所有整數型別像是 u32。
  • 布林型別 bool,它只有數值 true 與 false。
  • 所有浮點數型別像是 f64。
  • 字元型別 char。
  • 元組,不過包含的型別也都要有 Copy 才行。比如 (i32, i32) 就有 Copy,但 (i32, String) 則無。
線上測試範例
fn main() {
    {
        // s 在此處無效,因為它還沒宣告
        let s = "hello"; // s 在此開始視為有效

        println!("{}", s); // 使用 s
    } // 此作用域結束, s 不再有效
    // cannot find value `s` in this scope
    println!("{}", s); // 會出錯
    
    //=========================================================
    
    let s1 = String::from("hello");
    let s2 = s1;
    // borrow of moved value: `s1`
    println!("{}", s1); // 會出錯
    
    //=========================================================
    
    let x = 5;
    let y = x;
    println!("{}", x); // 不會出錯,因為 Copy Trait
    
    //=========================================================
    
    let s1 = String::from("hello");
    let s2 = s1.clone();
    println!("s1 = {}, s2 = {}", s1, s2); // 不會出錯,因重新 clone 一份給 s2
}
基本用法,可用引用閃過所有權
線上測試範例
fn main() {
    let s1 = String::from("hello");
    let s2 = &s1;
    println!("{} {}", s1, s2); // 不會出錯,因 s2 為引用
    //cannot borrow `*s2` as mutable, as it is behind a `&` reference
    // s2.push_str(", world"); // 因 s2 為不可變引用,故會出錯
    // println!("{}", s2);
    
    let mut s3 = String::from("hello");
    let s4 = &mut s3;
    s4.push_str(", world");
    println!("{}", s4);
    
    // cannot borrow `s1` as mutable, as it is not declared as mutable
    let s5 = &mut s1;
    s5.push_str(", world");
    println!("{}", s5);
}
限制條件
  • 在任何時候,我們要嘛只能有一個可變引用,要嘛可以有任意數量的不可變引用。
  • 引用必須永遠有效。
這項限制的好處是 Rust 可以在編譯時期就防止資料競爭(data races)。資料競爭和競爭條件(race condition)類似,它會由以下三種行為引發:
  • 同時有兩個以上的指標存取同個資料。
  • 至少有一個指標在寫入資料。
  • 沒有針對資料的同步存取機制。
線上測試範例
fn main() {
    let mut s = String::from("hello");

    let r1 = &mut s;
    // cannot borrow `s` as mutable more than once at a time
    let r2 = &mut s;
    println!("{}, {}", r1, r2);
    
    //===================================================
    
    let mut s = String::from("hello");
    {
        let r1 = &mut s;
    } // r1 離開作用域,所以建立新的引用也不會有問題
    let r2 = &mut s;
    
    //===================================================
    
    let mut s = String::from("hello");
    let r1 = &s; // 沒問題
    let r2 = &s; // 沒問題
    // cannot borrow `s` as mutable because it is also borrowed as immutable
    let r3 = &mut s; // 很有問題!
    println!("{}, {}, and {}", r1, r2, r3);
    
    //===================================================
    
    let mut s = String::from("hello");
    let r1 = &s; // 沒問題
    let r2 = &s; // 沒問題
    println!("{} and {}", r1, r2);
    // r1 和 r2 從此不再使用
    let r3 = &mut s; // 沒問題
    println!("{}", r3);
    
    //===================================================
    
    {
        let mut s = String::from("hello");
    } // s 離開作用域,已被 drop
    // cannot find value `s` in this scope
    let r1 = &mut s; // 很有問題!
}
線上測試範例
#![allow(unused)]
fn main() {
    use std::fs::File;
    use std::io;
    use std::io::Read;

    fn read_username_from_file() -> Result<String, io::Error> {
        let f = File::open("hello.txt");

        let mut f = match f {
            Ok(file) => file,
            Err(e) => return Err(e),
        };

        let mut s = String::new();

        match f.read_to_string(&mut s) {
            Ok(_) => Ok(s),
            Err(e) => Err(e),
        }
    }
    
    // 利用 ? 可達到同上的效果 ===========================

    fn read_username_from_file() -> Result<String, io::Error> {
        let mut f = File::open("hello.txt")?;
        let mut s = String::new();
        f.read_to_string(&mut s)?;
        Ok(s)
    }
}
可利用特徵界限,來限定型別
// 以下兩者等同
fn trait_fn<T: Copy + Debug>(a: T) -> T {}
fn trait_where<T>(a: T) -> T
where
    T: Copy + Debug,
{
}

// 以下兩者等同
fn trait_iml(a: impl Copy) -> impl Copy {}
fn trait_iml<T: Copy>(a: T) -> impl Copy {}

// impl Trait 語法糖,只在輸入參數,回傳型別並不相同
// 故以下兩者不等同
fn trait_iml(a: impl Copy) -> impl Copy {}
fn trait_iml<T: Copy>(a: T) -> T {}
生命週期詮釋不會改變引用能存活多久。
就像當函式簽名指定了一個泛型型別參數時,函式便能夠接受任意型別一樣。
函式可以指定一個泛型生命週期參數,這樣函式就能接受任何生命週期。
生命週期詮釋描述了數個引用的生命週期之間互相的關係,而不會影響其生命週期。
當引用沒有顯式詮釋生命週期時,編譯器會用三項規則來推導它們。
  • 第一個規則是每個引用都會有自己的生命週期參數。換句話說,一個函式只有一個參數的話,就只會有一個生命週期:
    fn foo<'a>(x: &'a i32);
    一個函式有兩個參數的話,就會有分別兩個生命週期參數:
    fn foo<'a, 'b>(x: &'a i32, y: &'b i32)
    ,以此類推。
  • 第二個規則是如果剛好只有一個輸入生命週期參數,該參數就會賦值給所有輸出生命週期參數:
    fn foo<'a>(x: &'a i32) -> &'a i32。
  • 第三個規則是如果有多個輸入生命週期參數,但其中一個是 &self 或 &mut self,由於這是方法,self 的生命週期會賦值給所有輸出生命週期參數。此規則讓方法更容易讀寫,因為不用寫更多符號出來。
線上測試範例
fn main() {
    let string1 = String::from("abcd");
    let string2 = "xyz";

    let result = longest(string1.as_str(), string2);
    println!("最長的字串為 {}", result);
}

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() {
        x
    } else {
        y
    }
}
let 即是一種 模式配對
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p = Point { x: 0, y: 7 };

    let Point { x: a, y: b } = p;
    assert_eq!(0, a);
    assert_eq!(7, b);
}
線上測試範例
enum Color {
    Rgb(i32, i32, i32),
    Hsv(i32, i32, i32),
}

enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(Color),
}

fn main() {
    let msg = Message::ChangeColor(Color::Hsv(0, 160, 255));

    match msg {
        // 透過 .. 忽略剩餘部分數值
        Message::ChangeColor(Color::Rgb(..,b)) => println!(
            "藍色 {}",
            b
        ),
        // 透過 _ 忽略整個數值
        Message::ChangeColor(Color::Hsv(_, s, _)) => println!(
            "飽和度 {}",
            s
        ),
        // Guards,compiler 無法檢查 if 所有可能性,需搭配 _ =>
        Message::Move{y,..} if y > 0 => println!(
            "移動 y:{}",
            y
        ),
        // @ 綁定
        Message::Move{x: x_variable @ 3..=7, y:_ } => println!(
            "移動 x:{} 在 3~7",
            x_variable
        ),
        // 透過 ..= 配對數值範圍 只允許使用數字或 char 數值
        Message::Move{x: 3..=7, y:_ } => println!(
            "移動 x 在 3~7"
        ),
        // 多重模式
        Message::Quit | Message::Write(_)=> println!(
            "Quit or Write"
        ),
        _ => (),
    }
}
詳細請參考連結
#[no_mangle]
pub extern "C" fn addition(a: u32, b: u32) -> u32 {
    a + b
}

留言