文章目录
-
- [变量绑定(Variable bindings)](#变量绑定(Variable bindings))
-
- [`let` 关键字](#
let关键字) - [类型标注(Type annotation)](#类型标注(Type annotation))
- 未初始化变量
- [丢弃值(Throwing values away)](#丢弃值(Throwing values away))
- 变量遮蔽(Shadowing)
- [`let` 关键字](#
- 元组(Tuples)
- 语句(Statements)
- 函数(Functions)
- 代码块(Blocks)
-
- [块也是表达式(Blocks are expressions)](#块也是表达式(Blocks are expressions))
- 隐式返回
- [一切皆表达式(Everything is an expression)](#一切皆表达式(Everything is an expression))
- [Field access and method calling(字段访问与方法调用)](#Field access and method calling(字段访问与方法调用))
- [Modules, `use` syntax(模块与 `use` 语法)](#Modules,
usesyntax(模块与use语法)) -
- [Types are namespaces too(类型也是命名空间)](#Types are namespaces too(类型也是命名空间))
- [The libstd prelude(标准库 prelude)](#The libstd prelude(标准库 prelude))
- Ownership(所有权)
- Borrowing(借用)
-
- [Borrowing rules(借用规则)](#Borrowing rules(借用规则))
- [String vs &str(String 与 &str)](#String vs &str(String 与 &str))
- Structs(结构体)
-
- [具名字段结构体(Named fields)](#具名字段结构体(Named fields))
- [元组结构体(Tuple structs)](#元组结构体(Tuple structs))
- [类单元结构体(Unit structs)](#类单元结构体(Unit structs))
- [更新语法(Struct update syntax)](#更新语法(Struct update syntax))
- Enums(枚举)
-
- 更复杂的枚举
- [枚举与模式匹配(Enums + match)](#枚举与模式匹配(Enums + match))
- [if let 与 while let](#if let 与 while let)
- [Error handling(错误处理)](#Error handling(错误处理))
-
- [`?` 运算符(The question mark operator)](#
?运算符(The question mark operator))
- [`?` 运算符(The question mark operator)](#
- [The `Option` type](#The
Optiontype) - Traits(特征)
-
- [Orphan rules(孤儿规则)](#Orphan rules(孤儿规则))
- [`Self` 类型](#
Self类型) - [Marker traits(标记特征)](#Marker traits(标记特征))
- [Trait 方法接收者](#Trait 方法接收者)
- [Deriving traits(派生特征)](#Deriving traits(派生特征))
- Generics(泛型)
-
- 泛型函数
- [类型参数约束(Trait bounds)](#类型参数约束(Trait bounds))
- Lifetimes(生命周期)
-
- [省略规则(Lifetime elision)](#省略规则(Lifetime elision))
- 结尾:继续前进吧!
要熟练掌握一门编程语言,最好的方法就是大量阅读它的代码。
但如果你连代码里的关键字和符号是什么意思都不知道,又怎么读得下去呢?
这篇文章不会只深挖一两个概念,而是尽可能展示大量 Rust 代码片段,并解释其中出现的关键词和符号。
准备好了吗?开始!
变量绑定(Variable bindings)
let 关键字
let 用于引入一个变量绑定(binding):
rust
let x; // 声明 "x"
x = 42; // 把 42 赋值给 "x"
也可以一行完成:
rust
let x = 42;
类型标注(Type annotation)
可以用 : 显式指定类型,这就是类型标注:
rust
let x: i32; // i32 是有符号 32 位整数
x = 42;
一行写法:
rust
let x: i32 = 42;
Rust 支持多种整数类型:i8、i16、i32、i64、i128 以及对应的无符号版本 u8、u16、u32、u64、u128。
2024 提示:类型推断在 2024 Edition 下依然强大,多数情况下不需要显式标注。
未初始化变量
如果你先声明变量,后面再赋值,编译器会严格检查:在使用前必须完成初始化。
rust
let x;
foobar(x); // 错误:使用可能未初始化的变量
x = 42;
但下面这样完全合法:
rust
let x;
x = 42;
foobar(x); // 类型会从此处推断
丢弃值(Throwing values away)
下划线 _ 是一个特殊的"无名"标识符,用于丢弃某个值:
rust
// 这什么都不做,因为 42 是常量
let _ = 42;
// 调用函数但丢弃返回值
let _ = get_thing();
以 _ 开头的名称仍是正常变量,只是编译器不会警告"未使用":
rust
// 我们以后可能会用到,先消除未使用警告
let _x = 42;
变量遮蔽(Shadowing)
允许用同一个名称引入新的绑定,这就是 shadowing:
rust
let x = 13;
let x = x + 3; // 现在 x 是 16
// 此后的 x 指代第二个绑定,第一个 x 仍然存在(离开作用域时会被 drop),但无法再访问
元组(Tuples)
Rust 的元组是"固定长度、元素类型可以不同的集合"。
rust
let pair = ('a', 17);
println!("{:?}", pair.0); // 'a'
println!("{:?}", pair.1); // 17
显式标注类型:
rust
let pair: (char, i32) = ('a', 17);
解构元组(Destructuring)
赋值时可以直接解构:
rust
let (some_char, some_int) = ('a', 17);
// 现在 some_char 是 'a',some_int 是 17
函数返回元组时特别实用:
rust
let (left, right) = slice.split_at(middle);
可以用 _ 丢弃部分值:
rust
let (_, right) = slice.split_at(middle);
语句(Statements)
分号 ; 表示语句结束:
rust
let x = 3;
let y = 5;
let z = y + x;
语句可以跨多行:
rust
let x = vec![1, 2, 3, 4, 5, 6, 7, 8]
.iter()
.map(|x| x + 3)
.fold(0, |x, y| x + y);
(后面会解释这些方法的具体含义)
函数(Functions)
fn 关键字声明函数。
无返回值(void)函数:
rust
fn greet() {
println!("Hi there!");
}
带返回值的函数,返回类型用 -> 表示:
rust
fn fair_dice_roll() -> i32 {
4
}
代码块(Blocks)
一对大括号 {} 形成一个块,有自己的作用域:
rust
fn main() {
let x = "out";
{
let x = "in"; // 这是另一个 x
println!("{}", x); // 打印 "in"
}
println!("{}", x); // 打印 "out"
}
块也是表达式(Blocks are expressions)
块可以求值产生一个值:
rust
// 这两种写法等价
let x = 42;
let x = { 42 };
块内可以有多个语句,最后一个表达式(不带分号)是块的返回值(尾表达式):
rust
let x = {
let y = 1;
let z = 2;
y + z // 没有分号 → 这是整个块的值
};
隐式返回
正因为块是表达式,函数末尾省略分号就相当于返回该值(推荐写法):
rust
fn fair_dice_roll() -> i32 {
4 // 隐式返回
}
// 等价于显式 return(较少使用)
fn fair_dice_roll() -> i32 {
return 4;
}
一切皆表达式(Everything is an expression)
if 是表达式:
rust
fn fair_dice_roll() -> i32 {
if feeling_lucky {
6
} else {
4
}
}
match 也是表达式:
rust
fn fair_dice_roll() -> i32 {
match feeling_lucky {
true => 6,
false => 4,
}
}
2024 Edition 补充 :if let 的临时值作用域在 2024 中有所收窄(临时值更早 drop),这让某些代码更安全,但对基础使用几乎无影响。
Field access and method calling(字段访问与方法调用)
点号 . 通常用于访问某个值的字段:
rust
let a = (10, 20);
a.0; // 这就是 10
let amos = get_some_struct();
amos.nickname; // 这就是 "fasterthanlime"
或者在某个值上调用方法:
rust
let nick = "fasterthanlime";
nick.len(); // 这就是 14
Modules, use syntax(模块与 use 语法)
双冒号 :: 用于操作命名空间(namespaces)。
示例中,std 是一个 crate (≈ 库),cmp 是一个 module (≈ 源文件),min 是一个函数:
rust
let least = std::cmp::min(3, 8); // 这就是 3
use 指令可以把其他命名空间中的名称"引入当前作用域":
rust
use std::cmp::min;
let least = min(7, 1); // 这就是 1
在 use 指令中,花括号有特殊含义(称为 globs):
rust
// 以下几种写法都有效
use std::cmp::min;
use std::cmp::max;
use std::cmp::{min, max};
use std::{cmp::min, cmp::max};
通配符 * 可以引入某个命名空间中的所有符号(谨慎使用):
rust
// 这会引入 min、max 以及很多其他东西
use std::cmp::*;
Types are namespaces too(类型也是命名空间)
类型本身也是命名空间,方法可以当作普通函数调用:
rust
let x = "amos".len(); // 4
let x = str::len("amos"); // 同样是 4
The libstd prelude(标准库 prelude)
str 是基本类型,但许多非基本类型默认也在作用域中。
rust
// Vec 是一个普通结构体,不是基本类型
let v = Vec::new();
// 这段代码完全等价,只是用了完整路径
let v = std::vec::Vec::new();
这是因为 Rust 在每个模块的开头自动插入了:
rust
use std::prelude::v1::*;
(它重新导出了大量常用符号,例如 Vec、String、Option、Result 等)。
2024 Edition 提示:prelude 内容略有扩展(包含更多异步相关 trait),但对本文的基础教程没有影响。
Ownership(所有权)
Rust 的核心理念之一是所有权(ownership)。
每个值都有一个所有者 (owner)。当所有者离开作用域时,该值就会被自动 drop(销毁)。
rust
fn main() {
let s = String::from("hello"); // s 拥有这个 String
// 在这里使用 s ...
} // s 离开作用域,String 的内存被自动释放
当你把值移动(move)给另一个变量时,原来的所有者就不再有效:
rust
let s1 = String::from("hello");
let s2 = s1; // s1 的所有权移动给了 s2
// println!("{}", s1); // 错误!s1 已经失效
println!("{}", s2); // 正确
2024 提示:移动语义和 drop 顺序在 2024 Edition 中保持稳定,临时值 drop 时机有时更早(更安全)。
Borrowing(借用)
有时你只想临时访问 一个值,而不想拿走它的所有权,这时就需要借用(borrowing)。
借用使用引用(reference)语法:
&T------ 不可变借用(immutable borrow)&mut T------ 可变借用(mutable borrow)
rust
fn calculate_length(s: &String) -> usize {
s.len()
}
fn main() {
let s1 = String::from("hello");
let len = calculate_length(&s1); // 借用 s1,不转移所有权
println!("The length of '{}' is {}.", s1, len);
}
Borrowing rules(借用规则)
Rust 有严格的借用规则(借用检查器 borrow checker 负责检查):
- 在任意时刻,你要么拥有一个可变引用 (
&mut T),要么拥有任意多个不可变引用 (&T),但不能同时拥有两者。 - 引用必须始终有效(不能悬垂指针)。
rust
let mut s = String::from("hello");
let r1 = &s; // 不可变借用
let r2 = &s; // 另一个不可变借用,没问题
// let r3 = &mut s; // 错误!已有不可变借用时不能创建可变借用
println!("{}, {}", r1, r2);
可变借用示例:
rust
fn change(s: &mut String) {
s.push_str(", world");
}
fn main() {
let mut s = String::from("hello");
change(&mut s);
println!("{}", s); // "hello, world"
}
String vs &str(String 与 &str)
String------ 堆上分配的可增长字符串,拥有所有权。&str------ 字符串切片(string slice),只是对 UTF-8 数据的引用,通常没有所有权。
rust
let s = String::from("hello"); // String,拥有数据
let slice: &str = &s[0..2]; // &str,借用 s 的部分数据
let literal = "world"; // &str,指向静态字符串数据
函数参数中通常优先使用 &str(更灵活):
rust
fn print_str(s: &str) {
println!("{}", s);
}
// 可以传入 &String 或 &str
print_str(&s);
print_str(literal);
2024 提示 :字符串处理在 2024 中依然推荐使用 &str 作为参数类型,结合改进的 lifetime elision 规则,代码会更简洁。
Structs(结构体)
结构体(struct)是自定义数据类型,用 struct 关键字声明。
具名字段结构体(Named fields)
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
println!("{} <{}>", user1.username, user1.email);
元组结构体(Tuple structs)
没有字段名,只有类型:
rust
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
访问时仍用 .0、.1 等:
rust
println!("Black is ({}, {}, {})", black.0, black.1, black.2);
类单元结构体(Unit structs)
没有任何字段,常用于标记类型:
rust
struct AlwaysEqual;
let subject = AlwaysEqual;
更新语法(Struct update syntax)
可以用 .. 从另一个结构体复制剩余字段(Rust 2024 中依然有效):
rust
let user2 = User {
email: String::from("another@example.com"),
..user1 // 复制 user1 中剩余字段(username、sign_in_count、active)
};
注意:user1 的 username 等字段会发生移动 (move),因此 user1 之后不能再被使用(除非是 Copy 类型)。
Enums(枚举)
枚举(enum)是 Rust 中非常强大的特性,可以表示一个值有多种可能的"变体"(variants)。
rust
enum IpAddr {
V4(String),
V6(String),
}
使用:
rust
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
更复杂的枚举
枚举的每个变体可以携带不同类型的数据,甚至是结构体:
rust
enum Message {
Quit, // 没有关联数据
Move { x: i32, y: i32 }, // 具名字段
Write(String), // 单个字符串
ChangeColor(i32, i32, i32), // 元组
}
枚举与模式匹配(Enums + match)
match 是 Rust 中处理枚举的最常用方式,它强制你处理所有可能的情况(穷尽性检查):
rust
fn main() {
let msg = Message::ChangeColor(0, 160, 255);
match msg {
Message::Quit => println!("The Quit variant has no data!"),
Message::Move { x, y } => println!("Move to x={}, y={}", x, y),
Message::Write(text) => println!("Text message: {}", text),
Message::ChangeColor(r, g, b) => println!("Change color to R={}, G={}, B={}", r, g, b),
}
}
2024 提示 :Rust 2024 的模式匹配更加灵活,支持 if let 链和更智能的临时值处理,但 match 的穷尽性检查仍然是核心安全特性。
if let 与 while let
有时你只关心枚举的某一个变体,这时 if let 非常方便:
rust
if let Message::Write(text) = msg {
println!("Text message: {}", text);
}
while let 用于循环处理:
rust
let mut stack = vec![1, 2, 3];
while let Some(top) = stack.pop() {
println!("{}", top);
}
Error handling(错误处理)
Rust 没有异常(exceptions)。错误用 Result<T, E> 类型处理。
rust
enum Result<T, E> {
Ok(T),
Err(E),
}
示例函数:
rust
fn get_user(id: u32) -> Result<User, String> {
if id == 1 {
Ok(User { /* ... */ })
} else {
Err(String::from("User not found"))
}
}
? 运算符(The question mark operator)
? 是早期返回错误的语法糖。在返回 Result 的函数中使用:
rust
fn get_user_name(id: u32) -> Result<String, String> {
let user = get_user(id)?;
Ok(user.username)
}
如果 get_user 返回 Err,则整个函数立即返回该错误;否则继续执行。
2024 提示 :? 运算符在 2024 Edition 中对 Option 和 Result 的支持更加一致,结合改进的 From trait 转换,错误处理代码更简洁。
The Option type
Option<T> 用于表示"可能有值,也可能没有值":
rust
enum Option<T> {
Some(T),
None,
}
示例:
rust
let some_number: Option<i32> = Some(42);
let absent_number: Option<i32> = None;
使用 if let 处理 Option:
rust
if let Some(number) = some_number {
println!("We have a number: {}", number);
}
? 也可以用于返回 Option 的函数中。
Traits(特征)
Traits 描述多个类型可以共有的行为(类似其他语言的接口):
rust
trait Signed {
fn is_strictly_negative(self) -> bool;
}
Orphan rules(孤儿规则)
Rust 允许你实现:
- 你的 trait 在任意类型上
- 任意 trait 在你的类型上
但不允许实现外部 trait 在外部类型上(防止冲突)。
以下是在我们自己的类型上实现 trait:
rust
struct Number {
odd: bool,
value: i32,
}
impl Signed for Number {
fn is_strictly_negative(self) -> bool {
self.value < 0
}
}
fn main() {
let n = Number { odd: false, value: -44 };
println!("{}", n.is_strictly_negative()); // 打印 "true"
}
在外部类型(例如原生 i32)上实现我们的 trait:
rust
impl Signed for i32 {
fn is_strictly_negative(self) -> bool {
self < 0
}
}
fn main() {
let n: i32 = -44;
println!("{}", n.is_strictly_negative()); // 打印 "true"
}
实现外部 trait 在我们自己的类型上(例如重载运算符):
rust
impl std::ops::Neg for Number {
type Output = Number;
fn neg(self) -> Number {
Number {
value: -self.value,
odd: self.odd,
}
}
}
fn main() {
let n = Number { odd: true, value: 987 };
let m = -n; // 因为实现了 Neg,所以可以这样写
println!("{}", m.value); // 打印 "-987"
}
Self 类型
在 impl 块中,Self 指代当前正在实现的类型:
rust
impl std::ops::Neg for Number {
type Output = Self;
fn neg(self) -> Self {
Self {
value: -self.value,
odd: self.odd,
}
}
}
Marker traits(标记特征)
有些 trait 不提供方法,只作为"标记"------表示该类型支持某种行为。例如 Copy:
rust
fn main() {
let a: i32 = 15;
let b = a; // i32 是 Copy,所以这里是复制
let c = a; // 再次复制
}
Number 默认不是 Copy,所以移动后不能再使用:
rust
fn main() {
let n = Number { odd: true, value: 51 };
let m = n; // 移动
// let o = n; // 错误:use of moved value
}
传递不可变引用则没有问题:
rust
fn print_number(n: &Number) {
println!(
"{} number {}",
if n.odd { "odd" } else { "even" },
n.value
);
}
fn main() {
let n = Number { odd: true, value: 51 };
print_number(&n);
print_number(&n); // 可以多次借用
}
可变引用需要 mut:
rust
fn invert(n: &mut Number) {
n.value = -n.value;
}
fn main() {
let mut n = Number { odd: true, value: 51 };
print_number(&n);
invert(&mut n);
print_number(&n);
}
Trait 方法接收者
trait 方法可以接收 self、&self 或 &mut self:
rust
impl std::clone::Clone for Number {
fn clone(&self) -> Self {
Self { ..*self }
}
}
调用时会隐式借用:
rust
let m = n.clone(); // 等价于下面这行
let m = std::clone::Clone::clone(&n);
Copy 是 marker trait(无方法),但要求先实现 Clone:
rust
#[derive(Clone)] // 推荐使用 derive
struct Number { /* ... */ }
impl std::marker::Copy for Number {}
现在 Number 可以复制而非移动:
rust
let m = n; // 复制
let o = n; // 再次复制
Deriving traits(派生特征)
常用 trait 可以使用 #[derive] 自动实现:
rust
#[derive(Clone, Copy)]
struct Number {
odd: bool,
value: i32,
}
2024 提示 :derive 在 2024 Edition 中依然是推荐写法,许多标准 trait(如 Debug、PartialEq、Eq)都支持自动派生。
Generics(泛型)
泛型函数
函数可以是泛型的:
rust
fn foobar<T>(arg: T) {
// 对 arg 做一些操作
}
可以有多个类型参数:
rust
fn foobar<L, R>(left: L, right: R) {
// 使用 left 和 right
}
类型参数约束(Trait bounds)
通常需要为泛型参数添加约束:
rust
use std::fmt::Display;
fn print<T: Display>(value: T) {
println!("value = {}", value);
}
或者使用 where 子句(更清晰,尤其约束复杂时):
rust
fn print<T>(value: T) where T: Display {
println!("value = {}", value);
}
多个约束:
rust
use std::fmt::Debug;
fn compare<T>(left: T, right: T) where T: Debug + PartialEq {
println!(
"{:?} {} {:?}",
left,
if left == right { "==" } else { "!=" },
right
);
}
Lifetimes(生命周期)
生命周期是 Rust 借用检查器的核心概念,用于确保引用不会超过它指向的数据的存活时间。
最常见的生命周期注解是 'a:
rust
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
省略规则(Lifetime elision)
在很多简单情况下,Rust 会自动推断生命周期(elision):
rust
// 这两个函数签名在大多数情况下等价
fn first_word(s: &str) -> &str;
fn first_word<'a>(s: &'a str) -> &'a str;
Rust 2024 Edition 重要更新 :Return Position impl Trait(RPIT)现在默认捕获所有 in-scope 的 lifetime 参数(与类型参数一致)。这让返回 impl Trait 的函数在涉及引用的场景下更安全、更直观。
示例(2024 风格):
rust
// 在 Rust 2024 中,这通常能自动捕获需要的 lifetime
fn make_ref<'a>(x: &'a i32) -> impl std::fmt::Display + 'a {
x
}
如果需要精确控制捕获哪些参数,可以使用 use<..> 语法(所有 Edition 均支持):
rust
fn foo<'a, T>(x: &'a T) -> impl Trait + use<'a, T> { ... }
这让代码在 2024 Edition 下更简洁,同时避免了旧版中常见的 "Captures trick" 或 '_ hack。
结尾:继续前进吧!
恭喜你!通过这篇文章,你已经了解了 Rust 中最核心的概念:变量绑定、所有权、借用、结构体、枚举、模式匹配、错误处理、trait、泛型和生命周期。
这些知识足以让你开始阅读真实的 Rust 代码,并逐步编写自己的程序。
2024 Edition 进阶建议:
- 使用
cargo new创建项目,并设置edition = "2024"。 - 多阅读标准库文档和高质量 crate(如
tokio、serde)。 - 练习编写带 trait bounds 的泛型函数和返回
impl Trait的 API。 - 当遇到 borrow checker 错误时,不要害怕------这是 Rust 在保护你。
Rust 是一门注重安全和性能的语言,学习曲线虽然陡峭,但回报巨大。
继续写代码、多读源码,你会越来越喜欢它。