Rust基础语法与所有权
Rustlings 入门
关键资源与配置指令
学习 Rust 前需完成环境配置,并储备核心学习资源,以下是经过验证的可靠资源和关键指令,确保环境配置顺利、学习有支撑:
- Rust 语言官网:https://www.rust-lang.org/zh-CN/,包含官方文档、最新特性介绍、生态工具等,是学习 Rust 的核心权威资源。
- 环境配置教程:https://rcore-os.cn/rCore-Tutorial-Book-v3/chapter0/5setup-devel-env.html,详细讲解了 Windows、Linux、Mac 三种系统的 Rust 开发环境配置步骤,适配训练营学习需求。
- Rustlings 仓库:https://classroom.github.com/a/S8vP0lDr,训练营专属 Rustlings 练习仓库,需通过该仓库获取 110 道练习题,完成后可提交验证。
- Rust 自动安装脚本:
curl https://sh.rustup.rs -sSf | sh,适用于 Linux 和 Mac 系统,可一键安装 Rust 工具链(包含 rustc、cargo 等核心工具),Windows 系统可通过官网下载安装包完成配置。 - 官方学习资料:《Rust 程序设计语言》(官方权威教材,俗称“the book”)、《通过例子学 Rust》(实操性强,适合边练边学)、Rust 语言中文社区(汇聚国内学习者经验,可解决常见问题)。
Rustlings 核心练习对应
本节入门课程与 Rustlings 中的多个练习模块直接对应,建议结合课程知识点同步练习,实现理论与实操的深度结合,具体对应练习模块如下:variables(变量)、functions(函数)、if(条件判断)、primitive_types(基本数据类型)、vecs(向量)、move_semantics(所有权转移)、structs(结构体)、strings(字符串),每完成一个知识点的学习,及时练习对应模块的题目,巩固知识点记忆。
Rust 基础语法
程序入口与基础结构
与大多数编程语言类似,Rust 程序的入口的是 main 函数,所有可执行代码都会从 main 函数开始执行。最基础、最经典的 Rust 程序是 Hello World 程序,其代码简洁易懂,可快速熟悉 Rust 的基本代码结构:
1 | fn main(){ |
在实际开发中,我们通常会通过 cargo 工具创建和管理 Rust 工程,cargo 是 Rust 官方提供的构建工具,可自动处理依赖、编译、运行等流程,具体工程创建与运行步骤如下:
- 创建工程:在终端中输入指令
cargo new hello,即可创建一个名为“hello”的 Rust 工程,工程会自动生成标准的目录结构(src 文件夹、Cargo.toml 配置文件等)。 - 运行工程:进入工程目录(cd hello),输入指令
cargo run,cargo 会自动编译工程代码,生成可执行文件,然后执行该文件,最终在终端输出“Hello, world!”,整个过程无需手动处理编译细节。
变量与可变性
变量声明与可变性
变量是 Rust 中存储数据的基本单元,其声明和使用有明确的规则,核心特点是“默认不可变”,这是 Rust 保障内存安全和并发安全的重要设计之一,具体规则如下:
- 使用 let 关键字声明变量,默认情况下变量是不可变的(immutable),一旦将某个值绑定到变量上,就无法修改该变量的值,这种设计可有效防止意外的数据修改,尤其在并发场景中,能避免数据竞争问题。
- 如果需要修改变量的值,需在 let 关键字后添加 mut 关键字,声明为可变变量,这样就可以随时修改变量绑定的值,满足实际开发中需要动态更新数据的场景。
- 示例:通过简单代码对比不可变变量和可变变量的使用差异,更直观理解其规则:
1 | let x = 5; // 不可变变量,无法修改 |
常量
常量与不可变变量类似,都是无法修改的值,但二者在使用场景、声明方式和特性上有明显区别,常量的核心规则如下:
- 使用 const 关键字声明常量,与 let 声明的不可变变量不同,常量总是不可变的,且不允许使用 mut 关键字修饰,其值从声明时就固定,无法在任何场景下修改。
- 常量只能赋值为常量表达式,不能是运行时才能计算出的值(如函数返回值、用户输入的值等),这是因为常量需要在编译阶段确定其值,确保程序运行时无需额外计算,提升执行效率。
- 示例:声明一个表示“三小时秒数”的常量,清晰展示常量的使用方式:const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
变量隐藏(Shadowing)
变量隐藏是 Rust 中一个特殊的特性,指的是可以在同一个作用域内,定义与之前变量同名的新变量,新变量会覆盖旧变量的绑定,且新变量的类型可以与旧变量不同。需要注意的是,变量隐藏与可变变量(mut)有本质区别:mut 是修改同一个变量的值,而隐藏是重新绑定一个新变量,旧变量会被隐藏,无法再访问。
1 | fn main() { |
基本数据类型
Rust 的基本数据类型分为标量类型和复合类型两大类,标量类型用于表示单个值,复合类型用于表示多个值的组合,掌握这些基本类型是编写 Rust 代码的基础。
标量类型(单个值类型)
标量类型是 Rust 中最基础的数据类型,每种标量类型都有明确的取值范围和用途,具体分为以下6类:
- 有符号整数:用于表示正数、负数和零,包含 i8、i16、i32、i64、i128、isize 六种,后缀数字表示位数(如 i8 表示 8 位有符号整数),其中 isize 的位数与平台指针宽度一致(32位平台为32位,64位平台为64位),适用于表示内存地址相关的数值。
- 无符号整数:仅用于表示非负数(正数和零),包含 u8、u16、u32、u64、u128、usize 六种,后缀数字表示位数,usize 与平台指针宽度一致,常用于表示数组索引、集合长度等非负数值。
- 浮点数:用于表示带有小数部分的数值,包含 f32(单精度浮点数)和 f64(双精度浮点数)两种,其中 f64 是 Rust 的默认浮点数类型,精度更高,运算更稳定,大多数场景下推荐使用 f64。
- char(字符):用于表示单个 Unicode 字符,不仅可以表示英文字母、数字、符号,还可以表示中文、日文、特殊符号等,每个 char 类型占 4 个字节,声明时用单引号包裹(如 ‘a’、’α’、’∞’、’中’)。
- bool(布尔型):用于表示逻辑判断结果,仅包含 true(真)和 false(假)两个值,常用于 if 条件判断、循环控制等场景,占用 1 个字节的内存。
- 单元类型(unit type):用 () 表示,其唯一的值也是 (),通常用于表示无返回值的函数(Rust 中没有明确的“无返回值”,而是返回单元类型),也可用于表示不需要实际意义的值。
复合类型(多个值组合)
复合类型可以将多个不同类型(或相同类型)的值组合在一起,Rust 中最常用的复合类型是元组和数组,二者的核心区别是:元组长度固定、元素类型可不同,数组长度固定、元素类型必须相同。
元组(tuple)
元组是一种灵活的复合类型,适用于将多个不同类型的值组合成一个整体,其核心特性和使用方法如下:
- 长度固定:一旦声明元组,其包含的元素个数就无法增减,即使是添加或删除元素,也需要重新声明一个新的元组。
- 元素类型可不同:元组中的每个元素可以是不同的基本数据类型,用圆括号包裹,元素之间用逗号分隔,声明时可指定每个元素的类型,也可让 Rust 自动推断。
- 访问方式:有两种常用访问方式,一是通过“点号 + 索引”访问单个元素(索引从 0 开始),二是通过模式匹配(解构)将元组的元素分别绑定到变量上,方便一次性访问多个元素。
- 示例:通过代码展示元组的声明、类型指定和访问方式:
1 | fn main() { |
数组(array)
数组是一种用于存储多个相同类型值的复合类型,其内存是连续分配的,适用于存储固定数量的同类型数据,核心特性和使用方法如下:
- 长度固定:与元组类似,数组的长度一旦声明就无法修改,若需要动态增减元素,应使用后续会介绍的 Vector 类型。
- 元素类型必须相同:数组中的所有元素必须是同一种基本数据类型,不能混合不同类型的值,这是数组与元组的核心区别之一。
- 存储位置:数组存储在栈上,而非堆上,访问速度更快,适合存储数量固定、无需动态修改的数据。
- 声明方式:有两种常用声明方式,一是明确指定元素类型和长度([元素类型; 长度]),二是直接初始化元素,让 Rust 自动推断类型;此外,还可以使用“[初始值; 元素个数]”的快捷方式,创建所有元素均为初始值的数组。
- 示例:通过代码展示数组的多种声明和访问方式:
1 | fn main() { |
函数
函数是 Rust 中组织代码的核心单元,用于封装可复用的逻辑,通过函数可以将复杂代码拆分为多个简单模块,提升代码的可读性和可维护性,其核心特性和使用方法如下:
- 声明方式:使用 fn 关键字声明函数,函数名称遵循 snake case 命名规范(全小写字母,单词之间用下划线分隔),例如 another_function、calculate_length 等,避免使用驼峰命名。
- 入口函数:main 函数是 Rust 程序的唯一入口,无需参数,也无需返回值(默认返回单元类型 ()),程序运行时会首先执行 main 函数中的代码。
- 函数调用与参数:函数可以嵌套调用(在一个函数中调用另一个函数),函数参数需明确指定类型(Rust 不允许省略参数类型),返回值可通过箭头 -> 指定类型,若省略返回值类型,则默认返回单元类型 ()。
- 示例:通过简单的函数声明和调用,展示函数的基本使用:
1 | fn main() { |
流程控制
流程控制用于控制程序的执行顺序,Rust 提供了三种核心流程控制结构:if 表达式(条件判断)、loop 循环、while 循环、for 循环(循环执行),其中 if 是表达式而非语句,这是 Rust 与其他语言的一个重要区别。
if 表达式
if 表达式用于根据不同的条件执行不同的代码分支,其核心特点是“条件无需加括号”,且可以作为表达式赋值给变量,具体规则和使用方法如下:
- 条件判断:if 后面的条件是一个 bool 类型的值(true 或 false),无需用圆括号包裹,条件为 true 时执行 if 后面的代码块,条件为 false 时执行 else 后面的代码块(else 可选)。
- 表达式特性:与其他语言不同,Rust 中的 if 是表达式,而非语句,这意味着 if 表达式会有返回值,返回值是执行分支中最后一条语句的值,且所有分支的返回值类型必须一致,否则会报错。
- 示例:通过代码展示 if 表达式的条件判断和赋值用法:
1 | fn main() { |
循环
循环用于重复执行一段代码,Rust 提供了三种循环方式,分别适用于不同场景:loop(无限循环)、while(条件循环)、for(遍历循环),可根据实际需求选择合适的循环方式。
loop 循环
loop 循环是 Rust 中最基础的循环方式,本质是无限循环,需通过 break 关键字手动终止循环,适用于不确定循环次数、需要手动控制终止时机的场景,其核心特性如下:
- 无限循环:loop 后面的代码块会一直重复执行,直到遇到 break 关键字才会终止,若忘记添加 break,程序会进入死循环。
- continue 关键字:用于跳过当前迭代的剩余代码,直接进入下一次循环,适用于需要跳过某些条件下的代码执行的场景。
- 返回值特性:loop 循环可以作为表达式,通过 break 关键字返回一个值,该值会被赋值给接收变量,这是 loop 循环的一个实用特性。
- 循环标签:当存在嵌套循环时,可给外层循环添加标签(标签前缀加 ‘),通过 break 或 continue 配合标签,指定作用的循环,避免影响内层循环。
- 示例(从循环返回值):通过 loop 循环计算数值,终止时返回结果:
1 | fn main() { |
while 循环
while 循环是条件循环,其核心逻辑是“条件为真时执行循环体,条件为假时终止循环”,适用于不确定循环次数,但已知循环终止条件的场景,相比 loop 循环,无需手动添加 break,更简洁高效。
1 | fn main() { |
for 循环
for 循环是 Rust 中最常用、最安全的循环方式,主要用于遍历集合(如数组、元组、Vector 等)或范围,无需手动控制索引,可有效避免索引越界问题,代码简洁易读,适用于已知遍历对象的场景。
1 | fn main() { |
自定义类型与常用集合
除了 Rust 提供的基本数据类型,我们还可以根据需求自定义类型(结构体),同时 Rust 也提供了多种常用集合类型,用于存储大量数据,其中最常用的是 Vector 和 HashMap。
结构体(struct)
结构体是 Rust 中用于自定义复合类型的核心语法,通过结构体可以将多个不同类型的字段组合在一起,形成一个新的类型,适用于描述具有多个属性的实体(如用户、商品、坐标等),其核心特性和使用方法如下:
- 定义方式:使用 struct 关键字定义结构体,结构体名称遵循 PascalCase 命名规范(首字母大写,单词首字母也大写),结构体内部包含多个字段,每个字段需指定名称和类型,字段之间用逗号分隔。
- 实例创建:创建结构体实例时,使用“结构体名 + 大括号”的形式,通过 key: value 的方式为每个字段赋值,字段的赋值顺序可以与结构体定义时的顺序不同,灵活便捷。
- 字段访问:通过“实例名.字段名”的方式访问结构体实例的字段,若需要修改字段的值,需将结构体实例声明为可变(mut),不可变实例的字段无法修改。
- 示例:通过定义 User 结构体,展示结构体的定义、实例创建和字段修改:
1 | // 定义结构体,描述用户信息,包含4个字段 |
元组结构体
元组结构体是结构体的一种特殊形式,它有结构体名称,但没有字段名,只有字段类型,适用于无需为字段命名、只需将多个不同类型的值组合成一个整体的场景。需要注意的是,即使两个元组结构体的字段类型完全相同,只要结构体名称不同,它们就是不同的类型,无法相互赋值或混用。
1 | struct Color(i32, i32, i32); // 元组结构体,用于表示颜色(红、绿、蓝) |
常用集合
集合用于存储大量数据,与数组、元组不同,集合的长度可以动态变化(部分集合),且存储在堆上,适用于需要存储不确定数量数据的场景,Rust 中最常用的两种集合是 Vector 和 HashMap。
Vector<T>
Vector(向量)是一种动态数组,可存储多个相同类型的值,内存连续分配,支持动态增减元素,适用于需要动态添加、删除数据的场景,其核心特性和使用方法如下:
- 核心特性:动态可变长度,元素类型必须相同,存储在堆上,访问速度接近数组,支持随机访问(通过索引)。
- 创建方式:有两种常用创建方式,一是使用 Vec::new() 方法创建空 Vector,需明确指定元素类型(Rust 无法自动推断空 Vector 的类型);二是使用 vec![] 宏创建,可直接初始化元素,Rust 会自动推断类型。
- 常用方法:push(向 Vector 末尾添加元素)、len(获取 Vector 的长度)、get(通过索引获取元素,返回 Option<&T>,避免索引越界)、pop(删除并返回 Vector 末尾的元素)等。
- 示例:通过代码展示 Vector 的创建和常用方法使用:
1 | fn main() { |
HashMap<K, V>
HashMap 是一种键值对集合,通过哈希函数将键(K)映射到值(V),键具有唯一性(不可重复),适用于需要通过键快速查找值的场景(如存储用户ID与用户信息的映射、成绩与学生姓名的映射等),其核心特性和使用方法如下:
- 核心特性:键值对存储,键唯一,通过哈希函数实现快速查找,存储在堆上,长度可动态变化,键和值可以是任意类型(需满足一定条件)。
- 导入方式:HashMap 不在 Rust 的 prelude(预导入模块)中,使用前需手动导入
use std::collections::HashMap;,否则无法使用。 - 常用方法:insert(插入键值对,若键已存在,会覆盖原有值)、get(通过键获取值,返回 Option<&V>,若键不存在则返回 None)、contains_key(判断某个键是否存在)、remove(通过键删除键值对)等。
- 示例:通过代码展示 HashMap 的导入、创建和常用方法使用:
1 | use std::collections::HashMap; |
Rust 所有权机制
所有权机制是 Rust 最核心、最独特的特性,也是 Rust 保障内存安全、无需垃圾回收(GC)的关键,其核心思想是通过一套规则管理内存,确保程序运行时不会出现内存泄漏、空指针、数据竞争等问题,所有 Rust 开发者都必须熟练掌握。
所有权核心规则
Rust 所有权机制有三条核心规则,所有关于所有权的操作都必须遵循这三条规则,牢记这三条规则就能快速理解所有权的核心逻辑:
- 所有权唯一:Rust 中的每一个值都有一个对应的所有者(owner),这个所有者通常是声明该值的变量。
- 单一所有者:值在任一时刻有且只有一个所有者,不存在多个变量同时拥有同一个值的所有权的情况。
- 作用域释放:当所有者(变量)离开其作用域(大括号闭合处),这个值将被自动丢弃,Rust 会自动调用 drop 方法释放该值占用的内存,无需手动管理内存。
示例:通过简单代码展示所有权的核心规则,理解值的所有权绑定与释放:
1 | let x = 123; // x是123的所有者,123的所有权绑定到x |
变量作用域
变量的作用域是指变量的有效范围,即变量从声明处开始,到当前作用域结束(大括号闭合处)之间的区域,在作用域内,变量可以被正常访问和使用;超出作用域后,变量将被自动销毁,其对应的内存会被 Rust 自动释放,这是所有权机制中“作用域释放”规则的具体体现。
1 | fn main() { |
所有权转移(移动)
所有权转移(也称为“移动”)是指将一个值的所有权从一个变量转移到另一个变量,转移后,原变量失去所有权,无法再访问该值,新变量成为该值的唯一所有者,这是遵循“单一所有者”规则的核心操作。需要注意的是,所有权转移的行为会因值的类型不同而有所差异,主要分为基本类型和复合类型两类。
基本类型与复合类型的差异
Rust 中的类型分为“实现 Copy trait 的类型”和“未实现 Copy trait 的类型”,这两类类型在赋值时的所有权行为完全不同:
- 基本类型(标量类型、元组/数组中的基本类型):这类类型大多实现了 Copy trait,赋值时会复制值的内容,所有权不会转移,原变量仍然拥有原值的所有权,可正常访问,新变量拥有复制后的值的所有权。
- 复合类型(String、Vector、HashMap 等):这类类型未实现 Copy trait,赋值时不会复制值的内容,而是将所有权从原变量转移到新变量,原变量被废弃,无法再访问该值,避免了重复释放内存的问题。
函数参数与返回值的所有权转移
所有权转移不仅发生在变量赋值时,在函数调用过程中,将值传递给函数参数、函数返回值时,也会发生所有权转移,具体规则与变量赋值一致:
- 参数传递:将值传递给函数参数时,会发生所有权转移(除非值实现了 Copy trait),原变量失去所有权,无法再访问该值,函数参数成为该值的新所有者。
- 返回值转移:函数返回值会将所有权转移给调用者,函数内部的变量(作为返回值)在函数结束时不会被销毁,而是将所有权转移给调用者,调用者可以正常访问该值。
- 示例:通过函数调用展示所有权转移的过程:
1 | fn main() { |
引用与借用(避免所有权转移)
在实际开发中,我们经常需要访问一个值,但又不想获取该值的所有权(避免原变量被废弃),此时就可以使用“引用”,引用是一种不获取所有权的指针,指向其他变量的数据,这种通过引用访问数据的行为称为“借用”,引用是 Rust 中避免所有权转移、实现数据共享的核心方式。
引用的定义
引用用 & 符号表示,其核心特点是“不获取所有权”,仅拥有对数据的访问权,不会影响原变量的所有权。引用的生命周期与原变量一致,当原变量离开作用域,引用也会失效,无法再访问数据,避免了悬垂引用(指向已销毁数据的引用)的问题。
1 | fn main() { |
可变引用
默认情况下,引用是不可变的,无法通过引用修改原数据,若需要通过引用修改原数据,需使用“可变引用”,可变引用用 &mut 表示,但其使用有严格的规则,用于避免数据竞争,保障并发安全。
- 不可变引用限制:默认的引用(&T)是不可变的,即使原变量是可变的,也无法通过不可变引用修改原数据,只能访问数据。
- 可变引用声明:使用 &mut 声明可变引用(&mut T),只有原变量是可变的(mut),才能创建其可变引用,通过可变引用可以修改原数据。
- 可变引用的规则(避免数据竞争):数据竞争是指多个线程同时访问同一个数据,且至少有一个线程在修改数据,这会导致程序行为不确定,Rust 通过以下规则避免数据竞争:
- 同一时刻,一个数据只能有一个可变引用;
- 同一时刻,可变引用和不可变引用不能同时存在;
- 不可变引用可以有多个(多个读者同时访问数据,不会产生竞争)。
示例:通过可变引用修改原数据,展示可变引用的使用规则:
1 | fn main() { |
String 与 String Slice(&str)
String 和 String Slice(&str)是 Rust 中处理字符串的两种核心类型,二者密切相关但又有本质区别:String 是拥有所有权的动态字符串,&str 是无所有权的字符串切片,掌握二者的区别和使用场景,是编写 Rust 字符串处理代码的关键。
String 类型
String 是 Rust 中用于表示动态字符串的类型,其核心特点是“拥有所有权、可修改、存储在堆上”,适用于需要动态创建、修改字符串的场景(如用户输入、字符串拼接等),其核心特性和使用方法如下:
- 核心特性:动态可变长度,可修改字符串内容,存储在堆上,拥有所有权,会自动管理内存(离开作用域后自动释放)。
- 创建方式:常用的三种创建方式,可根据需求选择:
- String::new():创建一个空的 String,后续可通过 push_str、push 等方法添加内容;
- String::from(“xxx”):从字符串字面值(&str)创建 String,将字面值的内容复制到堆上;
- “xxx”.to_string():与 String::from 功能一致,是字符串字面值的方法,更简洁。
字符串拼接:使用 + 运算符






