Rust 完全速查表
基本语法和概念
变量和数据类型
控制流
函数
错误处理
高级错误处理
枚举和模式匹配
所有权、借贷和寿命
仿制药
特征
结构
模块和命名空间
并发性:线程和消息传递
并发性:共享状态并发性
错误处理:panic、expect 和 unwrap
测试
FFI(外来功能接口)
宏
程序宏
Rust 的内置特性
迭代器和闭包
使用 Rust 进行异步编程
锈蚀中的销钉和松开
由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!
这份《Rust 完全速查表》全面介绍了 Rust 编程语言,涵盖了其所有主要特性。内容从语法和基本概念等基础知识,到并发和错误处理等更复杂的方面,应有尽有。速查表还深入探讨了 Rust 的独特特性,例如所有权、借用和生命周期,以及其强大的类型系统和完善的宏系统。每个主题都配有清晰的示例来阐释。对于刚刚入门 Rust 的初学者以及希望快速复习特定 Rust 概念的经验丰富的开发人员来说,这都是一份理想的资源。
我编写这份速查表是为了全面了解 Rust 编程语言,并希望它能成为我个人的参考工具。然而,Rust 社区的魅力就在于共享学习和协作。因此,如果您发现我遗漏的内容、错误,或者有任何改进建议,请随时分享您的反馈。记住,人无完人,这份资源也不例外——正是通过您的见解,我们才能不断完善它。祝您 Rust 学习愉快!
基本语法和概念
-
你好世界
这是用 Rust 编写的标准“Hello, world!”程序。
fn main() { println!("Hello, world!"); } -
变量与可变性
在 Rust 中,变量默认是不可变的。要使变量可变,请使用 `mutable`
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 使用 `std::error`
Result和 `std::error`Option类型进行错误处理。let result: Result<i32, &str> = Ok(42); // a successful result let option: Option<i32> = Some(42); // an optional value
这只是 Rust 语法和概念的冰山一角。随着学习的深入,这门语言还有更多特性等待你去探索。
变量和数据类型
Rust 是一种静态类型语言,这意味着它必须在编译时知道所有变量的类型。编译器通常可以根据变量值及其使用方式推断出我们想要使用的类型。
变量
Rust 中的变量默认是不可变的,这意味着它们在声明后不能被修改。如果你想让变量可变,可以使用 `mutable`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,即枚举类型)是一种数据类型,它表示若干种可能变体中的一种。枚举中的每个变体都可以选择性地关联一些数据。
定义枚举类型:
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 的标准库包含几个功能丰富的集合:`Array` Vec<T>、HashMap<K, V>`String` 和`ArrayList` HashSet<T>。
向量
Vector(或称 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 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);
}
在上面的例子中,尽管水果的插入顺序不同,但它们的打印顺序却是按字典顺序(即字母顺序)排列的。
二叉堆
这BinaryHeap是一个优先级队列。它允许你存储多个元素,但每次只处理其中“最大”或“最重要的”元素。当你需要优先级队列时,这种结构非常有用。
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、、和。loopwhileformatch
如果-否则
该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 中,函数的返回值等同于函数体代码块中最后一个表达式的值。你可以使用 ` returnreturn` 关键字并指定一个值来提前从函数返回,但大多数函数默认返回最后一个表达式的值。
这是一个返回布尔值的函数:
fn is_even(num: i32) -> bool {
num % 2 == 0
}
在 Rust 中,函数为变量创建了一个新的作用域,这可以引申出诸如变量遮蔽和所有权之类的概念,而这些概念是 Rust 内存管理系统的关键方面。
错误处理
Rust 将错误分为两大类:可恢复错误和不可恢复错误。对于可恢复错误,例如文件未找到错误,通常会将问题报告给用户并重试操作。而不可恢复错误则总是 bug 的表现,例如尝试访问数组末尾之外的位置。
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函数返回 false Err,则函数会返回错误信息main。如果函数返回 true Ok,则函数内部的值Ok将被赋给 false 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 来简化流程。该crate 可以自动完成创建自定义错误类型和为其thiserror实现 trait 的大部分工作。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的变体。该属性指定错误消息。Parsestd::io::Errorstd::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现在,当在定义此枚举的 crate 之外匹配此枚举时,Rust 将强制要求_包含一个 case:
match error {
Error::Io(err) => println!("I/O error: {}", err),
Error::Parse(err) => println!("Parse error: {}", err),
_ => println!("Unknown error"),
}
这种先进的错误处理方法为 Rust 中的错误管理提供了一种强大而灵活的方式,尤其适用于库的作者。
枚举和模式匹配
枚举(enums,即 enumerations 的缩写)允许你通过枚举其可能的值来定义一种类型。以下是一个枚举的基本示例:
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 中的 `is`属性#[non_exhaustive]是一项实用功能,它可以确保枚举或结构体不会在其定义的 crate 之外进行穷举匹配。这对于库的作者来说尤其有用,因为他们将来可能需要向枚举或结构体添加更多变体或字段,而不会破坏现有代码。
#[non_exhaustive]
pub enum Error {
Io(std::io::Error),
Parse(std::num::ParseIntError),
// potentially more variants in the future
}
在上面的示例中,枚举Error类型并非穷尽枚举,这意味着它可能会在其定义库的未来版本中添加其他变体。当在定义它的 crate 之外匹配非穷尽枚举时,必须包含一个_case 来处理未来可能出现的变体:
match error {
Error::Io(err) => println!("I/O error: {}", err),
Error::Parse(err) => println!("Parse error: {}", err),
_ => println!("Unknown error"),
}
如果_缺少此情况,代码将无法编译。这有助于确保您的代码能够适应枚举类型的未来变更Error。
该#[non_exhaustive]属性还可以与结构体一起使用,以防止它们在其定义 crate 之外被解构,从而确保将来可以添加字段而不会破坏现有代码。
Rust 的这一特性提供了一定程度的前向兼容性,使得在库中扩展枚举和结构体成为可能,而不会造成破坏性更改。
所有权、借贷和寿命
所有权是 Rust 中的一个关键概念,它确保了内存安全,而无需垃圾回收。它围绕三个主要规则展开:
-
Rust 中的每个值都有一个名为 owner 的变量。
-
同一时间只能有一个所有者。
-
当所有者超出范围时,价值将会下降。
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是一个 trait 绑定,这意味着此函数适用于任何T实现了该PartialOrdtrait 的类型(或者换句话说,可以排序的类型)。
泛型也可以用于结构体定义中:
struct Point<T> {
x: T,
y: T,
}
在这个例子中,Point`a` 是一个结构体,它有两个类型为 `T` 的字段T。这意味着 `a` 的 `a`和`b`Point可以是任何类型,只要它们类型相同即可。xy
泛型在编译时进行检查,因此您可以充分利用泛型的强大功能,而无需任何运行时开销。它们是编写灵活、可重用代码而不牺牲性能的强大工具。
特征
Rust 中的 Trait 是一种定义跨类型共享行为的方法。你可以把它们想象成其他语言中的接口。
以下是一个定义和实现特性的示例:
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!");
}
}
在上面的例子中,` Speakis` 是一个定义了名为speak`.`的方法的 trait Dog,而 `.` 和Cat`.` 是实现了该 trait 的结构体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();
}
在上面的例子中,我们使用 ` useto` 来引入作用域,这样我们就可以不用前缀hosting来调用它。add_to_waitlistfront_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发送器(transmitter)被移到新线程中,并向该通道发送字符串“hi”。rx主线程中的接收器(receiver)接收到该字符串并将其打印出来。
Rust 的线程和消息传递并发模型确保线程间传递的所有数据都是线程安全的。编译时检查保证不会出现数据竞争或其他常见的并发问题,从而使并发代码更安全、更易于理解。
并发性:共享状态并发性
除了消息传递之外,Rust 还允许通过使用互斥Mutex(即“互斥”)和Arc原子引用计数器来实现共享状态并发。
互斥锁Mutex提供互斥功能,这意味着它确保在任何给定时间只有一个线程可以访问某些数据。要访问数据,线程必须首先通过向互斥锁发出访问请求的信号。
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 可以通过编译时检查来确保安全的并发,从而帮助避免与共享状态并发相关的常见陷阱,例如竞态条件。
错误处理:panic、expect 和 unwrap
错误处理在任何编程语言中都至关重要,Rust 为此提供了多种工具:
panic!:此宏会导致程序终止执行,并在执行过程中展开和清理堆栈。
fn main() {
panic!("crash and burn");
}
unwrapOk:如果为Result真,则此方法返回内部的值;如果为真Ok,则调用宏。panic!ResultErr
let x: Result<u32, &str> = Err("emergency failure");
x.unwrap(); // This will call panic!
expect:此方法与类似unwrap,但允许您指定一个 panic 消息。
let x: Result<u32, &str> = Err("emergency failure");
x.expect("failed to get the value"); // This will call panic with the provided message.
虽然unwrap`and` 和 ` expector` 都很直接,但应该尽量少用,因为它们可能会导致程序突然终止。大多数情况下,你应该尽量优雅地处理错误,例如使用模式匹配,并在适当的时候传播错误。
测试
测试是软件开发的重要组成部分,Rust 为编写自动化测试提供了顶级的支持,它使用以下#[test]属性:
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}
在上面的代码中,#[test]将函数标记为测试函数,并且assert_eq!是一个宏,用于检查两个参数是否相等,如果不相等则引发 panic。
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在这个例子中,我们创建了一个过程宏,该宏会为给定的类型生成一个特性的实现。
要使用此宏,您首先需要将该 crate 添加到您的依赖项中Cargo.toml:
[dependencies]
HelloMacro = "0.1.0"
然后,在你的 Rust 代码中,你可以导入该宏并将其应用于结构体或枚举:
use HelloMacro::HelloMacro;
#[derive(HelloMacro)]
struct Pancakes;
fn main() {
Pancakes::hello_macro();
}
在这个例子中,过程宏会为结构体HelloMacro生成一个名为 `<function_name>` 的函数。调用该函数时,它会打印“Hello, Macro! My name is Pancakes”。hello_macroPancakes
请注意,创建过程宏比本示例所示要复杂得多。定义HelloMacro过程宏需要创建一个单独的 `<crate>` 类型proc-macro,并实现一个生成所需代码的函数。`<crate>`syn和quote`<crate>` 通常用于在过程宏中解析和生成 Rust 代码。
Rust 的内置特性
Rust 有几个内置特性,对 Rust 编译器具有特殊意义,例如,,,Copy等等。DropDeref
例如,` Copyis_copy` 特性表示可以通过简单地复制位来复制类型的值。如果一个类型实现了 `is_copy` 特性Copy,则可以在不“移动”原始值的情况下复制它。另一方面,`is_overflow`Drop特性用于指定当类型的值超出作用域时会发生什么。
-
Clone此外Copy:该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); } } -
Deref和DerefMut:这些特性用于重载解引用运算符。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语法使得异步编程更加人性化。以下是一个示例:
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
}
锈蚀中的销钉和松开
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);在上面的例子中,`a`
y是一个固定的Box变量,其值为 `0` 。当我们使用 ` a`5获取到 `a` 的可变引用时,我们可以更改 `a` 中的值,但不能将其指向其他值。`a` 中的值是“固定”的。yy.as_mut()Boxyy -
Unpin该Unpin特性是一个“自动特性”(由 Rust 编译器自动实现的特性),它为所有没有固定字段的类型实现,从本质上来说,这使得移动这些类型是安全的。以下是一个
Unpin类型示例:struct MyStruct { field: i32, }在上面的例子中,
MyStruct是Unpin因为它的所有字段都是空的Unpin。这意味着MyStruct在内存中移动它是安全的。
` Pinand`Unpin特性是 Rust 安全管理内存并确保对象引用始终有效的关键组成部分。它们在高级 Rust 编程中被广泛使用,例如在处理async/await`or` 或其他形式的“自引用”结构时。