Rust的一般概念
變數和可變性
變數預設是不可變(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);
}