2020/03/02

Rust03 Common Programming Concepts

這一章介紹所有程式語言都有的概念,在 Rust 中是如何使用的,包含了 變數、資料型別、Function、Comments、Control Flow。

Variable and Mutability

rust 的變數預設是 immutable,這也是 rust 安全性的基礎。

fn main() {
    let x = 5;
    println!("The value of x is: {}", x);
    // 編譯錯誤: cannot assign twice to immutable variable
    x = 6;
    println!("The value of x is: {}", x);

    // 宣告時加上 mut,就可以重新綁定 x
    let mut x = 5;
    println!("The value of x is: {}", x);
    x = 6;
    println!("The value of x is: {}", x);
}

Constants 常數類似 immutable variable,不能對常數使用 mut,宣告時要用 const 而不是 let

const MAX_POINTS: u32 = 100_000;

Shadowing

可以重新定義相同名稱的變數,前一個變數就會被 shadowing

fn main() {
    let x = 5;

    let x = x + 1;

    let x = x * 2;

    println!("The value of x is: {}", x);
}

使用 shadowing 跟 mut 是有差別的,let 會產生一個新的變數,可以改變該變數的資料型別,但使用相同的變數名稱。但 mut 不能改變該變數的資料型別。

Data Types

rust 中每一個值都有一個 data type,rust 才知道要如何使用該 data。

Rust 是 static type language,編譯時就要確定知道每一個變數的資料型別。編譯器可以推測變數的資料型別,但如果有多種可能時,必須自己指定資料型別。例如 parse,轉換後的結果要是數字的話,就要加上 :u32

let guess: u32 = "42".parse().expect("Not a number!");

scalar

rust 有四種 scalar: integers, floating-point numbers, Booleans, and characters

  • integers: 整數,有號跟無號兩種

    Length Signed Unsigned
    8-bit i8 u8
    16-bit i16 u16
    32-bit i32 u32
    64-bit i64 u64
    128-bit i128 u128
    arch isize usize

    有號數可儲存 \(-(2^{n - 1} )\) 到 \(2^{n - 1} - 1\) 的整數,無號數可儲存 0 到 \(2^n -1\) 的整數

    整數可以在數字後面加上型別的後綴 ex: 57u8 ,也可以加上 _ 分隔符號方便閱讀, ex: 1_000

    Number literals Example
    Decimal 98_222
    Hex 0xff
    Octal 0o77
    Binary 0b1111_0000
    Byte (u8 only) b'A'

integer overflow

​ 假設有個 u8 變數,如果將值修改為 256,就會發生 integer overflow。當 rust 在 debug mode 編譯時,會檢查這個問題,並讓程式 panic。 但是在 release mode,rust 不檢查 integer overflow,反而會進行 two's complement wrapping, 256 就會變成 0,而 257 變成 1。標準函式庫中有一個類別提供此功能: Wrapping

  • floating-point numbers

    rust 有兩種 floating-point numbers 類別: f32, f64。預設是 f64

    fn main() {
        let x = 2.0; // f64
    
        let y: f32 = 3.0; // f32
    }

    浮點數採用 IEEE-754 標準表示。f32 是單精度浮點數,f64 是雙精度浮點數。

  • Numeric Operations

​ 所有數字都支援基本的數學運算: + - * / 以及 % (mod)

     fn main() {
    // 加法
    let sum = 5 + 10;

    // 減法
    let difference = 95.5 - 4.3;

    // 乘法
    let product = 4 * 30;

    // 除法
    let quotient = 56.7 / 32.2;

    // 取餘
    let remainder = 43 % 5;
}

  • Booleans

    true 與 false

    fn main() {
        let t = true;
    
        let f: bool = false;
    }
  • characters

char 代表一個 unicode scalar value,從 U+0000U+D7FFU+E000U+10FFFF 在內的值

fn main() {
    let c = 'z';
    let z = 'ℤ';
    let heart_eyed_cat = '😻';
}

Compound Types

可將多種型別的資料合併成一個類別, rust 有兩個原生的 compound types: typle, array

tuple

用圓括號加逗點區隔。可用 pattern matching 取得每一個 tuple element 的數值。也可以用 . 加上 index,

ex: x.0

fn main() {
    let tup: (i32, f64, u8) = (500, 6.4, 1);

    let (x, y, z) = tup;
    println!("The value of y is: {}", y);

    let five_hundred = x.0;
    let six_point_four = x.1;
    let one = x.2;
}

array

array 中每一個元素的資料型別必須要相同,另外 array 的長度是固定的,宣告後就不能任意加長或縮短。

fn main() {
    let a = [1, 2, 3, 4, 5];
    let months = ["January", "February", "March", "April", "May", "June", "July",
              "August", "September", "October", "November", "December"];
}

如果想要在 stack 而不是 heap 儲存資料,或是想要有固定數量的元素時,可使用 array。要不然,就可以使用 vector,vector 類似 array 但允許增長或縮短。

array 的資料型別看起來像是 [type; number]

let a: [i32; 5] = [1, 2, 3, 4, 5];

array 存放在 stack,可用 index 取得元素

fn main() {
    let a = [1, 2, 3, 4, 5];

    let first = a[0];
    let second = a[1];
}

存取超過 array 長度的 index 會造成程式 panic

fn main() {
    let a = [1, 2, 3, 4, 5];
    let index = 10;

    let element = a[index];

    println!("The value of element is: {}", element);
}

編譯沒有錯誤,但執行會出現 runtime error。 這個特性提供了 rust 安全機制,禁止存取異常的 index 的資料。

$ cargo run
   Compiling guessing_game v0.1.0 (/Users/charley/project/guessing_game)
    Finished dev [unoptimized + debuginfo] target(s) in 0.70s
     Running `target/debug/guessing_game`
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:5:19
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Functions

fn 用來宣告函數, main 是程式的入口點

函數跟變數的名稱,使用 snake case 風格,所有字母都是小寫,且用 _ 分隔單字

fn main() {
    println!("Hello, world!");

    another_function();
}

fn another_function() {
    println!("Another function.");
}

函數可以加上參數 parameters,但實際上應該稱為 arguments。不過大家已經不區分 parameter (定義中的變數) 以及arguments (傳入函數的 value)。

fn main() {
    another_function(5, 6);
}

fn another_function(x: i32, y: i32) {
    println!("The value of x is: {}", x);
    println!("The value of y is: {}", y);
}

function bodies 包含 statements & expressions

function bodies 是由一連串的 statements組成,最後面 optional 可加上一個 expression 結束。

Statements: 執行一些操作但不返回值的指令

Expressions : 計算並產生一個值

fn main() {
    // 錯誤,因為 let statement 不會產生回傳 value
    let x = (let y = 6);
}

換句話說,不能用 x=y=6 這樣類似 C, Ruby 的寫法

fn main() {
    let x = 5;

    // { } 之間是一個 block of codes,最後是一個 expression
    // expression 後面不能加上 ;
    // expression 會產生回傳值,並指定給 y
    let y = {
        // 這裡的 x 不會影響到外面的 x
        let x = 3;
        x + 1
    };

    println!("The value of x is: {}, y is: {}", x, y);
}

有 return value 的 function

function 可定義 return value 的資料型別,可使用 return 在函數中間直接回傳,會是在函數最後面,使用 expression。return 後面可加上 ; ,但 expression 後面不能加上 ;

fn test(flag: bool) -> i32 {
    if flag {
        return 4;
    }
    5
}

fn main() {
    let x = test(true);

    println!("The value of x is: {}", x);
}

Comments

// 後面就是註解

fn main() {
    let lucky_number1 = 7; // I’m feeling lucky today

    // I’m feeling lucky today
    let lucky_number2 = 7;
}

Control Flow

loop 跟 if

if expression

Rust 只會執行第一個條件為真的代碼塊,並且一旦它找到一個以後,就不會檢查剩下的條件了。所以這個程式只會列印 number is divisible by 3

fn main() {
    let number = 6;

    if number % 4 == 0 {
        println!("number is divisible by 4");
    } else if number % 3 == 0 {
        println!("number is divisible by 3");
    } else if number % 2 == 0 {
        println!("number is divisible by 2");
    } else {
        println!("number is not divisible by 4, 3, or 2");
    }
}

在 let statement 中使用 if

因 if 是 expression,可以放在 let 的右邊

fn main() {
    let condition = true;
    let number = if condition {
        5
    } else {
        6
    };

    println!("The value of number is: {}", number);
}

但如果 if 跟 else 回傳的資料型別不同,就會發生編譯錯誤

fn main() {
    let condition = true;
    
    // 編譯錯誤
    let number = if condition {
        5
    } else {
        "six"
    };

    println!("The value of number is: {}", number);
}

Loops

rust 有三種循環: loop, while, for

fn main() {
    let mut counter = 0;

    let result = loop {
        counter += 1;

        // 滿足此條件時, counter 為 10
        if counter == 10 {
            // 以 break 停止循環,並回傳 10 * 2
            break counter * 2;
        }
    };

    assert_eq!(result, 20);
}

while 迴圈,在裡面也可以直接用 break 停止迴圈

fn main() {
    let mut number = 3;

    while number != 0 {
        println!("{}!", number);

        number = number - 1;
    }

    println!("LIFTOFF!!!");
}

可用 while 或是 for,處理 array 中每一個元素

fn main() {
    let a = [10, 20, 30, 40, 50];
    let mut index = 0;

    // 使用 while 要注意 index,不能超過 array 的長度
    while index < 5 {
        println!("the value is: {}", a[index]);

        index = index + 1;
    }

    // 使用 for 就不需要注意 index
    for element in a.iter() {
        println!("the value is: {}", element);
    }

    // (1..4) 是 Range,可用 rev() 反轉順序
    for number in (1..4).rev() {
        println!("{}!", number);
    }
    println!("LIFTOFF!!!");
}

References

The Rust Programming Language

中文版

中文版 2

沒有留言:

張貼留言