精读《Rust编程之道》:吃透语言精要,彻底搞懂所有权与借用“好读书,不求甚解;每有会意,便欣然忘食。”学习Rust就像开启一场独特的编程探险。它强大、安全,但也以其陡峭的学习曲线而闻名,尤其是其独特的“所有权”和“借用”机制,常常让初学者感到困惑。这篇文章是我在精读《Rust编程之道》第
“好读书,不求甚解;每有会意,便欣然忘食。”
学习 Rust 就像开启一场独特的编程探险。它强大、安全,但也以其陡峭的学习曲线而闻名,尤其是其独特的“所有权”和“借用”机制,常常让初学者感到困惑。
这篇文章是我在精读《Rust编程之道》第二章“语言精要”时,整理的一份核心学习笔记。它并非对书中内容的简单复刻,而是力求将那些关键且抽象的概念——从语言的基本构成,到语句与表达式的哲学,再到所有权系统的精髓——梳理得清晰明了。
那么,希望这份“欣然忘食”的笔记,能成为你学习路上的得力助手,助你稳固地迈出掌握 Rust 的第一步。
本文是经典书籍《Rust编程之道》第二章“语言精要”的深度学习笔记。文章系统性地梳理了 Rust 语言的五大核心组成部分(语言规范、编译器 rustc、核心库、标准库、包管理器 Cargo)。在此基础上,深入辨析了 Rust 中“一切皆表达式”的设计哲学,阐明了语句与表达式的本质区别。核心部分,笔记详细剖析了变量绑定、可变性 (mut)、位置表达式(左值)与值表达式(右值)等概念,并最终聚焦于 Rust 最具革命性的特性——所有权 (Ownership)、移动 (Move) 语义和借用 (Borrow) 机制。这篇笔记旨在为 Rust 学习者打下坚实的基础,清晰地解释那些初学时最容易混淆的关键概念。
好读书,不求甚解;每有会意,便欣然忘食。
Rust 语言规范主要由 Rust 语言参考(The Rust Reference)和 RFC 文档共同构成。
The Rust Reference:<https://doc.rust-lang.org/stable/reference/>
Rust 参考手册 中文版:<https://rustwiki.org/zh-CN/reference/>
Rust 规范文档:https://rustwiki.org/wiki/
Rust 官方文档中文教程:https://rustwiki.org/
Rust 是一门静态编译型语言。Rust官方的编译器叫 rustc,负责将 Rust 源代码编译为可执行文件或其他库文件(.a、.so、.lib、.dll 等)。
Rustc 有如下特点:
Rust 语言的语法由核心库和标准库共同提供。
Rust 核心库是标准库的基础。
可以通过在模块顶部引入 #![no_std]
来使用核心库。
核心库包含如下部分:
标准库包含的内容大概如下:
把按一定规则组织的多个 rs 文件编译后就得到一个包(crate)。
包是 Rust 代码的基本编译单元,也是程序员直接共享代码的基本单元。
Rust 社区的公开第三方包都集中在 crates.io 网站上面,它们的文档被自动发布到 docs.rs 网站上。
Rust 提供了非常方便的 包管理器 Cargo。
https://doc.rust-lang.org/cargo/
https://rustwiki.org/zh-CN/cargo/index.html
cargo new bin_crate
cargo new --lib lib_crate
cargo new 命令默认可以创建一个用于编写可执行二进制文件的项目。
cargo new 命令添加 --lib 参数,可以创建用于编写库的项目。
cargo build 对项目进行编译
cargo run 项目运行
Rust 中的语法可以分成两大类:语句(Statement)和表达式(Expression)。
语句是指要执行的一些操作和产生副作用的表达式。
表达式主要用于计算求值。
语句分为两种:声明语句(Declaration statement)和表达式语句(Expression statement)。
// extern crate std;
// use std::prelude::v1::*;
fn main() {
pub fn answer() -> () {
let a = 40;
let b = 2;
assert_eq!(sum(a, b), 42);
}
pub fn sum(a: i32, b: i32) -> i32 {
a + b
}
answer();
}
Rust 会为每个 crate 都自动引入标准库模块,除非使用 #![no_std]
或 #[no_std]
属性明确指定了不需要标准库。
关键字 fn 是 function 的缩写。
单元类型拥有唯一的值,就是它本身。
单元类型的概念来自 OCaml,它表示”没有什么特殊的价值“。
函数无返回值的函数默认不需要在函数签名中指定返回类型。
assert_eq! 是宏语句,是Rust提供的断言,允许判断给定的两个表达式求值结果是否相同。
Rust 编译器在解析代码的时候,如果碰到分号,就会继续往后面执行;
如果碰到语句,则执行语句;
如果碰到表达式,则会对表达式求值,如果分号后面什么都没有,就会补上单元值()。
当遇到函数的时候,会将函数体的花括号识别为块表达式(Block Expression)。
块表达式是由一对花括号和一系列表达式组成的,它总是返回块中最后一个表达式的值。
在某种程度上,可以将Rust看作是一切皆表达式。
let 关键字创建变量
let 创建的变量一般称为 绑定(Binding)
它表明了标识符(Identifier)和值(Value)之间建立的一种关联关系。
Rust 中的表达式一般可以分为位置表达式(Place Expression)和值表达式(Value Expression)。
在其他语言中,一般叫做左值(LValue)和右值(RValue)
位置表达式是表示内存位置的表达式。分别有以下几类:
通过位置表达式可以对某个数据单元的内存进行读写。
除此之外的表达式就是值表达式。
值表达式一般只引用了某个存储单元地址中的数据,它相当于数据值,只能进行读操作。
从语义角度来说,位置表达式代表了持久性数据,值表达式代表了临时数据。
表达式的求值过程在不同的上下文中会有不同的结果。
求值上下文也分为位置上下文(Place Context)和值上下文(Value Context)
位置上下文的表达式:
除了上述几种情况,其余表达式都属于值上下文。
值表达式不能出现在位置上下文中。
pub fn temp() -> i32 {
return 1;
}
fn main() {
let x = &temp();
temp() = *x; // error
}
temp 函数调用时一个无效的位置表达式,它是值表达式。
使用 let 关键字声明的位置表达式默认不可变,为不可变绑定。
fn main() {
let a = 1;
// a = 2; // immutable and error
let mut b = 2;
b = 3; // mutable
}
通过 mut 关键字,可以声明可变的位置表达式,即可变绑定。
可变绑定可以正常修改和赋值。
从语义上来说
let 默认声明的不可变绑定只能对相应的存储单元进行读取
let mut 声明的可变绑定可以对相应的存储单元进行写入
当位置表达式出现在值上下文中时,该位置表达式将会把内存地址转移给另外一个位置表达式,这其实是所有权的转移。
fn main() {
let place1 = "hello";
let place2 = "hello".to_string();
let other = place1;
println!("{:?}", place1);
let other = place2;
println!("{:?}", place2); // Err: other value used here after move
}
因为 place1 是一个位置表达式,现在出现在了赋值操作符右侧,即一个值上下文内,所以 place1 会将内存地址转移给 other。
在语义上,每个变量绑定实际上都拥有该存储单元的所有权,这种转移内存地址的行为就是所有权(OwnerShip)的转移,在Rust中称为移动(Move)语义,那种不转移的情况实际上是一种复制(Copy)语义。
Rust 没有GC,所以完全依靠所有权来进行内存管理。
Rust 提供借用(Borrow)操作符(&),可以直接获取表达式的存储单元地址,即内存位置。
可以通过该内存位置对存储进行读取。
fn main() {
let a = [1,2,3];
let b = &a;
println!("{:p}", b); // 0x7ffcbc067704
let mut c = vec![1,2,3];
let d = &mut c;
d.push(4);
println!("{:?}", d); // [1,2,3,4]
let e = &42;
assert_eq!(42, *e);
}
通过 println! 宏指定 {:p} 格式,可以打印 b 的指针地址,也就是内存地址。
注意,要获取可变引用,必须先声明可变绑定。
对于字面量 42 来说,其本身属于值表达式。
通过借用操作符,相当于值表达式在位置上下文中进行求值,所以编译器会为 &42 创建一个临时值。
值表达式在位置上下文中求值时会被创建临时值
将“ & ”称为借用操作符,通过借用操作符得到的类型叫引用(Reference)类型。
main 函数代表程序的入口。
函数是通过关键字 fn 定义的。
定义一个 FizzBuzz 函数:输入一个数字,当数字是3的倍数时,输出fizz;当数字是5的倍数时,输出buzz;
当数字是3和5共同的倍数时,输出 fizzbuzz;其他情况返回该数字。
通过对《Rust编程之道》第二章“语言精要”的学习,我们系统地回顾了 Rust 的核心基石。从宏观的生态构成(编译器、Cargo),到微观的语法哲学(语句与表达式),我们一步步深入,最终触及了 Rust 的灵魂——所有权系统。
本次学习的核心收获可以概括为以下三点:
Rust 是一个完整的生态系统:它不仅仅是一门语言,rustc 编译器友好的提示、Cargo 强大的包管理能力,共同构成了高效、现代的开发体验。
Rust 是“表达式驱动”的语言:理解块表达式、函数体返回值等“一切皆表达式”的理念,是写出地道、简洁 Rust 代码的关键。
所有权是内存管理的基石:Rust 摒弃了垃圾回收(GC),通过所有权(Ownership)、借用(Borrow) 和 生命周期(后续章节内容)这套静态分析机制,在编译期就保证了内存安全。理解 let 绑定的 Move 语义和 & 引用的 Borrow 语义,是跨越 Rust 学习最大障碍的核心所在。
正如开篇所言,“每有会意,便欣然忘食”。今天,我们对 Rust 的这些基础规则有了更深的“会意”。这不仅是理论知识的填充,更是编程思维模式的一次重要升级。掌握了这些“语言精要”,我们才算真正拿到了进入 Rust 世界的门票。接下来,让我们带着这些基础,继续探索 Rust 更加广阔和强大的功能吧!
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!