初见Rust:编程界的原神
2024-6-19
| 2026-1-22
字数 5444阅读时长 14 分钟
password
icon
AI summary
type
status
date
slug
summary
tags
category

0. 学习资料

生态极好,学习资源丰富,社区讨论度极高
  • rustlings:在VsCode中练习,有很多练习题
  • 极客时间:Rust训练营
 

1. Java→Rust概念映射

Java 概念
Rust 对应概念
核心差异
Class
Struct (数据) + Impl (行为)
Rust 数据与行为分离,没有继承,只有组合。
Interface
Trait
Trait 更强大,可以包含默认实现,甚至作为类型约束(Bounds)。
Null
Option<T>
Rust 没有 Null!强制你在编译期处理“空值”情况,彻底告别 NPE。
Exception
Result<T, E>
错误是值,必须处理。没有 try-catch 的控制流跳转,逻辑更线性。
Annotation
Attribute (宏)
#[derive(Debug)]。Rust 的宏是元编程,比 Java 注解更底层更强大。
Maven/Gradle
Cargo
体验极佳的包管理和构建工具。Cargo.toml 等同于 pom.xml

2. 基础语法

参考:https://learnxinyminutes.com/rust/(新编程语言快速上手网站)
有些特别的点是:

变量:

1)不可变性

  • 默认行为:Rust中所有通过let定义的绑定默认都是不可变的(immutable)
  • 具体表现:例如 let x = 1; 后,不能执行 x = 2 这样的重新赋值操作
  • 设计原理:这种设计保证了变量一旦绑定后,其值在后续访问时必定保持不变

2)可变变量

  • 可变声明:通过添加mut关键字可声明可变变量,如 let mut x = 1;
  • 重新赋值:可变变量允许后续修改,如 x = 2 的操作在mut声明下合法
使用建议:默认使用不可变绑定,仅在需要修改值时使用mut,这符合Rust的安全设计理念

字符串

1)字符串类型

  • 字面量表示:使用引号包裹的文本可直接赋值给变量,如"hello",这种形式的类型是&str
  • String类型:通过to_string()方法创建,如"hello world".to_string(),是具有所有权的字符串类型
  • 内存分配:
    • &str是引用类型,指向字符串数据的引用
    • String类型数据存储在堆上,栈上保存指向堆数据的指针

2)类型转换与引用

  • 引用关系:String类型可以通过引用转换为&str类型
  • 所有权概念:String具有完整所有权,而&str只是借用引用(所有权概念将在后续详细讲解)

3)输出方法

  • println!宏:
    • 使用叹号!标识,表明是宏而非函数
    • 编译时会被展开成一系列代码
    • 可以打印数字、字符串等各种数据结构
  • print!宏:与println!类似,但不自动换行

向量和数组

  • 内存分配:
    • 数组:分配在栈上,如示例[1,2,3,4]用方括号声明
    • 向量:分配在堆上,通过栈指针引用堆内存
  • 大小特性:
    • 数组:固定大小和类型,不可动态增长
    • 向量:动态增长结构,支持push操作(需声明为let mut vector)
  • 初始化方式:
    • 向量:使用vec!宏初始化,如vec![1,2,3]会依次push元素
  • 视图访问:
    • 向量引用可获得只读视图(类似String的&str),本质是包含起始指针和长度的胖指针
  • 打印方式:
    • 使用{}对应Display trait
    • 使用{:?}对应Debug trait

元组

  • 类型特性:
    • 固定长度但元素类型可异构(对比数组要求同类型)
  • 访问方式:
    • 解构访问:类似JavaScript的解构语法
    • 索引访问:通过.index形式,如x.1访问第二个元素(索引从0开始)
  • 示例说明:
    • 若x = (5, "hello"),则x.1输出字符串"hello"

自定义数据类型

1)结构体

  • 基本形式: 使用struct关键字定义,包含命名字段,如struct Point {x:i32, y:i32}
  • 元组结构体: 无命名字段的结构体,如struct Point2(i32, i32)
  • 初始化方式:
    • 命名字段结构体:Point {x:0, y:0}
    • 元组结构体:Point2(0, 0)
  • product type特性: 结构体是多个字段的组合类型,每个字段必须被初始化

2)枚举

  • 基本枚举: 类似C语言的简单枚举,如enum Direction {Left, Right, Up, Down}
  • 带字段枚举: 类似tagged union,如enum OptionalI32 {AnI32(i32), Nothing}
  • sum type特性: 枚举值是多个可能类型的集合
  • 初始化方式: 必须指定具体变体,如OptionalI32::AnI32(21)或OptionalI32::Nothing
  • 应用场景: 特别适合处理状态相关的类型组合问题

3)泛型

  • 语法形式: 在类型名后加<T>,如struct Foo<T> {bar: T}
  • 标准库示例: Option枚举就是泛型,定义为enum Option<T> {Some(T), None}
  • 实现优势: 避免为不同类型重复编写相同逻辑的代码
  • 编译机制: 使用时具体化类型参数,编译时展开为具体类型版本

4)方法

  • 实现语法: 使用impl块为结构体定义方法
  • self参数类型:
    • &self(只读): 不可变引用,方法内不能修改结构体
    • &mut self(读写): 可变引用,允许修改结构体
    • self(销毁): 获取所有权,调用后结构体被消耗
  • 泛型方法: 实现时需要声明泛型参数,如impl<T> Foo<T> {...}
  • 所有权影响: 使用self作为参数会转移所有权,调用后原变量失效

5)Trait特质

  • 概念类比: 类似Java的interface或Scala的type class
  • 定义方式: 使用trait关键字定义行为规范
  • 实现示例: 如FromStr特质定义了字符串解析行为
  • 系统设计应用: 先定义核心数据结构,再通过特质定义它们的行为
  • 组合优势: 特质可以组合多个行为,增强代码复用性

6)函数指针

  • 基本概念: 指向函数而非数据的指针
  • 使用场景: 主要用于高阶函数和回调机制
  • 类型表示: 如fn(i32) -> i32表示接收i32返回i32的函数指针
  • 与其他语言区别: Rust的函数指针更强调类型安全性

7)模式匹配

  • 语法形式: 使用match表达式进行模式匹配
  • 枚举匹配: 可以匹配枚举的不同变体及其内部数据
  • 深度匹配: 支持嵌套结构的多层级模式匹配
  • 穷尽性检查: 编译器会强制要求处理所有可能情况
  • 常见应用: 与Option和Result类型配合处理可能失败的操作

8) derive派生宏自动实现Trait

derive 的核心思想是:“让编译器去写那些无聊的样板代码”。 比如
其实编译器在后台偷偷写下了:

9)宏 vs 函数

特性
函数
调用方式
func_name(args)
macro_name!(args)
参数类型
固定类型
灵活,可以是任意 token
编译时展开
语法检查
编译前
展开后
示例
vec::new()
vec![1, 2, 3]
有两种宏

声明式宏 (macro_rules!)

过程宏 (Procedural Macro)

控制流程

内存安全与指针

1)所有权指针

  • 唯一所有权:同一时间只能有一个所有者,离开作用域时自动释放
  • 所有权转移:赋值会导致所有权转移,原变量不可再用

2)引用与借用

  • 不可变引用:创建引用称为"借用",借用期间原值不可修改
  • 借用有效期:直到最后一次使用借用变量

3)可变引用

  • 独占访问:可变借用期间,原变量完全不可访问
类型
含义
可变性
所有权
T
拥有值
取决于 mut
&T
不可变引用
不可变
&mut T
可变引用
可变
符号
含义
&T
不可变引用(只读)
&mut T
可变引用(可读写)
*r
解引用(获取引用指向的值)

类型系统

notion image

内存布局

notion image
notion image

所有权和生命周期

1. 基本规则

  • 唯一拥有权:值被唯一的scope拥有,它们共存亡。scope可以是函数或花括号括起的作用域。
  • 所有权转移:值可以移动到另一个scope,新的scope将拥有这个值。
  • 引用规则:
    • 一个值可以有多个只读引用或单个可变引用
    • 这些引用之间是互斥关系(类似RwLock)
    • 引用不能超越值的存活期
  • 并发类比:与多线程开发中的Mutex互斥锁和读写锁原理相似,写操作需要独占访问权。

2. 值的存储方式

notion image

1)堆与栈的概述

  • 栈内存:
    • 动态生命周期
    • 随函数调用自动增长/回收
    • 存储局部变量和函数调用信息
  • 堆内存:
    • 未定义/受控生命周期
    • 需要显式分配和释放
    • 存储动态大小的数据

2)程序启动时的内存布局

  • 内核空间:与内核地址映射的区域
  • 程序参数区:存储程序入口参数和环境变量
  • 调用栈:每个线程独立的执行调用栈
  • 堆:动态分配的大内存区域
  • 特殊段:
    • BSS段:未初始化数据
    • Read-Only Data:只读数据(如字符串字面量)
    • Text段:程序代码

3)栈内存的特点与生命周期

  • 自动管理:随函数调用自动分配和释放
  • 确定大小:函数所需栈空间在编译期确定
  • 调用过程:
    • 调用时保存返回地址
    • 保存寄存器副本
    • 分配局部变量空间
    • 压入被调用函数参数

4)堆内存的特点与管理方式

  • 动态分配:大小在运行时确定
  • 管理方式对比:
    • C/C++:手动管理(malloc/free)
    • Java/Go:GC垃圾回收
    • Swift:ARC引用计数
    • Rust:所有权系统自动管理

5)其他内存段

  • BSS段:存储未初始化全局变量
  • Read-Only Data:存储常量数据和字符串字面量
  • Text段:存储程序可执行代码

6)内存空间的生命周期

  • 最长生命周期:Text段、RO Data段、BSS段(与程序同生命周期)
  • 动态生命周期:栈和堆内存(生命周期不确定)

7)栈与堆的引用关系

  • 典型结构:栈上的胖指针指向堆内存(如String类型)
  • 内存问题:
    • 栈指针指向未分配堆内存 → 野指针
    • 堆内存无栈引用 → 内存泄漏

8)函数调用栈变化

  • 调用过程:
    • 保存返回地址
    • 保存寄存器副本
    • 分配局部变量空间
    • 压入被调用函数参数
    • 调用函数

应用案例

1)String内存管理示例

notion image
  • 创建过程:
    • let mut greeting = String::with_capacity(32)在栈上创建胖指针
    • 堆上分配32字节内存
  • 写入数据:push_str("hello world")使用11字节
  • 自动扩展:超过容量时会重新分配更大内存
  • 释放过程:
    • 调用drop()释放堆内存
    • 退出作用域释放栈内存

2)悬垂指针示例

notion image
  • C/C++: 程序员手动管理堆内存,容易出现内存泄漏和悬空指针。
  • Java/Go: 使用垃圾回收器(GC)自动管理堆内存,但无法精确控制释放时机,存在内存累积问题。
  • Rust折中方案: 通过所有权规则在编译期确保内存安全,既不需要手动管理,也避免了GC的开销。
 
notion image
  • 潜在问题检测: 编译器能识别看似无害但可能导致内存安全问题的操作,如vector扩容导致的悬空指针。
  • 错误预防: 通过所有权和借用规则,在编译期阻止可能导致内存不安全的行为。
  • 实际案例:
    • 当vector需要扩容时,旧内存可能被释放,导致原有引用变为悬空指针。
    • Rust编译器会阻止在引用存在时修改容器的操作,从根本上避免这类问题。
  • 并发安全: 所有权机制不仅解决内存安全问题,也为并发安全提供了基础保障。
  • 生命周期标注: 帮助编译器理解各参数生命周期关系,确保引用有效性。

3) 所有权机制可以很好地解决内存并发安全问题

notion image
  • 唯一拥有原则: 值被唯一的scope拥有,它们共存亡。例如线程一拥有字符串"hello world"时,线程二不能直接访问。
  • 移动机制: 值可以移动到另一个scope,新scope将拥有这个值。如将值从线程一移动到线程二时,线程一会失去控制权。
  • 引用规则:
    • 一个值可以有多个只读引用或单个可变引用
    • 引用之间是互斥关系(类似RwLock)
    • 引用不能超过值的存活期
  • 共享内存处理:
    • 使用Arc等引用计数智能指针追踪访问权
    • 将值移动到另一个scope(原scope会失去控制权)
    • 通过Mutex或RwLock进行保护
  • 内存保护机制:
    • 两个scope不能同时拥有同一内存
    • 需要共享时必须使用同步原语保护
    • 可通过拷贝方式在不同scope间传递值

生命周期的多种情况

notion image

1)调用栈的生命周期关系

  • 调用者优先原则: 调用者(main函数)的生命周期必然长于被调用者(local_ref函数)
  • 非法引用示例: 不允许引用生命周期更短的变量(如local_ref函数中的变量a)
  • 内存安全机制: 这种限制存在于大多数编程语言中,是内存安全的基本保障

2)堆栈生命周期一致性

  • 统一生命周期模型: 将堆和栈的生命周期视为一体时,特定引用关系可以成立
  • 合法引用条件:
    • 堆内存的生命周期等于其引用者(data)的生命周期
    • 被引用变量(v)与引用者(data)处于同一作用域
    • 三者生命周期完全一致时引用关系合法
  • 非法场景: 当堆内存尝试引用生命周期更短的局部变量时(如local_ref中的v),该引用关系不成立
  • 核心原则: 任何内存引用必须确保被引用者的生命周期不短于引用者

传值行为

notion image

1)移动语义

  • 基本概念:当值从一个scope传递到另一个scope时,默认会发生所有权转移(Move),原scope将失去对该值的所有权。
  • 实现条件:适用于未实现Copy trait的数据结构,特别是包含堆内存分配的类型(如Vec<T>)。
  • 内存行为:移动后,目标scope获得栈上数据的副本(仍指向相同堆内存),原scope的栈数据失效。
  • 生命周期规则:值只能被唯一的scope拥有,新旧scope不能同时持有同一值的所有权。

2)复制

  • Copy与Clone区别:
    • Copy:仅适用于原生类型(primitive types),进行按位拷贝(浅拷贝),编译器自动实现
    • Clone:可手动实现,通常需要深拷贝(如复制堆内存)
  • 隐式Copy行为:对实现Copy trait的类型(如i32),传值时会自动复制
  • 显式Clone:当需要保留原所有权时,需显式调用clone()

3)借用/引用

  • 使用条件:当函数不需要所有权但需要访问数据时
  • 语法形式:使用&符号创建引用
  • 所有权保留:调用后原始scope仍保留所有权
  • 生命周期约束:引用生命周期不能超过被引用值的生命周期
  • 引用类型:
    • 共享引用(&T):允许多个只读引用共存
    • 可变引用(&mut T):独占访问,与其他引用互斥

泛型和trait

  • 核心思想:泛型是数据结构的函数,通过类型参数化避免为不同数据类型重复编写相同算法代码
  • 现代语言趋势:连Go语言也在近年加入泛型支持,体现其重要性
  • 类比理解:如同将重复行为封装成函数,泛型是对数据结构的抽象封装
 
notion image
  • 代码复用:
    • 示例copy<R, W>函数可处理文件、socket、cache、db等多种数据源
    • 避免为每种组合编写单独实现
  • 抽象编程:
    • 针对行为(trait约束)而非具体类型编程
    • 只要实现指定trait,代码自动适配新类型
  • 扩展性:未来新增数据类型只需实现相应trait即可兼容现有代码

Rust提供的trait

notion image
notion image

3. 项目开发实践

1. 代码审查最佳实践

  1. 架构设计
  • 审查重点:新功能的架构是否符合良好设计规范
  • 适用场景:对于复杂PR需要特别关注架构设计
  1. 接口设计
  • 审查范围:包括API、数据结构和公共方法
  • 关键指标:接口设计的合理性是代码质量的核心要素
  1. 代码质量
  • 最佳实践:
    • 命名规范性
    • 算法效率(如避免不必要的循环)
  • 可读性:避免晦涩难懂的算法,必要时需注明优化原因
  • 安全性:
    • 数据库操作需防范注入风险
    • 前端代码需防范CSRF和XSS攻击
  1. 错误处理
  • 重要性:错误处理与正确路径同等重要
  • 设计原则:
    • 错误处理接口应合理
    • 处理范围要适度,不宜过大或过细
  1. 可维护性
  • DRY原则:杜绝代码复制行为
  • KISS原则:保持代码简单
  • 文档要求:在关键位置提供必要但不冗余的文档
  1. 团队规范
  • 审查依据:需符合团队特定实践要求
  • 自动化检查:格式检查等基础工作应交由CI完成

2. 合并流程规范

  • 分支保护:
    • 主分支应设置保护
    • 防止意外修改
  • PR合并条件:所有检查通过后才能合并
  • 合并方式偏好:推荐使用squash merge将PR内所有提交合并为一个
  • 分支清理:合并后应及时删除特性分支
  • Rust
  • 简明文档写作指南实验室服务器使用进阶
    Loading...