基本語法和概念
-
你好世界
這是標準的“你好,世界!” Rust 中的程序。
fn main() { println!("Hello, world!"); } -
變量和可變性
Rust 中的變量默認是不可變的。要使變量可變,請使用
mut關鍵字。let x = 5; // immutable variable let mut y = 5; // mutable variable y = 6; // this is okay -
數據類型
Rust 是一種靜態類型語言,這意味著它必須在編譯時知道所有變量的類型。
let x: i32 = 5; // integer type let y: f64 = 3.14; // floating-point type let z: bool = true; // boolean type let s: &str = "Hello"; // string slice type -
控制流
Rust 的控制流關鍵字包括
if、else、while、for和match。if x < y { println!("x is less than y"); } else if x > y { println!("x is greater than y"); } else { println!("x is equal to y"); } -
功能
Rust 中的函數是用
fn關鍵字定義的。fn greet() { println!("Hello, world!"); } -
結構體
結構體用于在 Rust 中創建復雜的數據類型。
struct Point { x: i32, y: i32, } let p = Point { x: 0, y: 0 }; // instantiate a Point struct -
枚舉
Rust 中的枚舉是可以有多種不同變體的類型。
enum Direction { Up, Down, Left, Right, } let d = Direction::Up; // use a variant of the Direction enum -
模式匹配
Rust 具有強大的模式匹配功能,通常與
match關鍵字一起使用。match d { Direction::Up => println!("We're heading up!"), Direction::Down => println!("We're going down!"), Direction::Left => println!("Turning left!"), Direction::Right => println!("Turning right!"), } -
錯誤處理
Rust 使用
Result和Option類型進行錯誤處理。let result: Result<i32, &str> = Ok(42); // a successful result let option: Option<i32> = Some(42); // an optional value
這只是 Rust 語法和概念的初步體驗。當您繼續學習時,該語言還有更多功能需要探索。
變量和數據類型
Rust 是一種靜態類型語言,這意味著它必須在編譯時知道所有變量的類型。編譯器通常可以根據值以及我們如何使用它來推斷我們想要使用什么類型。
變量
默認情況下,Rust 中的變量是不可變的,這意味著它們的值在聲明后就無法更改。如果你想要一個變量是可變的,你可以使用關鍵字mut。
不可變變量:
let x = 5;
可變變量:
let mut y = 5;
y = 6; // This is allowed because y is mutable
數據類型
Rust 語言內置了多種數據類型,可分為:
-
標量類型:表示單個值。例如整數、浮點數、布爾值和字符。
-
復合類型:將多個值分組為一種類型。例如元組和數組。
標量類型
整數:
let a: i32 = 5; // i32 is the type for a 32-bit integer
漂浮:
let b: f64 = 3.14; // f64 is the type for a 64-bit floating point number
布爾值:
let c: bool = true; // bool is the type for a boolean
特點:
let d: char = 'R'; // char is the type for a character. Note that it's declared using single quotes
復合類型
元組:
let e: (i32, f64, char) = (500, 6.4, 'J'); // A tuple with three elements
大批:
let f: [i32; 5] = [1, 2, 3, 4, 5]; // An array of i32s with 5 elements
這些是 Rust 中一些最基本的數據類型和變量聲明。隨著您繼續學習,您將遇到更復雜的類型并學習如何創建自己的類型。
高級數據類型
結構體
結構體允許您創建自定義數據類型。它們是一種從簡單類型創建復雜類型的方法。
定義一個結構體:
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
創建結構體的實例:
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
枚舉
枚舉是枚舉的縮寫,是一種表示數據的類型,該數據是幾種可能的變體之一。枚舉中的每個變體都可以選擇具有與其關聯的數據。
定義一個枚舉:
enum IpAddrKind {
V4,
V6,
}
創建枚舉的實例:
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
選項
Option 枚舉是 Rust 作為其標準庫的一部分提供的特殊枚舉。當值可以是某物或什么都不是時使用它。
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None; // Note that we need to provide the type of None here
結果
Result 枚舉是標準庫中的另一個特殊枚舉,主要用于錯誤處理。它有兩個變體,Ok(成功)和 Err(錯誤)。
enum Result<T, E> {
Ok(T),
Err(E),
}
這些是 Rust 中的一些更高級的數據類型。理解這些概念將使您能夠編寫更健壯、更靈活的 Rust 程序。
標準系列
集合是保存多個值的數據結構。Rust 的標準庫包括幾個通用集合:Vec<T>、HashMap<K, V>和HashSet<T>。
向量
Vector(或Vec<T>)是 Rust 標準庫提供的可調整大小的數組類型。它允許您在單個數據結構中存儲多個值,該數據結構將所有值在內存中彼此相鄰。
創建一個向量并向其中添加元素:
let mut v: Vec<i32> = Vec::new(); // creates an empty vector of i32s
v.push(5);
v.push(6);
v.push(7);
v.push(8);
哈希映射
HashMap,或者說HashMap<K, V>,是一個鍵值對的集合,類似于其他語言中的字典。它允許您將數據存儲為一系列鍵值對,其中每個鍵必須是唯一的。
創建 HashMap 并向其中添加元素:
use std::collections::HashMap;
let mut scores = HashMap::new();
scores.insert(String::from("Blue"), 10);
scores.insert(String::from("Yellow"), 50);
哈希集
HashSet,或者HashSet<T>,是唯一元素的集合。它被實現為一個哈希表,其中每個鍵的值都是無意義的 (),因為我們唯一關心的值是鍵。
創建一個 HashSet 并向其中添加元素:
use std::collections::HashSet;
let mut hs = HashSet::new();
hs.insert("a");
hs.insert("b");
這些是 Rust 中的一些主要集合類型。它們中的每一個都非常有用,具體取決于您想要在程序中實現的目標。
B樹圖
ABTreeMap是按其鍵排序的映射。它允許您按需獲取一系列條目,當您對最小或最大的鍵值對感興趣,或者您想要查找小于或大于某個值的最大或最小鍵時,這非常有用。
use std::collections::BTreeMap;
let mut btree_map = BTreeMap::new();
btree_map.insert(3, "c");
btree_map.insert(2, "b");
btree_map.insert(1, "a");
for (key, value) in &btree_map {
println!("{}: {}", key, value);
}
在上面的示例中,盡管以不同的順序插入,但打印時鍵仍按升序排序。
B樹集
本質BTreeSet上,BTreeMap您只想記住您見過的鍵,并且沒有與您的鍵關聯的有意義的值。當您只想要一套時,它很有用。
use std::collections::BTreeSet;
let mut btree_set = BTreeSet::new();
btree_set.insert("orange");
btree_set.insert("banana");
btree_set.insert("apple");
for fruit in &btree_set {
println!("{}", fruit);
}
在上面的示例中,盡管以不同的順序插入,但水果還是按字典順序(即字母順序)打印出來。
二叉堆
ABinaryHeap是優先級隊列。它允許您存儲一堆元素,但在任何給定時間只處理“最大”或“最重要”的元素。當您需要優先級隊列時,此結構非常有用。
use std::collections::BinaryHeap;
let mut binary_heap = BinaryHeap::new();
binary_heap.push(1);
binary_heap.push(5);
binary_heap.push(2);
println!("{}", binary_heap.peek().unwrap()); // prints: 5
在上面的示例中,盡管以不同的順序插入,“peek”操作仍檢索堆中的最大數字。
控制流
Rust 提供了多種結構來控制程序中的執行流程,包括if、else、loop、while、for和match。
如果別的
該if關鍵字允許您根據條件分支代碼。else并可else if用于替代條件。
let number = 7;
if number < 5 {
println!("condition was true");
} else {
println!("condition was false");
}
環形
該loop關鍵字給你一個無限循環。要停止循環,可以使用關鍵字break。
let mut counter = 0;
loop {
counter += 1;
if counter == 10 {
break;
}
}
盡管
該while關鍵字可用于在條件為真時進行循環。
let mut number = 3;
while number != 0 {
println!("{}!", number);
number -= 1;
}
為了
該for關鍵字允許您循環遍歷集合的元素。
let a = [10, 20, 30, 40, 50];
for element in a.iter() {
println!("the value is: {}", element);
}
匹配
該match關鍵字允許您將值與一系列模式進行比較,然后根據模式匹配執行代碼。
let value = 1;
match value {
1 => println!("one"),
2 => println!("two"),
_ => println!("something else"),
}
這些控制流結構中的每一個都可用于控制 Rust 程序中的執行路徑,使它們更加靈活和動態。
功能
函數是一個命名的語句序列,它接受一組輸入、執行計算或操作,并可選擇返回一個值。函數的輸入稱為參數,返回的輸出稱為返回值。
定義和調用函數
函數是用fn關鍵字定義的。函數的一般形式如下所示:
fn function_name(param1: Type1, param2: Type2, ...) -> ReturnType {
// function body
}
下面是一個簡單函數的示例,它接受兩個整數并返回它們的和:
fn add_two_numbers(x: i32, y: i32) -> i32 {
x + y // no semicolon here, this is a return statement
}
以下是調用此函數的方式:
let sum = add_two_numbers(5, 6);
println!("The sum is: {}", sum);
功能參數
參數是一種將值傳遞給函數的方法。參數在函數定義中指定,當調用函數時,這些參數將包含傳入的值。
這是帶有參數的函數的示例:
fn print_sum(a: i32, b: i32) {
let sum = a + b;
println!("The sum of {} and {} is: {}", a, b, sum);
}
從函數返回值
函數可以返回值。在 Rust 中,函數的返回值與函數體塊中最終表達式的值同義。return您可以通過使用關鍵字并指定值從函數中提前返回,但大多數函數都會隱式返回最后一個表達式。
這是一個返回布爾值的函數:
fn is_even(num: i32) -> bool {
num % 2 == 0
}
在 Rust 中,函數為變量創建了一個新的作用域,這可能會導致諸如影子和所有權之類的概念,這些概念是 Rust 內存管理系統的關鍵方面。
錯誤處理
Rust 將錯誤分為兩大類:可恢復錯誤和不可恢復錯誤。對于可恢復的錯誤,例如找不到文件錯誤,向用戶報告問題并重試操作是合理的。不可恢復的錯誤始終是錯誤的癥狀,例如嘗試訪問超出數組末尾的位置。
Rust 也不例外。Result<T, E>相反,它具有可恢復錯誤的類型和panic!當程序遇到不可恢復錯誤時停止執行的宏。
這是使用的基本示例Result:
fn division(dividend: f64, divisor: f64) -> Result<f64, String> {
if divisor == 0.0 {
Err(String::from("Can't divide by zero"))
} else {
Ok(dividend / divisor)
}
}
您可以這樣處理Result:
match division(4.0, 2.0) {
Ok(result) => println!("The result is {}", result),
Err(msg) => println!("Error: {}", msg),
}
然而,Rust 提供了?可在返回的函數中使用的運算符Result,這使得錯誤處理更加簡單:
fn main() -> Result<(), Box<dyn std::error::Error>> {
let result = division(4.0, 0.0)?;
println!("The result is {}", result);
Ok(())
}
在上面的示例中,如果division函數返回Err,則函數將返回錯誤main。如果返回Ok,則 中的值Ok將被分配給result。
除了 Rust 提供的標準錯誤類型之外,您還可以定義自己的錯誤類型。
enum MyError {
Io(std::io::Error),
Parse(std::num::ParseIntError),
}
impl From<std::io::Error> for MyError {
fn from(err: std::io::Error) -> MyError {
MyError::Io(err)
}
}
impl From<std::num::ParseIntError> for MyError {
fn from(err: std::num::ParseIntError) -> MyError {
MyError::Parse(err)
}
}
高級錯誤處理
對于更高級的錯誤處理,我們可以利用thiserrorcrate 來簡化流程。該板條箱自動執行了創建自定義錯誤類型并為其thiserror實現特征的大部分過程。Error
首先,添加thiserror到您的Cargo.toml依賴項:
[dependencies]
thiserror = "1.0.40"
然后,您可以用來#[derive(thiserror::Error)]創建自己的自定義錯誤類型:
use thiserror::Error;
#[derive(Error, Debug)]
pub enum MyError {
#[error("I/O error: {0}")]
Io(#[from] std::io::Error),
#[error("Parse error: {0}")]
Parse(#[from] std::num::ParseIntError),
// Add other error variants here as needed
}
對于此錯誤類型,由于Io該屬性,Parse將分別自動創建std::io::Error和變體。該屬性指定錯誤消息。std::num::ParseIntError#[from]#[error("...")]
您可以在返回的函數中使用此自定義錯誤類型Result:
use std::fs::File;
fn read_file() -> Result<(), MyError> {
let _file = File::open("non_existent_file.txt")?;
Ok(())
}
為了確保您的代碼能夠適應未來對枚舉的更改Error,Rust 具有該#[non_exhaustive]屬性。當它添加到您的枚舉中時,它就變得不詳盡,因此可以在庫的未來版本中使用其他變體進行擴展:
#[non_exhaustive]
pub enum Error {
Io(std::io::Error),
Parse(std::num::ParseIntError),
// potentially more variants in the future
}
Error現在,當在定義的包之外匹配此枚舉時,Rust 將強制_包含一個 case:
match error {
Error::Io(err) => println!("I/O error: {}", err),
Error::Parse(err) => println!("Parse error: {}", err),
_ => println!("Unknown error"),
}
這種高級錯誤處理方法提供了一種強大而靈活的方法來管理 Rust 中的錯誤,特別是對于庫作者而言。
枚舉和模式匹配
枚舉是枚舉的縮寫,允許您通過枚舉可能的值來定義類型。這是枚舉的基本示例:
enum Direction {
North,
South,
East,
West,
}
枚舉的每個變體都是它自己的類型。您可以將數據與枚舉變體相關聯:
enum OptionalInt {
Value(i32),
Missing,
}
Rust 有一個強大的功能,稱為模式匹配,它允許您使用干凈的語法檢查不同的情況。以下是如何將模式匹配與枚舉結合使用:
let direction = Direction::North;
match direction {
Direction::North => println!("We are heading north!"),
Direction::South => println!("We are heading south!"),
Direction::East => println!("We are heading east!"),
Direction::West => println!("We are heading west!"),
}
Rust 中的模式匹配是詳盡的:我們必須窮盡所有最后的可能性才能使代碼有效,否則代碼將無法編譯。這個功能在處理枚舉時特別有用,因為我們被迫處理所有變體。
Rust 還提供了該if let構造作為match僅關注一種情況的更簡潔的替代方案:
let optional = OptionalInt::Value(5);
if let OptionalInt::Value(i) = optional {
println!("Value is: {}", i);
} else {
println!("Value is missing");
}
在上面的示例中,if let允許您提取并打印它,或者如果是Value(i),optional則打印“值丟失” 。optionalOptionalInt::Missing
枚舉變體還可以具有帶有impl關鍵字的方法:
enum Message {
Quit,
ChangeColor(i32, i32, i32),
Write(String),
}
impl Message {
fn call(&self) {
// method body
}
}
let m = Message::Write(String::from("hello"));
m.call();
call在此示例中,我們定義一個在枚舉上命名的方法Message,然后將其用于實例Message::Write。
Rust 中的枚舉非常通用,并且通過模式匹配,它們在程序中提供了高度的控制流。
非詳盡的枚舉和結構
Rust 中的屬性#[non_exhaustive]是一個有用的功能,可確保枚舉或結構不會在其定義的板條箱外部進行徹底匹配。這對于可能需要向枚舉或結構添加更多變體或字段的庫作者特別有用將來不會破壞現有代碼。
#[non_exhaustive]
pub enum Error {
Io(std::io::Error),
Parse(std::num::ParseIntError),
// potentially more variants in the future
}
在上面的示例中,Error枚舉是非詳盡的,這意味著它可以在定義它的庫的未來版本中使用其他變體進行擴展。在其定義包之外的非詳盡枚舉上進行匹配時,必須包含一個_案例處理未來潛在的變體:
match error {
Error::Io(err) => println!("I/O error: {}", err),
Error::Parse(err) => println!("Parse error: {}", err),
_ => println!("Unknown error"),
}
如果_不包含大小寫,代碼將無法編譯。這有助于確保您的代碼能夠適應未來的枚舉變化Error。
該#[non_exhaustive]屬性還可以與結構一起使用,以防止它們在定義的包之外被解構,確保可以在不破壞現有代碼的情況下添加未來的字段。
Rust 的這一功能提供了一定程度的前向兼容性,并且可以在不造成重大更改的情況下擴展庫中的枚舉和結構。
所有權、借用和生命周期
所有權是 Rust 中的一個關鍵概念,它可以確保內存安全,而不需要垃圾回收。它圍繞三個主要規則:
-
Rust 中的每個值都有一個稱為其所有者的變量。
-
一次只能有一位所有者。
-
當所有者超出范圍時,該值將被刪除。
let s1 = String::from("hello"); // s1 becomes the owner of the string.
let s2 = s1; // s1's ownership is moved to s2.
// println!("{}", s1); // This won't compile because s1 no longer owns the string.
借用是 Rust 中的另一個關鍵概念,它允許您對一個值進行多個引用,只要它們不沖突。借用有兩種類型:可變借用和不可變借用。
let s = String::from("hello");
let r1 = &s; // immutable borrow
let r2 = &s; // another immutable borrow
// let r3 = &mut s; // This won't compile because you can't have a mutable borrow while having an immutable one.
生命周期是 Rust 編譯器確保引用始終有效的一種方式。這是 Rust 中的一個高級概念,通常編譯器可以在大多數情況下推斷生命周期。但有時,您可能必須自己注釋生命周期:
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
在上面的示例中,該函數longest返回兩個字符串切片中最長的一個。生命周期注釋'a指示返回的引用的生命周期至少與兩個輸入生命周期中最短的生命周期一樣長。
所有權、借用和生命周期對于理解 Rust 如何管理內存和確保安全至關重要。Rust 編譯器在編譯時強制執行這些規則,從而實現高效且安全的程序。
泛型
泛型是一種創建在不同類型之間具有廣泛適用性的函數或數據類型的方法。它們是在 Rust 中創建可重用代碼的基本工具。
以下是使用泛型的函數示例:
fn largest<T: PartialOrd>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
在此示例中,T是通用數據類型的名稱。T: PartialOrd是一個特征綁定,這意味著該函數適用于任何T實現該PartialOrd特征的類型(或者換句話說,可以排序的類型)。
泛型也可以用在結構定義中:
struct Point<T> {
x: T,
y: T,
}
在此示例中,Point是一個具有兩個類型為 的字段的結構T。這意味著 aPoint可以具有任何類型x,y只要它們是相同的類型。
泛型在編譯時進行檢查,因此您可以擁有泛型的所有功能,而無需任何運行時成本。它們是編寫靈活、可重用代碼而不犧牲性能的強大工具。
性狀
Rust 中的特征是一種定義跨類型共享行為的方法。您可以將它們視為其他語言中的界面。
這是定義特征并實現它的示例:
trait Speak {
fn speak(&self);
}
struct Dog;
struct Cat;
impl Speak for Dog {
fn speak(&self) {
println!("Woof!");
}
}
impl Speak for Cat {
fn speak(&self) {
println!("Meow!");
}
}
在上面的示例中,Speak是一個特征,定義了名為 的方法speak。Dog和Cat是實現該Speak特征的結構。這意味著我們可以在和speak的實例上調用該方法。DogCat
結構體
結構或結構是自定義數據類型,可讓您命名并將多個相關值打包在一起。
以下是定義結構體的方法:
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
以下是創建結構體實例的方法:
let user = User {
email: String::from("someone@example.com"),
username: String::from("someusername"),
active: true,
sign_in_count: 1,
};
結構體用于在程序中創建復雜的數據類型,它們是任何 Rust 程序的基本組成部分。
模塊和命名空間
Rust 中的模塊允許您將代碼組織到不同的命名空間中。這對于可讀性和防止命名沖突很有用。
以下是如何定義模塊的示例:
mod front_of_house {
pub mod hosting {
pub fn add_to_waitlist() {}
}
}
在上面的示例中,front_of_house是一個包含另一個模塊 的模塊hosting。add_to_waitlist是模塊中定義的函數hosting。
您可以使用use關鍵字將路徑納入范圍:
use crate::front_of_house::hosting;
fn main() {
hosting::add_to_waitlist();
}
在上面的例子中,我們使用了use“bring hostinginto scope”,這使得我們可以add_to_waitlist在沒有front_of_house前綴的情況下進行調用。
模塊和命名空間對于管理更大的代碼庫和在程序的不同部分重用代碼至關重要。
并發:線程和消息傳遞
并發是許多程序中復雜但重要的一部分,Rust 提供了多種處理并發編程的方法。一種方法是使用帶有消息傳遞的線程來進行線程之間的通信。
以下是創建新線程的方法:
use std::thread;
use std::time::Duration;
fn main() {
thread::spawn(|| {
for i in 1..10 {
println!("hi number {} from the spawned thread!", i);
thread::sleep(Duration::from_millis(1));
}
});
for i in 1..5 {
println!("hi number {} from the main thread!", i);
thread::sleep(Duration::from_millis(1));
}
}
在這個例子中,我們使用thread::spawn創建一個新線程。新線程打印一條消息并循環休眠一毫秒。
但是我們如何處理線程之間的通信呢?Rust 的標準庫提供了用于此目的的通道:
use std::thread;
use std::sync::mpsc; // mpsc stands for multiple producer, single consumer.
fn main() {
let (tx, rx) = mpsc::channel();
thread::spawn(move || {
let val = String::from("hi");
tx.send(val).unwrap();
// println!("val is {}", val); // This won't compile because `val` has been moved.
});
let received = rx.recv().unwrap();
println!("Got: {}", received);
}
在此示例中,mpsc::channel創建一個新通道。(發送器tx)被移動到新線程中,并且它沿著通道發送字符串“hi”。主線程中的(接收者rx)接收字符串并打印它。
Rust 的線程和消息傳遞并發模型強制線程之間發送的所有數據都是線程安全的。編譯時檢查可確保您不會出現數據爭用或其他常見并發問題,這可以使并發代碼更安全、更容易推理。
并發:共享狀態并發
除了消息傳遞之外,Rust 還允許通過使用Mutex(“互斥”的縮寫)和Arc(原子引用計數器)來實現共享狀態并發。
AMutex提供互斥,這意味著它確保在任何給定時間只有一個線程可以訪問某些數據。要訪問數據,線程必須首先通過詢問互斥體來發出它想要訪問的信號。
Arc另一方面,是一種智能指針,它允許同一數據的多個所有者,并確保當對數據的所有引用超出范圍時數據得到清理。
Mutex以下是如何使用and的示例Arc:
use std::sync::{Mutex, Arc};
use std::thread;
fn main() {
let counter = Arc::new(Mutex::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter = Arc::clone(&counter);
let handle = thread::spawn(move || {
let mut num = counter.lock().unwrap();
*num += 1;
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
println!("Result: {}", *counter.lock().unwrap());
}
在此示例中,我們在一個內部創建一個計數器Arc<Mutex<T>>,可以在多個線程之間安全地共享和改變。MutexGuard每個線程獲取一個鎖,遞增計數器,然后在超出范圍時釋放鎖。
使用這些工具,Rust 可以通過編譯時檢查確保安全并發,有助于避免與共享狀態并發相關的常見陷阱(例如競爭條件)。
錯誤處理:恐慌 vs. 預期 vs. 展開
錯誤處理在任何編程語言中都至關重要,Rust 為此提供了多種工具:
panic!:該宏使程序終止執行,同時展開并清理堆棧。
fn main() {
panic!("crash and burn");
}
unwrapOk:如果Resultis 則此方法返回 an 內的值,如果is則Ok調用宏。panic!ResultErr
let x: Result<u32, &str> = Err("emergency failure");
x.unwrap(); // This will call panic!
expect:此方法與 類似unwrap,但允許您指定緊急消息。
let x: Result<u32, &str> = Err("emergency failure");
x.expect("failed to get the value"); // This will call panic with the provided message.
雖然unwrap和expect很簡單,但應減少使用頻率,因為它們可能會導致程序突然終止。在大多數情況下,您應該致力于在適當的時候使用模式匹配和傳播錯誤來優雅地處理錯誤。
測試
測試是軟件開發的重要組成部分,Rust 對使用以下屬性編寫自動化測試具有一流的支持#[test]:
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
在上面的代碼中,#[test]將該函數標記為測試函數,并且assert_eq!是一個宏,用于檢查兩個參數是否相等,如果不相等則發生恐慌。
FFI(外部函數接口)
Rust 提供了外部函數接口 (FFI),允許 Rust 代碼與其他語言編寫的代碼進行交互。下面是從 Rust 調用 C 函數的示例:
extern "C" {
fn abs(input: i32) -> i32;
}
fn main() {
unsafe {
println!("Absolute value of -3 according to C: {}", abs(-3));
}
}
在此示例中,該extern "C"塊定義了 C 函數的接口abs。之所以被標記,是unsafe因為要由程序員來確保外來代碼的正確性。
宏
Rust 中的宏是定義可重用代碼塊的一種方式。宏看起來像函數,只不過它們對指定為其參數的代碼標記進行操作,而不是對這些標記的值進行操作。
這是一個簡單宏的示例:
macro_rules! say_hello {
() => (
println!("Hello, world!");
)
}
fn main() {
say_hello!();
}
在此示例中,say_hello!是一個打印“Hello, world!”的宏。!宏使用與常規 Rust 函數不同的語法,并且在名稱后用 來表示。它們是 Rust 中代碼重用和元編程的強大工具。
程序宏
Rust 中的過程宏就像函數:它們接受代碼作為輸入,對該代碼進行操作,并生成代碼作為輸出。它們比聲明性宏更靈活。下面是派生宏的示例,它是一種特定類型的過程宏:
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(HelloWorld)]
pub fn hello_world_derive(input: TokenStream) -> TokenStream {
let ast = parse_macro_input!(input as DeriveInput);
let gen = quote! {
impl HelloWorld for #ast {
fn hello_world() {
println!("Hello, World! My name is {}", stringify!(#ast));
}
}
};
gen.into()
}
HelloWorld在此示例中,我們創建一個過程宏,為給定類型生成特征的實現。
要使用此宏,您首先需要將板條箱添加到您的依賴項中Cargo.toml:
[dependencies]
HelloMacro = "0.1.0"
然后,在 Rust 代碼中,您將導入宏并將其應用到結構或枚舉:
use HelloMacro::HelloMacro;
#[derive(HelloMacro)]
struct Pancakes;
fn main() {
Pancakes::hello_macro();
}
在此示例中,過程宏生成為結構HelloMacro調用的函數。調用時,此函數會打印“Hello,Macro!我的名字是 Pancakes”。hello_macroPancakes
請注意,創建過程宏所涉及的復雜性比此示例所示的要復雜得多。定義HelloMacro過程宏需要創建一個單獨的類型為 的包proc-macro,并實現一個生成所需代碼的函數。和包通常用于在過程宏中解析和生成 Rust 代碼syn。quote
Rust 的內置特征
Rust 有幾個對 Rust 編譯器有特殊意義的內置特征,例如Copy、Drop、Deref等等。
例如,該Copy特征表示可以通過復制位來復制類型的值。如果類型實現了Copy,則可以復制它,而無需“移動”原始值。另一方面,Drop特征用于指定當類型的值超出范圍時會發生什么。
-
CloneandCopy:該Clone特征用于需要實現創建實例副本的方法的類型。如果復制過程很簡單(即,僅復制位),則Copy可以使用該特征。#[derive(Clone, Copy)] struct Point { x: i32, y: i32, } -
Drop:此特征允許您自定義當值超出范圍時會發生的情況。當您的類型正在管理資源(如內存或文件)并且您需要在使用完畢后進行清理時,這特別有用。struct Droppable { name: &'static str, } impl Drop for Droppable { fn drop(&mut self) { println!("{} is being dropped.", self.name); } } -
DerefandDerefMut:這些特征用于重載解引用運算符。Deref用于重載不可變解引用運算符,而DerefMut用于重載可變解引用運算符。use std::ops::Deref; struct DerefExample<T> { value: T, } impl<T> Deref for DerefExample<T> { type Target = T; fn deref(&self) -> &T { &self.value } } -
PartialEq和Eq:這些特征用于比較對象的等效性。PartialEq允許部分比較,而Eq要求完全相等(即,它要求每個值必須與其自身相等)。#[derive(PartialEq, Eq)] struct EquatableExample { x: i32, } -
PartialOrd和Ord:這些特征用于比較對象以進行排序。PartialOrd允許部分比較,而Ord需要全排序。#[derive(PartialOrd, Ord)] struct OrderableExample { x: i32, } -
AsRef和AsMut:這些特征用于廉價的引用到引用的轉換。AsRef用于轉換為不可變引用,而AsMut用于轉換為可變引用。fn print_length<T: AsRef<str>>(s: T) { println!("{}", s.as_ref().len()); }
這些只是 Rust 中可用的內置特征的幾個示例。還有更多,每一個都有特定的目的。這是 Rust 支持多態性的方式之一。
迭代器和閉包
迭代器是一種生成值序列的方法,通常在循環中。這是一個例子:
let v1 = vec![1, 2, 3];
let v1_iter = v1.iter();
for val in v1_iter {
println!("Got: {}", val);
}
閉包是一個可以捕獲其環境的匿名函數。這是一個例子:
let x = 4;
let equal_to_x = |z| z == x;
let y = 4;
assert!(equal_to_x(y));
使用 Rust 進行異步編程
Rust 的async/.await語法使得 Rust 中的異步編程更加符合人體工程學。這是一個例子:
async fn hello_world() {
println!("hello, world!");
}
fn main() {
let future = hello_world(); // Nothing is printed
futures::executor::block_on(future); // "hello, world!" is printed
}
Rust 中的固定和取消固定
Pin是一種標記類型,指示它所包裝的值不得移出其中。這對于自引用結構和其他不需要移動的情況很有用。
Unpin是一個自動特征,表明它所實現的類型可以安全地移出。
-
Pin:該Pin類型是一個包裝器,它使得它包裝的值不可移動。這意味著,一旦一個值被固定,它就不能再移動到其他地方,并且它的內存地址也不會改變。當處理需要具有穩定地址的某些類型的不安全代碼時,例如在構建自引用結構或處理異步編程時,這可能很有用。這是固定值的示例:
let mut x = 5; let mut y = Box::pin(x); let mut z = y.as_mut(); *z = 6; assert_eq!(*y, 6);在上面的示例中,是包含值 的
y固定值。當我們獲得對with 的可變引用時,我們可以更改 中的值,但不能更改為指向其他內容。里面的值是“固定”的。Box5yy.as_mut()Boxyy -
Unpin:該Unpin特征是一個“自動特征”(由 Rust 編譯器自動實現的特征),它是為所有沒有任何固定字段的類型實現的,本質上可以安全地移動這些類型。下面是一個
Unpin類型的示例:struct MyStruct { field: i32, }在上面的例子中,
MyStruct是Unpin因為它的所有字段都是Unpin. 這意味著在內存中移動是安全的MyStruct。
和特性是 Rust 安全處理內存并確保對對象的引用保持有效的能力的關鍵部分Pin。Unpin它們廣泛用于高級 Rust 編程,例如使用async/await或其他形式的“自引用”結構時。