Rust的一般概念

語言: CN / TW / HK

變數和可變性

變數預設是不可變(immutalbe)的。剛開始學習Rust的人可能不太習慣,但是變數預設不可變能夠提升程式的安全性且更容易做到併發。為什麼Rust鼓勵你使用不可變的變數呢?

當一個變數是不可變的時候,一旦某個值繫結到這個變量了,你就不能再改變這個值了。我們新建一個工程來測試一下:

cargo new varibales --bin

main.rs內容為: rust fn main() { let x = 5; println!("x = {}", x); x = 6; // 報錯!error[E0384] println!("x = {}", x); } 執行cargo run試編譯一下,在 x = 6; 那一行會報錯!

error[E0384]: cannot assign twice to immutable variable x

意思是你不能對一個不可變的變數x進行二次賦值!我們在let x = 5;的時候並沒有指定x是immutable的,為什麼Rust會這麼提示呢?因為Rust預設會給變數新增不可變的屬性,只要你沒有給變數新增可變(mut)修飾,它就是不可變的。

如果要讓變數變成可變的,需要在let後面新增mut關鍵字。 rust fn main() { let mut x = 5; println!("x = {}", x); x = 6; println!("x = {}", x); }

變數和常量

既然有變數就會有常量,常量和immutable的變數很像,但有一些不同: 1. 你不能用mut來修飾常量,常量不僅僅叫做預設不可變,它是永遠不能變! 2. 常量用const關鍵字來定義,而不是let,常量定義時必須指明型別。 ```rust fn main() { let mut x = 5; println!("x = {}", x); x = 6; println!("x = {}", x);

let y = 7;
println!("y = {}", y);
const HUNDRED: u32 = 100;

println!("HUNDRED = {}", HUNDRED); } 3. 常量可以在任何地方定義,包括全域性的!**let只能在函式中使用**,如果我們將`let y = 7`移動到main函式外面的,編譯會報錯!rust let y = 7; // 報錯!error: expected item, found keyword let fn main() { ... 4. 常量不能通過函式呼叫賦值,只能使用常量表達式賦值;rust const HUNDRED: u32 = 100 + 6; // 正確 fn main() { ... ```

變數遮蔽(Shadowing)

當你定義了和前面變數一樣的名字的時候,新的變數會將之前舊的變數名蓋住。 ```rust fn main() { let x = 1; let x = x + 1; let x = x * 3;

println!("x = {}", x);

} ```

執行結果:x = 6

變數遮蔽和可變變數的比較: 1. 變數遮蔽每次都需要關鍵字let,是新建一個變數!可變變數只使用過一次let,並需要用mut來修飾變數,第二次賦值不是新建變數; 2. 變數遮蔽可以改變值的型別 rust let spaces = " "; let spaces = spaces.len(); 第一個spaces的型別是字串型別,第二個spaces是整數型別。也可以這樣理解,第二個整數型別的變數spaces剛好用了和第一個字串型別的變數相同的名字!

而如果我們用mut來修飾變數,然後賦予不同型別的值的話,就會報型別不匹配的錯誤! rust let mut spaces = " "; spaces = spaces.len(); // error[E0308]: mismatched types // expected `&str`, found `usize`

資料型別

在Rust中每一個值都有一個確定的資料型別!資料型別包含有2個大的子集:數值型和複合型別。

Rust是一門靜態型別語言,靜態型別語言意味著Rust必須在編譯期間就確定所有的變數型別!我們在很多地方都會碰到將字串轉換成數值的需求,因為數值型別有很多種類,如果編譯器在編譯期間無法確定最終數值的型別的話,就會報錯,它要求開發者指定好!比如: rust let guess = "42".trim().parse().expect("請輸入一個整型數!");

報錯:error[E0282]: type annotations needed; consider giving guess a type 就是要給guess指定一個型別,比如 guess: u32

數值型別(Scalar Types)

Rust有4種基本的數值型別:整型,浮點型,布林型和字元型。每個數值型別代表一個值。我們稍微看看。

整型

整型沒有小數部分,它可以分為有符號數和無符號數。有符號數的整型用i開頭,無符號數的整型用u開頭。 Rust內建支援的整型型別

長度 | 有符號 | 無符號 --- | --- | --- 8位 | i8 | u8 16位 | i16 | u16 32位 | i32 | u32 64位 | i64 | u64 架構相關 | isize | usize

isize和usize是與執行程式的CPU架構相關的:在32位機器上是64位長度,在64位機器上是64位長度。如果你不確定用哪一個整型型別,那麼就用Rust預設的,為i32,++使用這個型別進行運算時是最快的,即使在64位計算機上也是++。

在Rust中有多種整型字面常量的書寫方式。其中下劃線_僅用來便於開發者閱讀,不對數字的大小構成影響!

數字字面常量 | 舉例 --- | --- 十進位制 | 98_222 十六進位制 | 0xff 八進位制 | 0o77 二進位制 | 0b1111_0000 位元組 | b'A'

注意: b‘A'會列印成65,’A'會列印成A。 0x表示的是十六進位制,0o表示的是八進位制,0b表示的是二進位制,在數字0後面分別是小寫字母x、o和b,不能是大寫的

測試一下: rust fn main() { let x = 20_0000; println!("x is {}", x); let x = 0x20_0000; println!("x is {}", x); let x = 0o77; println!("x is {}", x); let x = 0b1111_0000; println!("x is {}", x); let x = b'A'; println!("x is {}", x); let x = 'A'; println!("x is {}", x); }

執行結果: shell x is 200000 x is 2097152 x is 63 x is 240 x is 65 x is A

浮點型別

Rust支援2種基本的浮點型別:單精度f32和雙精度f64。預設的浮點型別是f64,因為現代的計算機效能都很強勁,計算64位的速度與32位差不多,前者的的精度更高,適用範圍更廣。 rust let x = 2.0; // f64 let y: f32 = 3.0; // f32

布林型別

rust fn main() { let t = true; let f: bool = false; // with explicit type annotation }

字元型別

字元用單引號包圍起來,字串用雙引號rust fn main() { let c = 'z'; let z = 'Ƶ'; let heart_eyed_cat = '😻'; let zhong = '中'; println!("heart_eyed_cat is {}", heart_eyed_cat); println!("zhong is {}", zhong); } 輸出: shell heart_eyed_cat is 😻 zhong is 中 Rust中的字元型別不是單純的ASCII字元編碼,而是Unicode編碼!所以我們可以看到上面能夠打印出中文和Emoji表情。Unicode編碼值的範圍是U+0000 ~ U+D7FF,以及U+E000 ~ U+10FFFF。

因為Rust的字元型別與我們通常瞭解的很不一樣,要特別注意!

複合型別

複合型別可以將多個值組織到一個型別中,Rust支援2種基本的複合型別:元組(tuples)和陣列(arrays)

元組型別(元/圓括號())

元組可以將多個不同型別的值放在一個集合裡。 建立元組時需要用圓括號括起來,裡面的每個值用逗號隔開。你可以為每個元素指定好型別,也可以由Rust幫你推斷。 rust let tup: (i32, f64, u8) = (2, 3.0, 250); // 正確 let tup = (2, 3.0, 250); // 正確,自動推斷型別 let tup: (i32, f64) = (2, 3.0, 250); // 錯誤 error[E0308]: mismatched types: // expected a tuple with 2 elements, found one with 3 elements 前兩句表示式都建立了tup元組,元組中有三個值,分別為2, 3.0, 250,前者指定了值型別,後者由Rust自動推斷三個值的型別。

如何讀取元組中的資料呢? ```rust fn main() { let tup: (i32, f64, u8) = (2, 3.0, 250); let mut tup = (2, 4.0, 250);

let (x, mut y, z) = tup; // 解元組:將一個元組分解成幾個部分
println!("y is {}", y);
println!("tup.0 is {}", tup.0); // 點運算
tup.2 = 200;
y = 5.0;
println!("y is {}", y);
println!("z is {}", z);

} 執行結果:shell y is 4 tup.0 is 2 y is 5 z is 250 ``` 讀取元組資料的方法: 1. 將元組賦值給另外一個由變數組成的元組,然後用元組中的變數獲得對應的值! 2. 使用.(點)運算子和序號的方式獲取,序號從0開始。

陣列型別(方括號[])

另一個將多個值放在一個集合裡的方法是陣列,和元組不同,陣列中的元素只能一種!Rust的陣列的長度是固定的,一旦定義就不能再修改了!建立陣列時需要用方括號括起來,裡面的每個值用逗號隔開rust let a = [1, 2, 3, 4, 5];

如何訪問陣列元素?方括號中加下標,下標從0開始。 rust let a = [1, 2, 3, 4, 5]; println!("a[0] is {}", a[0]); 如果我們訪問不存在的序號呢?我們知道Rust的陣列中的長度是固定的,所以猜想Rust會在編譯時報錯吧?是的! rust println!("a[0] is {}", a[5]); // 報錯 index out of bounds: // the length is 5 but the index is 5 Rust在訪問元素的時候會看序號是否超出範圍了,超出的話就會果斷panic退出了。這樣的處理讓程式變得更加安全,對比一些語言不做比較的話就會訪問到不可用的記憶體,那麼程式當時可能沒有錯誤退出,在過一段時間之後才表現出來,問題追查的時候就比較難以定位到原因。

++標準庫中提供的vector和array具有相似的功能++,都是同一種類型,但是vector不固定長度,靈活性要比array大得多,如果你不知道在這兩者間如何選擇,就選擇vector使用。 下面這種情況用array很合適,如月份的定義、星期的定義,都是固定長度和同一種型別的。 rust let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];

函式

函式在Rust中非常常見,像main函式就是其中一個非常重要的函式,它是整個應用程式的入口。Rust中的函式程式碼採用蛇形命名法編寫,蛇形命名法是由全小寫的字母和_下劃線連線單詞的方式。 rust fn main() { println!("Hello, world!"); another_function(); } fn another_function() { println!("Another function."); } 函式以fn開頭,後面跟著的是函式名。看上面的main函式可以呼叫到定義在其後面的another_function函式,說明在Rust中,被呼叫函式another_function的定義與呼叫者main函式書寫先後順序無關。

引數

函式的簽名中,引數的型別是必須指定的! rust 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); }

函式中的語句Statement和表示式Expression

Rust中的函式應該由若干條語句和末尾的一條表示式組成(也可以沒有表示式!)。和其它語言不一樣,在Rust中對這兩個概念做了區分。

語句是用來執行的指令,沒有結果值。表示式更像是一個實物,則有一個實際的結果用於賦值。 rust let y = 6; // 語句 let x = (let y = 6); // 報錯,因為let y = 6是語句,是沒有返回值的 語句沒有返回值,所以你不能用語句作為一個值賦值給另外一個變數 rust let x = y = 6; // 在Rust中也是不成立的

再看看下面的語句和表示式: ```rust fn add(x: i32, y: i32) -> i32 { return x + y; // 是一條語句 }

fn minus(x: i32, y: i32) -> i32 { x + y + 100 // 沒有分號!為表示式,x+y+100的結果用於minus的返回值 }

fn main() { println!("result add: {}", add(1, 2)); // 語句 println!("result minus: {}", minus(1, 2)) // 表示式,在函式的末尾了,這樣寫也可以正常編譯 } ```

控制流

rust fn main() { let condition = true; let number = if condition { 5 } else { "six" }; println!("The value of number is: {}", number); } 會報錯!因為if 是一個語句,所以它有返回值。但是兩個分支的返回值不一樣就不行!Rust是靜態編譯語言,無法處理這樣型別不確定的情況!

迴圈

loop

巢狀的loop迴圈 ```rust fn main() { 'outer: loop { println!("Entered the outer loop");

    'inner: loop {
        println!("Entered the inner loop");

        // This would break only the inner loop
        //break;

        // This breaks the outer loop
        break 'outer;
    }

    println!("This point will never be reached");
}

println!("Exited the outer loop");

} loop的返回值:rust let mut counter = 0;

let result = loop {
    counter += 1;

    if counter == 10 {
        break counter * 2;
    }
};

```

while

rust while n > 0 { println!("n is {}", n); n -= 1; }

for

資料的iter方法 rust fn main() { let a = [10, 20, 30, 40, 50]; for element in a.iter() { println!("the value is: {}", element); } }

```rust fn main() { for number in (1..4).rev() { println!("{}!", number); } println!("LIFTOFF!!!"); }

```

rust for i in 1..=10 { println!("i(include) is {}", i); }