引言
Rust 是一门系统级编程语言,由 Mozilla 研究院开发,旨在提供内存安全、并发安全和高性能。它没有垃圾回收器,而是通过所有权系统在编译时保证内存安全。Rust 的语法类似 C/C++,但融入了许多现代语言特性,如模式匹配、类型推断、trait 系统等。近年来,Rust 在开发者社区中越来越受欢迎,被广泛应用于操作系统、嵌入式、WebAssembly、网络服务等领域。
本指南将从零开始,带你全面了解 Rust 的安装、语法、核心概念、工具链、项目组织,一直到如何将你的 crate 发布到 crates.io。无论你是初学者还是有一定经验的开发者,都能从中受益。
安装 Rust
Rust 官方提供了便捷的安装工具rustup,它可以管理 Rust 版本和相关的工具链。我们将介绍在主流操作系统上的安装步骤。
在 Windows 上安装
访问 https://rustup.rs,下载
rustup-init.exe。运行下载的可执行文件,会出现一个命令行界面,提示选择安装选项。通常选择默认安装(键入 1 并回车)即可。
安装程序会自动下载并安装 Rust 编译器(rustc)、包管理器 Cargo、以及文档等组件。
安装完成后,需要重启终端或手动刷新环境变量,以便
rustc和cargo命令可用。
此外,Rust 依赖于 Microsoft C++ 构建工具。如果你在安装后尝试编译代码时遇到链接错误,可能需要安装 Visual Studio Build Tools 或 Visual Studio(包含 C++ 工具链)。
在 macOS 上安装
macOS 用户可以通过终端运行以下命令安装:
bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
该脚本会下载并运行rustup-init,同样会有交互式选项,选择默认安装即可。安装程序会自动将~/.cargo/bin添加到 PATH。之后你需要重启终端或执行source ~/.cargo/env使配置生效。
在 Linux 上安装
在 Linux 上,同样使用上述curl命令进行安装。大多数 Linux 发行版需要安装编译器和链接器(如gcc、make等)。例如在 Ubuntu 上:
bash
sudo apt update sudo apt install build-essential curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
安装完成后,同样需要将~/.cargo/bin加入 PATH。
验证安装
安装完成后,打开新终端并执行:
bash
rustc --version cargo --version
如果看到类似rustc 1.70.0 (90c541806 2023-05-31)的输出,说明安装成功。
更新和卸载 Rust
更新 Rust 工具链:
bash
rustup update
卸载 Rust:
bash
rustup self uninstall
第一个 Rust 程序
Hello, World!
按照传统,我们从 Hello, World! 开始。创建一个新目录,比如hello_world,并在其中新建一个文件main.rs,内容如下:
rust
fn main() { println!("Hello, world!"); }在终端进入该目录,执行:
bash
rustc main.rs ./main # 在 Windows 上是 .\main.exe
你会在屏幕上看到 "Hello, world!"。这里rustc直接编译了单个文件,但在实际项目中我们通常使用 Cargo。
使用 Cargo 创建项目
Cargo 是 Rust 的构建系统和包管理器。创建一个新项目:
bash
cargo new hello_cargo cd hello_cargo
Cargo 会生成如下目录结构:
text
hello_cargo/ Cargo.toml src/ main.rs
Cargo.toml是项目的配置文件,类似于package.json。src/main.rs是源代码文件。
默认生成的main.rs已经包含一个 "Hello, world!" 程序。构建并运行:
bash
cargo build # 编译,生成可执行文件在 target/debug/hello_cargo cargo run # 编译并运行
你也可以使用cargo check快速检查代码是否通过编译,而不生成可执行文件。
基础语法
变量与可变性
Rust 中变量默认是不可变的。使用let声明变量:
rust
let x = 5; // 不可变 x = 6; // 错误!不能对不可变变量二次赋值
若希望变量可变,需添加mut关键字:
rust
let mut y = 5; y = 6; // 允许
常量使用const声明,必须注明类型,且值必须在编译时确定:
rust
const MAX_POINTS: u32 = 100_000;
变量可以隐藏(shadowing),即重新使用let声明同名变量,新变量会覆盖旧变量:
rust
let x = 5; let x = x + 1; // 新变量 x 为 6,原 x 被隐藏
数据类型
Rust 是静态类型语言,但编译器通常能推断类型。主要分为标量(scalar)和复合(compound)类型。
标量类型
整数:有符号
i8, i16, i32, i64, i128, isize(与架构相关),无符号u8, u16, u32, u64, u128, usize。默认i32。浮点数:
f32, f64。默认f64。布尔:
bool,值为true或false。字符:
char,用单引号,Unicode 标量值,如'a'、'😻'。
复合类型
元组:不同类型值的集合,长度固定。如
let tup: (i32, f64, u8) = (500, 6.4, 1);。可以通过模式匹配或索引访问:tup.0。数组:相同类型值的集合,长度固定。如
let arr = [1, 2, 3];。访问:arr[0]。数组存储在栈上,若需要动态大小,应使用 Vector。
函数
使用fn定义函数,参数需注明类型,返回值类型在箭头后注明:
rust
fn add(x: i32, y: i32) -> i32 { x + y // 表达式,不加分号即为返回值 }最后一个表达式就是返回值,也可使用return提前返回。
注释
普通注释:
//或/* ... */文档注释:
///用于生成 API 文档,支持 Markdown。
控制流
if 表达式
rust
let number = 3; if number < 5 { println!("condition was true"); } else if number == 5 { println!("condition was five"); } else { println!("condition was false"); }if是表达式,可以用于赋值:
rust
let condition = true; let number = if condition { 5 } else { 6 };循环
loop:无限循环,可用break退出,并可返回值。rust
let result = loop { counter += 1; if counter == 10 { break counter * 2; } };while:条件循环。for:遍历集合,最常用。rust
let a = [10, 20, 30]; for element in a.iter() { println!("value: {}", element); } // 或使用范围 for number in 1..4 { println!("{}", number); // 1 2 3 }
所有权系统
所有权是 Rust 最独特的特性,它让 Rust 无需垃圾回收即可保证内存安全。
所有权规则
Rust 中每一个值都有一个被称为其所有者的变量。
值在任一时刻有且只有一个所有者。
当所有者(变量)离开作用域,这个值将被丢弃。
内存与分配
对于栈上数据(如标量),拷贝是廉价的;对于堆上数据(如String),当变量离开作用域时,Rust 会自动调用drop释放内存。考虑以下代码:
rust
let s1 = String::from("hello"); let s2 = s1; // s1 的所有权被移动到 s2,s1 不再有效 println!("{}", s1); // 错误!s1 已被移动若想深拷贝堆数据,可以使用clone:
rust
let s1 = String::from("hello"); let s2 = s1.clone(); // 堆数据被复制 println!("s1 = {}, s2 = {}", s1, s2); // 都有效对于栈上数据,如整数,实现了Copytrait,赋值时会自动拷贝:
rust
let x = 5; let y = x; // x 仍然有效,因为整数是 Copy 的
引用与借用
如果我们想使用值但不获取所有权,可以使用引用。引用默认也是不可变的。
rust
fn calculate_length(s: &String) -> usize { // s 是对 String 的引用 s.len() } let s1 = String::from("hello"); let len = calculate_length(&s1); // 传递引用,s1 所有权未移动通过引用传递参数称为借用。引用默认不可变,但可以创建可变引用:
rust
fn change(s: &mut String) { s.push_str(", world"); } let mut s = String::from("hello"); change(&mut s);注意:在特定作用域中,对特定数据只能有一个可变引用,且不能同时拥有可变和不可变引用。这防止了数据竞争。
切片
切片是集合中一部分连续元素的引用,它没有所有权。例如字符串切片:
rust
let s = String::from("hello world"); let hello = &s[0..5]; // "hello" let world = &s[6..11]; // "world"数组切片类似:let slice = &arr[1..3];。
字符串字面量本身就是切片:let s: &str = "Hello";。
结构体与方法
定义结构体
使用struct关键字定义:
rust
struct User { username: String, email: String, sign_in_count: u64, active: bool, }实例化结构体
rust
let user1 = User { email: String::from("someone@example.com"), username: String::from("someusername123"), active: true, sign_in_count: 1, };可以使用点号访问字段。如果变量名与字段名相同,可以用字段初始化简写:
rust
fn build_user(email: String, username: String) -> User { User { email, username, active: true, sign_in_count: 1, } }结构体更新语法:
rust
let user2 = User { email: String::from("another@example.com"), ..user1 // 其余字段从 user1 获取,注意 user1 的部分字段可能被移动 };元组结构体(类似元组但有名):
rust
struct Color(i32, i32, i32); let black = Color(0, 0, 0);
单元结构体(无字段):
rust
struct Empty;
方法语法
方法在impl块中定义,第一个参数总是self(或其变体&self、&mut self),代表调用该方法的实例。
rust
impl User { fn is_active(&self) -> bool { self.active } } let user1 = User { ... }; println!("{}", user1.is_active());关联函数
在impl中定义不带self参数的函数,称为关联函数,通常用作构造函数,例如String::from。
rust
impl User { fn new(email: String, username: String) -> User { User { email, username, active: true, sign_in_count: 1, } } } let user = User::new(String::from("..."), String::from("..."));枚举与模式匹配
枚举定义
枚举允许你定义一个类型,该类型的值只能是几个变体之一:
rust
enum IpAddrKind { V4, V6, } let four = IpAddrKind::V4;每个变体可以关联不同类型的数据:
rust
enum IpAddr { V4(u8, u8, u8, u8), V6(String), } let home = IpAddr::V4(127, 0, 0, 1); let loopback = IpAddr::V6(String::from("::1"));枚举也可以定义方法。
Option 枚举
Rust 没有空值,而是使用Option<T>枚举来表示值存在或不存在:
rust
enum Option<T> { Some(T), None, }它被包含在预导入模块中,可以直接使用Some和None。
rust
let some_number = Some(5); let absent_number: Option<i32> = None;
使用Option<T>时,必须处理None的情况,避免空指针异常。
match 控制流
match允许将一个值与一系列模式比较,并执行匹配模式对应的代码。模式可以是字面值、变量名、通配符等。
rust
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, } }匹配必须是穷尽的。如果不想处理所有情况,可以使用_通配符:
rust
let some_u8_value = 0u8; match some_u8_value { 1 => println!("one"), 3 => println!("three"), _ => (), }if let 简洁控制
当你只关心某一种模式而忽略其他时,if let更简洁:
rust
let some_u8_value = Some(0u8); if let Some(3) = some_u8_value { println!("three"); } else { // 可选 }模块系统
Rust 提供了一套模块系统来组织代码:包(package)、crate(单元包)、模块(module)和路径(path)。
包、crate 与模块
crate是一个二进制项或库。
crate root是源代码文件,Cargo 会依此编译。包是提供一系列功能的一个或多个 crate。包含一个
Cargo.toml文件,描述如何构建这些 crate。模块用于在 crate 内组织代码,控制私有性。
默认情况下,所有项(函数、结构体等)都是私有的。使用pub关键字公开。
路径
路径有两种形式:
绝对路径:从 crate root 开始,使用 crate 名或字面量
crate。相对路径:从当前模块开始,使用
self、super或当前模块的标识符。
例如,在库 crate 中:
rust
mod front_of_house { pub mod hosting { pub fn add_to_waitlist() {} } } pub fn eat_at_restaurant() { // 绝对路径 crate::front_of_house::hosting::add_to_waitlist(); // 相对路径 front_of_house::hosting::add_to_waitlist(); }use 关键字
使用use将路径引入作用域,类似于import。
rust
use crate::front_of_house::hosting; // 或者使用相对路径 // use self::front_of_house::hosting; pub fn eat_at_restaurant() { hosting::add_to_waitlist(); }习惯上,对于函数,引入父模块;对于结构体、枚举等,引入完整路径。也可以使用as重命名。
rust
use std::fmt::Result; use std::io::Result as IoResult;
pub use可以重导出项,使外部代码能访问原本私有的项。
模块分割文件
当模块变大时,可以将它们放入单独的文件。在src/lib.rs或src/main.rs中:
rust
mod front_of_house; // 告诉 Rust 从 front_of_house.rs 或 front_of_house/mod.rs 加载模块
然后在src/front_of_house.rs中定义模块内容。如果模块还有子模块,可以创建front_of_house目录和mod.rs。
常用集合
Vector
Vec<T>是动态数组,可存储多个相同类型的值。
rust
let mut v: Vec<i32> = Vec::new(); v.push(5); v.push(6); // 使用 vec! 宏创建 let v = vec![1, 2, 3]; // 读取元素 let third: &i32 = &v[2]; // 索引,越界 panic match v.get(2) { // 返回 Option<&T> Some(third) => println!("third element is {}", third), None => println!("no third element"), } // 遍历 for i in &v { println!("{}", i); } for i in &mut v { *i += 50; // 解引用修改 }字符串
Rust 的字符串是String(可增长、可变、拥有所有权)和&str(字符串切片)。String内部使用 UTF-8 编码。
rust
let mut s = String::new(); let data = "initial contents"; let s = data.to_string(); // 或 "initial contents".to_string() let s = String::from("initial contents"); // 更新 s.push_str("bar"); // 追加字符串切片 s.push('l'); // 追加字符 let s1 = String::from("Hello, "); let s2 = String::from("world!"); let s3 = s1 + &s2; // s1 被移动,不能再使用 // 格式化宏 let s = format!("{}-{}", s1, s2); // 不支持索引访问,因为 UTF-8 编码。需使用 chars() 或 bytes() for c in "你好".chars() { println!("{}", c); // 你 好 } for b in "你好".bytes() { println!("{}", b); // 输出字节 }HashMap
HashMap<K, V>存储键值对。
rust
use std::collections::HashMap; let mut scores = HashMap::new(); scores.insert(String::from("Blue"), 10); scores.insert(String::from("Yellow"), 50); // 通过 collect 从元组向量创建 let teams = vec![String::from("Blue"), String::from("Yellow")]; let initial_scores = vec![10, 50]; let scores: HashMap<_, _> = teams.iter().zip(initial_scores.iter()).collect(); // 访问 let score = scores.get("Blue"); // 返回 Option<&V> // 遍历 for (key, value) in &scores { println!("{}: {}", key, value); } // 更新:insert 会覆盖;entry 处理不存在时插入 scores.entry(String::from("Blue")).or_insert(50); // 仅当不存在时插入错误处理
Rust 将错误分为两类:可恢复错误(如文件未找到)和不可恢复错误(如数组越界)。
panic! 不可恢复错误
当程序遇到无法处理的错误时,可以调用panic!宏,程序会打印错误信息、展开并清理栈(或直接中止)。
rust
panic!("crash and burn");可以通过设置环境变量RUST_BACKTRACE=1获取回溯信息。
Result 可恢复错误
Result枚举定义如下:
rust
enum Result<T, E> { Ok(T), Err(E), }常用于可能失败的操作:
rust
use std::fs::File; use std::io::ErrorKind; let f = File::open("hello.txt"); let f = match f { Ok(file) => file, Err(error) => match error.kind() { ErrorKind::NotFound => match File::create("hello.txt") { Ok(fc) => fc, Err(e) => panic!("Problem creating the file: {:?}", e), }, other_error => panic!("Problem opening the file: {:?}", other_error), }, };传播错误
将错误返回给调用者处理:
rust
use std::io; use std::io::Read; use std::fs::File; fn read_username_from_file() -> Result<String, io::Error> { let f = File::open("hello.txt"); let mut f = match f { Ok(file) => file, Err(e) => return Err(e), }; let mut s = String::new(); match f.read_to_string(&mut s) { Ok(_) => Ok(s), Err(e) => Err(e), } }使用?运算符简化:
rust
fn read_username_from_file() -> Result<String, io::Error> { let mut f = File::open("hello.txt")?; let mut s = String::new(); f.read_to_string(&mut s)?; Ok(s) }?会从Result中取出值,若为Err则提前返回。也可链式调用:
rust
fn read_username_from_file() -> Result<String, io::Error> { let mut s = String::new(); File::open("hello.txt")?.read_to_string(&mut s)?; Ok(s) }?还可以用于Option类型,若为None则提前返回None。
何时使用 panic
在示例、原型代码和测试中,可以使用panic!。在能确保不会出现Err的情况下也可以使用unwrap或expect。对于库代码,应该将错误返回给调用者。
泛型、trait 与生命周期
泛型函数和结构体
泛型允许定义适用于多种类型的函数或结构体:
rust
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T { let mut largest = list[0]; for &item in list { if item > largest { largest = item; } } largest } struct Point<T> { x: T, y: T, } impl<T> Point<T> { fn x(&self) -> &T { &self.x } }trait 定义与实现
trait 类似于接口,定义共享行为:
rust
pub trait Summary { fn summarize(&self) -> String; } pub struct NewsArticle { pub headline: String, pub location: String, pub author: String, pub content: String, } impl Summary for NewsArticle { fn summarize(&self) -> String { format!("{}, by {} ({})", self.headline, self.author, self.location) } }trait 作为参数
可以接受实现了某个 trait 的类型:
rust
pub fn notify(item: &impl Summary) { println!("Breaking news! {}", item.summarize()); }等价于 trait bound 语法:
rust
pub fn notify<T: Summary>(item: &T) { println!("Breaking news! {}", item.summarize()); }多个 trait 使用+:
rust
pub fn notify(item: &(impl Summary + Display)) {}复杂 bound 可使用where子句:
rust
fn some_function<T, U>(t: &T, u: &U) -> i32 where T: Display + Clone, U: Clone + Debug, { // ... }trait 也可以作为返回类型(但只能返回单一类型):
rust
fn returns_summarizable() -> impl Summary { NewsArticle { ... } }生命周期注解
生命周期确保引用始终有效。它们也是泛型的一种。
rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }这里'a是生命周期参数,表示返回的引用与两个参数中的较小生命周期一致。
结构体中也可包含引用,需要生命周期注解:
rust
struct Excerpt<'a> { part: &'a str, }生命周期省略规则
在某些情况下,Rust 编译器可以自动推断生命周期,这称为生命周期省略规则。例如:
每个引用参数都有自己的生命周期参数。
如果只有一个输入生命周期,它会被赋给所有输出生命周期。
如果有多个输入生命周期,但其中一个为
&self或&mut self,则self的生命周期赋给所有输出。
编写测试
测试函数
在 Rust 中,测试是通过#[test]属性标记的函数。运行cargo test会执行所有测试函数。
rust
#[cfg(test)] mod tests { #[test] fn it_works() { assert_eq!(2 + 2, 4); } #[test] fn another() { panic!("Make this test fail"); } }断言
assert!(expression):若表达式为 false 则 panic。assert_eq!(left, right)和assert_ne!(left, right):比较两个值,若不等/等则 panic。可添加自定义消息:
assert_eq!(a, b, "a 不等于 b, a={}, b={}", a, b)
测试组织
单元测试通常放在与代码相同的文件中,使用#[cfg(test)]条件编译。集成测试放在tests目录下,每个文件被视为单独的 crate,只能测试公共 API。
智能指针
智能指针是实现了Deref和Droptrait 的数据结构,它们拥有数据的所有权并提供额外功能。
Box<T>
Box<T>用于在堆上分配值,最简单直接。适合递归类型、在需要特定大小的地方使用。
rust
let b = Box::new(5); println!("b = {}", b); // 可以像普通引用一样使用 // 递归类型 Cons list enum List { Cons(i32, Box<List>), Nil, }Deref 和 Drop trait
Deref允许智能指针像普通引用一样解引用(*v)。Rust 会自动解引用以匹配类型。Drop允许自定义值离开作用域时的清理代码。
Rc<T>、RefCell<T> 和内部可变性
Rc<T>是引用计数指针,允许多个所有者(只读)。RefCell<T>在运行时检查借用规则,实现内部可变性,即即使RefCell<T>本身不可变,也能修改其内部值。
rust
use std::rc::Rc; use std::cell::RefCell; let value = Rc::new(RefCell::new(5)); let a = Rc::clone(&value); let b = Rc::clone(&value); *value.borrow_mut() += 10; // 修改内部值 println!("{:?}", value); // RefCell { value: 15 }循环引用
Rc<T>和RefCell<T>可能导致循环引用和内存泄漏。可以使用Weak<T>打破循环。
并发编程
线程
使用thread::spawn创建新线程:
rust
use std::thread; use std::time::Duration; let handle = 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)); } handle.join().unwrap(); // 等待子线程结束消息传递
使用通道(channel)在线程间通信:
rust
use std::sync::mpsc; use std::thread; let (tx, rx) = mpsc::channel(); thread::spawn(move || { let val = String::from("hi"); tx.send(val).unwrap(); }); let received = rx.recv().unwrap(); // 阻塞接收 println!("Got: {}", received);共享状态
使用互斥器Mutex<T>和原子引用计数Arc<T>实现线程间共享数据:
rust
use std::sync::{Arc, Mutex}; use std::thread; 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());Send 和 Sync trait
Send允许在线程间转移所有权。几乎所有 Rust 类型都是Send,但Rc<T>不是。Sync允许多线程同时访问。实现了Sync的类型,其引用&T是Send。基本类型都是Sync,但RefCell<T>不是,Mutex<T>是。
面向对象特性
trait 对象
trait 对象允许动态分发,实现类似多态的效果。需要使用dyn关键字。
rust
pub trait Draw { fn draw(&self); } pub struct Screen { pub components: Vec<Box<dyn Draw>>, } impl Screen { pub fn run(&self) { for component in self.components.iter() { component.draw(); } } }这里Box<dyn Draw>是一个 trait 对象,它可以指向任何实现了Draw的类型。
实现多态
可以为不同类型实现同一个 trait,然后通过 trait 对象统一调用:
rust
struct Button { width: u32, height: u32, label: String, } impl Draw for Button { fn draw(&self) { // 绘制按钮 } } struct SelectBox { width: u32, height: u32, options: Vec<String>, } impl Draw for SelectBox { fn draw(&self) { // 绘制选择框 } } fn main() { let screen = Screen { components: vec![ Box::new(SelectBox { ... }), Box::new(Button { ... }), ], }; screen.run(); }模式与匹配进阶
模式语法
模式可以出现在match、if let、while let、for循环、let语句、函数参数等位置。常见模式:
字面值:
1、"abc"变量名:
x(会绑定值)通配符
_范围
..=:1..=5解构结构体、元组、枚举
引用模式:
&v、ref v、ref mut v匹配守卫:在模式后添加
if条件
示例:
rust
let x = 5; match x { 1 => println!("one"), 2 | 3 | 5 => println!("two, three or five"), 6..=10 => println!("six to ten"), _ => println!("something else"), } let p = Point { x: 0, y: 7 }; match p { Point { x, y: 0 } => println!("on the x axis at {}", x), Point { x: 0, y } => println!("on the y axis at {}", y), Point { x, y } => println!("on neither axis: ({}, {})", x, y), }高级特性
不安全 Rust
不安全 Rust 允许你进行一些编译器无法保证安全的操作,如解引用裸指针、调用不安全函数、访问可变静态变量、实现不安全 trait。使用unsafe关键字标记代码块或函数。
rust
unsafe fn dangerous() {} let mut num = 5; let r1 = &num as *const i32; // 裸指针 unsafe { dangerous(); println!("r1 is: {}", *r1); // 解引用裸指针 }应尽量将不安全代码封装在安全抽象中。
高级 trait
关联类型:在 trait 中指定一个类型占位符。
rust
pub trait Iterator { type Item; fn next(&mut self) -> Option<Self::Item>; }默认泛型参数和运算符重载:
std::ops中的 trait 如Add支持重载+。完全限定语法:当多个 trait 有同名方法时,使用
<Type as Trait>::method(...)。
高级类型
类型别名:
type Kilometers = i32;动态大小类型和
Sizedtrait:如str是动态大小,只能通过&str使用。泛型默认是Sized,可以用?Sized放宽。
宏
宏分为声明宏和过程宏。声明宏使用macro_rules!定义,类似于模式匹配生成代码。过程宏接收代码作为输入,操作后输出代码,有三种:自定义#[derive]宏、类属性宏、类函数宏。
示例声明宏:
rust
macro_rules! say_hello { () => { println!("Hello!"); }; } say_hello!();异步编程
Rust 的异步编程基于Futuretrait 和async/.await语法。
async/.await
使用async fn定义异步函数,返回一个实现了Future的类型。在 async 函数内部可以使用.await等待另一个 Future 完成。
rust
async fn hello() { println!("Hello, async!"); } #[tokio::main] async fn main() { hello().await; }Futures
Future代表一个可能尚未完成的计算。Rust 的 Future 是惰性的,只有被轮询才会推进。通常需要一个执行器(executor)来驱动 Future。
异步运行时
常用的异步运行时是 Tokio 和 async-std。它们提供了 I/O、计时器、任务生成等功能。
使用 Tokio:
rust
use tokio::time; #[tokio::main] async fn main() { let handle = tokio::spawn(async { time::sleep(time::Duration::from_secs(1)).await; println!("task finished"); }); handle.await.unwrap(); }项目结构与工作空间
工作空间
工作空间是一系列共享同一个 Cargo.lock 和输出目录的包。在根目录创建Cargo.toml:
toml
[workspace] members = [ "adder", "add-one", ]
每个成员都是一个独立的 crate。成员之间可以通过路径依赖。
依赖管理
Cargo.toml
在Cargo.toml的[dependencies]部分添加依赖:
toml
[dependencies] rand = "0.8.5"
Cargo 会从 crates.io 下载相应版本。
版本指定
版本号遵循语义化版本。可以使用^、~、*、<、>等指定范围。例如rand = "0.8"相当于^0.8,允许兼容 0.8.x 但不包括 0.9.0。
依赖覆盖
可以使用[patch]或[replace]覆盖依赖来源,例如使用本地路径或 git 仓库。
构建脚本与自定义构建
如果需要在编译前执行一些代码(如生成代码、链接系统库),可以在项目根目录创建build.rs构建脚本。它会在编译 crate 前被编译并执行。
rust
// build.rs fn main() { println!("cargo:rustc-link-search=native=/usr/local/lib"); }构建脚本可以通过println!输出指令给 Cargo。
文档编写
文档注释
文档注释使用///或//!。支持 Markdown,并会生成 HTML 文档。
rust
/// Adds one to the number given. /// /// # Examples /// /// ``` /// let arg = 5; /// let answer = my_crate::add_one(arg); /// assert_eq!(6, answer); /// ``` pub fn add_one(x: i32) -> i32 { x + 1 }# Examples部分的代码会作为测试运行。
生成文档
运行cargo doc会在target/doc生成文档。cargo doc --open会在浏览器打开。
发布到 crates.io
创建账号
访问 crates.io,使用 GitHub 账号登录,然后在本地使用cargo login <API_TOKEN>登录。
打包
在发布前,确保Cargo.toml中有必要的元数据:
toml
[package] name = "my_crate" version = "0.1.0" authors = ["Your Name <you@example.com>"] edition = "2021" description = "A short description of my crate" license = "MIT OR Apache-2.0" repository = "https://github.com/yourname/my_crate"
运行cargo publish --dry-run检查。然后执行cargo publish发布。
发布与版本管理
发布后无法删除,但可以发布新版本。修改版本号后再运行cargo publish。
FFI 与外部语言接口
调用 C 函数
使用extern块声明外部函数,并链接到相应库。
rust
extern "C" { fn abs(input: i32) -> i32; } fn main() { unsafe { println!("Absolute value of -3 according to C: {}", abs(-3)); } }需要在构建脚本或通过links键指定库。
从 C 调用 Rust
将 Rust 函数标记为#[no_mangle]并使用extern "C"导出,编译为静态库或动态库。
rust
#[no_mangle] pub extern "C" fn call_from_c() { println!("Just called a Rust function from C!"); }在Cargo.toml中设置 crate 类型:
toml
[lib] crate-type = ["staticlib", "cdylib"]
C 代码可以链接生成的库并调用函数。
常用开发工具
rustfmt 和 clippy
rustfmt自动格式化代码:cargo fmtclippy提供更多 lint 检查:cargo clippy
rust-analyzer
rust-analyzer 是 Rust 的语言服务器,提供代码补全、跳转定义等功能。主流编辑器(VS Code、Vim/Neovim、Emacs)都支持。
生态与常用库
Rust 拥有丰富且活跃的生态系统。以下是一些常用库:
Web 框架:Actix-web、Rocket、Warp
异步运行时:Tokio、async-std
序列化:serde(与 JSON、YAML、TOML 等配合)
HTTP 客户端:reqwest
数据库驱动:sqlx(异步)、diesel(ORM)
日志:log、env_logger
命令行解析:clap
错误处理:anyhow、thiserror
测试:proptest、quickcheck
嵌入式:embedded-hal
WebAssembly:wasm-bindgen