一網(wǎng)打盡 Rust 語法
1. 構(gòu)建運(yùn)行環(huán)境
我們?cè)赗ust環(huán)境配置和入門指南中詳細(xì)介紹了
- 如何安裝Rust環(huán)境
- 構(gòu)建一個(gè)Rust應(yīng)用
- 編譯和運(yùn)行的區(qū)別
- 使用Cargo構(gòu)建Rust應(yīng)用
下面,我們就之間直入主題了。
通過創(chuàng)建一個(gè)名為 main.rs 的文件并將以下代碼放入其中來編寫我們的第一個(gè) Rust 代碼:
fn main() {
println!("Hello, Front789!");
}
然后通過運(yùn)行 rustc main.rs 和 ./main.exe 來運(yùn)行這個(gè)程序,就像運(yùn)行 C 程序一樣。
Cargo 是 Rust 的構(gòu)建系統(tǒng)和包管理器
我們也可以使用 cargo 創(chuàng)建項(xiàng)目。
- cargo new hello_cargo:初始化一個(gè)新項(xiàng)目。
- cargo build:構(gòu)建一個(gè) cargo 項(xiàng)目。
- cargo run:運(yùn)行一個(gè) cargo 項(xiàng)目,這將編譯并運(yùn)行代碼。
- cargo check:檢查是否有編譯錯(cuò)誤,它比cargo build速度更快。
- cargo build --release:這將使用優(yōu)化進(jìn)行編譯,用于最終生產(chǎn)構(gòu)建。
2. 變量類型
在 Rust 中,默認(rèn)情況下「變量是不可變」的,這意味著一旦給變量賦值,其值就不會(huì)改變。
所以如果想要一個(gè)可變的,即可改變的值,使用 mut。
let a = 5;
let mut b = 5; // 可變的
- 整數(shù):有各種大小的有符號(hào)和無符號(hào)整數(shù)(例如,i8、i16、i32、i64、u8、u16、u32、u64)
圖片
let number: i32 = 42;
- 浮點(diǎn)數(shù):單精度和雙精度浮點(diǎn)數(shù)(例如,f32、f64)
圖片
let pi: f64 = 3.14159;
- 布爾值:Rust的布爾類型只擁有兩個(gè)可能的值true和false,它「只會(huì)占據(jù)單個(gè)字節(jié)的空間大小」。使用bool來表示一個(gè)布爾類型。
let is_rust_cool: bool = true;
- 字符:在Rust中char類型「占4字節(jié)」,是一個(gè)Unicode標(biāo)量值,這意味著它可以表示比ASCII多的字符內(nèi)容。使用char 類型表示一個(gè)字符類型
let heart_emoji: char = '?';
- 字符串:可變字符串
圖片
let mut s = String::from("front789");
- 字符串切片:不可變且借用的字符串切片
let s1: &str = "front789";
- 數(shù)組:數(shù)組中每一個(gè)元素都必須是「相同類型」。 Rust中「數(shù)組擁有固定的長度,一旦聲明就再也不能隨意更改大小」
let array: [i32; 3] = [1, 2, 3];
let a = [3; 5]; // 用值 3 初始化大小為 5 的數(shù)組
- 元組
圖片
為了從元組中獲得單個(gè)的值,可以使用「模式匹配」來解構(gòu)元組
還可以通過「索引」并使用點(diǎn)號(hào)(.)來訪問元組中的值
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
let aa = tup.0; // 引用元組中的第一個(gè)項(xiàng)目
- 向量
圖片
- 指針和引用
指針是一個(gè)變量,它存儲(chǔ)了一個(gè)值的「內(nèi)存地址」
Rust 中最常見的指針是引用。引用以 & 符號(hào)為標(biāo)志并「借用了它們所指向的值」。除了引用數(shù)據(jù)沒有任何其他特殊功能。它們也沒有任何額外開銷,所以應(yīng)用得最多。
fn main() {
// 標(biāo)量類型
let number: i32 = 42;
let pi: f64 = 3.14159;
let is_rust_cool: bool = true;
let heart_emoji: char = '?';
// 復(fù)合類型
let array: [i32; 3] = [1, 2, 3];
let tuple: (i32, f64, char) = (10, 3.14, 'a');
let slice: &[i32] = &[1, 2, 3];
let string: String = String::from("Hello, Front789!");
let string_slice: &str = "Hello, Front789!";
// 特殊類型
let reference_to_number: &i32 = &number;
let optional_value: Option<i32> = Some(42);
let result_value: Result<i32, &str> = Ok(42);
}
以上內(nèi)容就是Rust中所涉及到的各種數(shù)據(jù)類型,我們可以從以下的鏈接中找到更為詳細(xì)的解釋
- 基礎(chǔ)概念
- 集合
- 智能指針
3. 操作數(shù)組
不可變數(shù)組:
不可變數(shù)組在 Rust 中用 [T; N] 語法來聲明,其中 T 表示數(shù)組元素的類型,而 N 表示數(shù)組的長度。
對(duì)于不可變數(shù)組,我們可以使用下標(biāo)訪問其元素,但不能修改元素的值。
let array = [1, 2, 3, 4, 5];
let first_element = array[0]; // 訪問第一個(gè)元素
arr[0] = 6; // 這行代碼會(huì)導(dǎo)致編譯錯(cuò)誤,因?yàn)閿?shù)組是不可變的
// 迭代
// 使用 for 循環(huán)
for &num in &array {
println!("{}", num);
}
// 另一種迭代器
array.iter().for_each(|&num| {
println!("{}", num);
});
let slice = &array[1..3]; // 從索引 1 到索引 2(包括)切片
可變數(shù)組
Vec<T> 是 Rust 中可變長數(shù)組的實(shí)現(xiàn),它允許您動(dòng)態(tài)地增加或減少數(shù)組的大小。
let mut array = [1, 2, 3, 4, 5];
array[0] = 10; // 修改第一個(gè)元素
let mut vec = Vec::new(); // 創(chuàng)建一個(gè)空 Vec
vec.push(1); // 向 Vec 中添加一個(gè)元素
vec.push(2);
vec.push(3);
// 使用 iter() 遍歷元素
for item in array.iter() {
println!("{}", item);
}
// iter_mut() 方法返回一個(gè)可變的迭代器,允許修改 Vec 中的元素
for item in array.iter_mut() {
*item += 1; // 對(duì)每個(gè)元素加 1
}
// map
let doubled_array: Vec<_> =
array.iter()
.map(|&num| num * 2)
.collect();
// filter
let even_elements: Vec<_> =
array.iter()
.filter(|&&num| num % 2 == 0)
.collect();
// len() 方法返回 Vec 中元素的數(shù)量
array.len()
// remove() 方法移除指定索引位置的元素,并返回該元素。如果索引越界,它將導(dǎo)致 panic。
let removed_item = array.remove(2) // removed_item 為3
4. 操作字符串
let s1 = String::from("Hello, ");
let s2 = String::from("Front789!");
let combined = s1 + &s2; // 注意:s1 在這里被移動(dòng),之后不能再使用
println!("{}", combined); // 打印 "Hello, Front789!"
let mut s = String::from("Hello, ");
s.push_str("Front789!");
println!("{}", s); // 打印 "Hello, Front789!"
// 獲取字符
let s = String::from("hello");
let first_char = s.chars().nth(0); // 訪問第一個(gè)字符
// 子字符串
let s = String::from("hello Front789");
let substring = &s[0..5]; // 提取 "hello"
// len()
let s = String::from("hello");
let length = s.len(); // 字符串的長度
// replace
let s = String::from("hello");
let replaced = s.replace("l", "z"); // 替換 "l" 為 "z"
// split
let s = String::from("hello Front789");
let words: Vec<&str> = s.split_whitespace().collect(); // 分割成單詞
// 轉(zhuǎn)換 &str 和 String
let s = String::from("hello");
let s_ref: &str = &s; // 將 String 轉(zhuǎn)換為 &str
let s_copy: String = s_ref.into(); // 將 &str 轉(zhuǎn)換為 String
5. 操作向量
let mut v1 = vec![1, 2, 3]; // 使用 vec![] 宏
let mut v2: Vec<i32> = Vec::new(); // 使用 Vec::new() 構(gòu)造函數(shù)
let mut v = Vec::new();
v.push(1);
v.push(2);
let first_element = v[0]; // 訪問第一個(gè)元素
// 迭代
// 使用 for 循環(huán)
for num in &v {
println!("{}", num);
}
// 使用迭代器
v.iter().for_each(|&num| {
println!("{}", num);
});
// slice
let slice = &v[1..3]; // 從索引 1 到索引 2(包括)提取元素
// remove
let removed_element = v.remove(1); // 移除索引為 1 的元素(返回被移除的元素)
// sort()
v.sort();
// join
let tt= vec!["hello", "Front789"];
let joined_string = tt.join(", "); // 使用逗號(hào)和空格連接元素
6. 函數(shù)
Rust代碼使用「蛇形命名法」來作為規(guī)范函數(shù)和變量名稱的風(fēng)格。蛇形命名法「只使用小寫的字母進(jìn)行命名,并以下畫線分隔單詞」。
- 參數(shù),它們是一種「特殊的變量,并被視作函數(shù)簽名的一部分」。當(dāng)函數(shù)存在參數(shù)時(shí),你需要在「調(diào)用函數(shù)時(shí)為這些變量提供具體的值」
- 在Rust中,「函數(shù)的返回值等同于函數(shù)體的最后一個(gè)表達(dá)式」。
語法
fn 函數(shù)名(參數(shù)1: 類型1, 參數(shù)2: 類型2) -> 返回類型 {
// 函數(shù)體
// 可選的表達(dá)式
}
最后一行返回值時(shí)不需要調(diào)用 return。
fn add_numbers(x: i32, y: i32) -> i32 {
let sum = x + y;
sum // 函數(shù)中的最后一個(gè)表達(dá)式會(huì)隱式返回
}
如果想要一個(gè)無返回值的函數(shù),不要定義返回類型。
我們可以在基礎(chǔ)概念_函數(shù)部分查看更詳細(xì)的解釋
7. 輸入/輸出
輸入
要讀取一個(gè)值,使用 io stdin 并給出變量的值,在失敗時(shí)需要提供 expect 消息,否則會(huì)出錯(cuò)。
let mut guess = String::new();
io::stdin().read_line(&mut guess).expect("該行讀取失敗");
輸出 / 打印
println!("輸出對(duì)應(yīng)的變量信息 {}", guess); // 這里的 guess 是變量名。
你也可以在末尾有變量
let y = 10;
println!("y + 2 = {}", y + 2);
8. Shadowing
在Rust中,一個(gè)「新的聲明變量可以覆蓋掉舊的同名變量」,我們把這一個(gè)現(xiàn)象描述為:「第一個(gè)變量被第二個(gè)變量遮蔽Shadow了」。這意味著隨后使用這個(gè)名稱時(shí),它指向的將會(huì)是第二個(gè)變量。
fn main() {
let x = 5; // 定義值為 5 的變量 x
println!("原始值 x: {}", x); // 打印 "原始值 x: 5"
let x = 10; // Shadowing:定義一個(gè)新的值為 10 的變量 x
println!("Shadowed x: {}", x); // 打印 "Shadowed x: 10"
}
圖片
9. 控制塊
圖片
If else
if condition1 {
// 如果 condition1 為真,則執(zhí)行的代碼
} else if condition2 {
// 如果 condition2 為真,則執(zhí)行的代碼
} else {
// 如果 condition1 和 condition2 都為假,則執(zhí)行的代碼
}
10. 循環(huán)
Rust提供了3種循環(huán)
- loop
- while
- for
loop
loop {
println!("永無止境的執(zhí)行");
}
While 循環(huán)
let mut count = 0;
while count < 5 {
println!("Count: {}", count);
count += 1;
}
For 循環(huán)
for i in 0..5 {
println!("{}", i);
}
foreach
當(dāng)然也少不了對(duì)數(shù)值的遍歷操作。
(1..=5).for_each(|num| {
println!("Number: {}", num);
});
// Number: 1
// Number: 2
// Number: 3
// Number: 4
// Number: 5
..:它表示一個(gè)擴(kuò)展運(yùn)算符,表示從第一個(gè)數(shù)字到最后一個(gè)數(shù)字生成。
我們也可以在循環(huán)中使用 continue 和 break。
11. 所有權(quán)
圖片
這個(gè)概念是需要特別注意和反復(fù)觀看的部分。
MOVE(或)重新分配變量
當(dāng)變量值被重新分配時(shí),值會(huì)給新的所有者,并且舊的所有者被丟棄。
這種行為在字符串中經(jīng)??吹剑皇瞧渌愋?,如下所示:
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
這將導(dǎo)致錯(cuò)誤,因?yàn)?nbsp;s1 在 s2=s1 之后不再有效。
如何解決上面的問題呢,我們可以使用 Clone:
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
某些類型隱式實(shí)現(xiàn)了 Clone。
let x = 5; // x 擁有整數(shù) 5
let y = x; // 將 x 的值復(fù)制到 y,不傳遞所有權(quán)
例如,整數(shù)隱式實(shí)現(xiàn)了 Clone,因此這段代碼不會(huì)報(bào)錯(cuò)。
圖片
所有權(quán)和函數(shù)
fn main() {
let s = String::from("hello"); // s 進(jìn)入作用域
takes_ownership(s); // s 的值移動(dòng)進(jìn)入函數(shù)...
// ... 所以這里不再有效
let x = 5; // x 進(jìn)入作用域
makes_copy(x); // x 會(huì)移入函數(shù),
// 但 i32 是 Copy,所以在之后繼續(xù)使用 x 是可以的
} // 在這里,x 超出作用域,然后是 s。但因?yàn)?s 的值被移動(dòng)了,所以沒有什么特別的發(fā)生。
fn takes_ownership(some_string: String) { // some_string 進(jìn)入作用域
println!("{}", some_string);
} // 在這里,some_string 超出作用域,調(diào)用 drop。內(nèi)存被釋放。
fn makes_copy(some_integer: i32) { // some_integer 進(jìn)入作用域
println!("{}", some_integer);
} // 在這里,some_integer 超出作用域。沒有什么特別的發(fā)生。
如果我們像在變量被移動(dòng)后,繼續(xù)使用,那么我們就使用 takes_ownership(s.clone()); (或者)在 takes_ownership 函數(shù)中返回值,像這樣:
fn main() {
let s2 = String::from("hello"); // s2 進(jìn)入作用域
let s3 = takes_and_gives_back(s2); // s2 移入并被返回
}
fn takes_and_gives_back(a_string: String) -> String {
a_string // 返回并移出到調(diào)用函數(shù)
}
借用 — 所有權(quán)
傳遞變量的引用,所有權(quán)不會(huì)被傳遞。
我們稱「創(chuàng)建引用的操作為借用」。就像現(xiàn)實(shí)生活中,如果一個(gè)人擁有一樣?xùn)|西,你可以從他們那里借來。借了之后,你必須歸還。你不擁有它。
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len);
}
fn calculate_length(s: &String) -> usize {
s.len()
}
針對(duì)此處更詳細(xì)的內(nèi)容,可以翻看我們之前的所有權(quán)
12. 結(jié)構(gòu)體
struct,或者 structure,是一個(gè)「自定義數(shù)據(jù)類型」,允許我們命名和包裝多個(gè)相關(guān)的值,從而形成一個(gè)有意義的組合。
圖片
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let mut user1 = User {
active: true,
username: String::from("front789"),
email: String::from("front789@example.com"),
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
let user2 = User {
email: String::from("another@example.com"),
..user1
};
}
在 user2 中,你會(huì)看到 ..,它是擴(kuò)展運(yùn)算符,將 user1 中剩余的值傳遞給 user2(除了已經(jīng)定義的 email)。
結(jié)構(gòu)體的方法
使用 impl 結(jié)構(gòu)體名,并在其中定義函數(shù)。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"長方形的面積為 {}",
rect1.area()
);
}
圖片
針對(duì)此處更詳細(xì)的內(nèi)容,可以翻看我們之前的結(jié)構(gòu)體
13. 枚舉
枚舉,也被稱作 enums。枚舉允許你通過「列舉可能的成員variants來定義一個(gè)類型」
enum IpAddrKind {
V4,
V6,
}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
枚舉的成員位于其標(biāo)識(shí)符的「命名空間中」,并「使用兩個(gè)冒號(hào)分開」。
match
這是類似于 switch 的東西,
enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
let number = 5;
match number {
1 => println!("One"),
2 => println!("Two"),
3 | 4 | 5 => println!("Three, Four, or Five"),
_ => println!("Other"), // 默認(rèn)情況
}
}
每個(gè)分支相關(guān)聯(lián)的代碼是一個(gè)表達(dá)式,而表達(dá)式的結(jié)果值將作為整個(gè) match 表達(dá)式的返回值。
Option 枚舉和其相對(duì)于空值的優(yōu)勢
圖片
if Let
這是一種使用 if 的花式方式,我們?cè)谄渲卸x一個(gè)表達(dá)式。
fn main() {
let optional_number: Option<i32> = Some(5);
// 使用 if let 匹配 Some 變體并提取內(nèi)部值
if let Some(num) = optional_number {
println!("Value: {}", num);
} else {
println!("No value");
}
}
14. 并發(fā)性
并發(fā)編程和并行編程
圖片
代碼實(shí)現(xiàn)
為了創(chuàng)建一個(gè)新線程,需要調(diào)用 thread::spawn 函數(shù)并「傳遞一個(gè)閉包」,并在其中包含希望在新線程運(yùn)行的代碼。
可以通過將 thread::spawn 的「返回值儲(chǔ)存在變量中來修復(fù)新建線程部分沒有執(zhí)行或者完全沒有執(zhí)行的問題」。thread::spawn 的返回值類型是 JoinHandle。JoinHandle 是一個(gè)「擁有所有權(quán)的值」,當(dāng)「對(duì)其調(diào)用 join 方法時(shí),它會(huì)等待其線程結(jié)束」。
use std::thread;
fn main() {
// 數(shù)據(jù)
let numbers = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// 將數(shù)據(jù)分成兩部分
let mid = numbers.len() / 2;
let (left, right) = numbers.split_at(mid);
// 生成兩個(gè)線程來計(jì)算每一半的總和
let handle1 = thread::spawn(move || sum(left));
let handle2 = thread::spawn(move || sum(right));
// 等待線程完成并獲取它們的結(jié)果
let result1 = handle1.join().unwrap();
let result2 = handle2.join().unwrap();
// 計(jì)算最終總和
let total_sum = result1 + result2;
println!("Total sum: {}", total_sum);
}
fn sum(numbers: &[i32]) -> i32 {
let mut sum = 0;
for &num in numbers {
sum += num;
}
sum
}
thread::spawn 要求閉包具有 'static 生命周期,這意味著它不會(huì)從周圍范圍借用任何東西,并且可以在整個(gè)程序的持續(xù)時(shí)間內(nèi)存在。
因此,我們使用move 閉包,其經(jīng)常與 thread::spawn 一起使用,因?yàn)樗试S我們「在一個(gè)線程中使用另一個(gè)線程的數(shù)據(jù)」。