Rust学习文档(二)

目录

[02 所有权与借用](#02 所有权与借用)

[2.1 什么是所有权(Ownership)](#2.1 什么是所有权(Ownership))

[Rust 为什么需要所有权](#Rust 为什么需要所有权)

所有权三条铁律

作用域(Scope)

[String 类型与栈/堆](#String 类型与栈/堆)

[移动(Move)------ 核心机制](#移动(Move)—— 核心机制)

[克隆(Clone)------ 深拷贝](#克隆(Clone)—— 深拷贝)

[Copy Trait ------ 栈上拷贝](#Copy Trait —— 栈上拷贝)

函数传参与所有权转移

函数返回值与所有权转移

[2.2 引用与借用(References & Borrowing)](#2.2 引用与借用(References & Borrowing))

问题:所有权转移导致使用不便

解决方案:引用(Reference)

[可变引用(Mutable Reference)](#可变引用(Mutable Reference))

[可变引用的限制 ------ 同一时刻只能有一个](#可变引用的限制 —— 同一时刻只能有一个)

[不可变引用 vs 可变引用的互斥](#不可变引用 vs 可变引用的互斥)

[悬垂引用(Dangling References)------ Rust 在编译期防止](#悬垂引用(Dangling References)—— Rust 在编译期防止)

引用规则总结

[2.3 切片(Slices)](#2.3 切片(Slices))

字符串切片

[字符串切片的危险性 ------ UTF-8 边界](#字符串切片的危险性 —— UTF-8 边界)

函数参数中的字符串切片

数组切片

切片在内存中的表示

[2.4 所有权决策树(速查)](#2.4 所有权决策树(速查))

学习目标检验


02 所有权与借用

2.1 什么是所有权(Ownership)

Rust 为什么需要所有权

|-------------|--------------------|-------------------------|
| 语言 | 内存管理方式 | 问题 |
| C/C++ | 手动 malloc/free | 忘记释放 → 内存泄漏;重复释放 → 悬垂指针 |
| Java/Go | 垃圾回收(GC) | 运行时开销;GC 暂停影响延迟 |
| Rust | 所有权系统(编译期检查) | 零运行时开销,编译器保证安全 |

所有权三条铁律

复制代码
1. Rust 中每一个值都有一个被称为其 所有者(owner)的变量
复制代码
2. 一次只能有一个所有者
复制代码
3. 当所有者离开作用域时,这个值将被丢弃(drop)

作用域(Scope)

rust 复制代码
{
    let s = String::from("hello");  // s 从这里开始有效
    // 使用 s
}                                   // 作用域结束,s 被丢弃(drop),内存自动释放

String 类型与栈/堆

rust 复制代码
let s1 = String::from("hello");   // 堆上分配,可变长度
let s2 = "hello";                   // 栈上(&str 切片类型),不可变

// String 由三部分组成(存储在栈上):
// - 指向堆数据的指针(ptr)
// - 长度(len)
// - 容量(capacity)

移动(Move)------ 核心机制

rust 复制代码
let s1 = String::from("hello");
let s2 = s1;    // s1 的所有权"移动"到 s2
// println!("{s1}");   // 编译错误!s1 已失效
println!("{s2}");      // OK

// 原因:如果 s1 和 s2 都指向同一块堆内存,
// 作用域结束时两者都会尝试释放 → 双重释放 → 内存不安全
// Rust 选择在移动时使原变量失效来避免此问题

克隆(Clone)------ 深拷贝

rust 复制代码
let s1 = String::from("hello");
let s2 = s1.clone();    // 深拷贝,堆数据独立
println!("s1 = {s1}, s2 = {s2}");   // 两者都有效

Copy Trait ------ 栈上拷贝

rust 复制代码
let x = 5;
let y = x;       // i32 实现了 Copy trait,直接复制值(栈拷贝)
println!("x = {x}, y = {y}");   // 两者都有效

// 实现 Copy trait 的类型(全部存储在栈上):
// - 所有整数类型(i8, u32, usize 等)
// - 所有浮点类型(f32, f64)
// - 布尔 bool
// - 字符 char
// - 元组(仅当所有元素都是 Copy 时,如 (i32, f64))

函数传参与所有权转移

rust 复制代码
fn main() {
    let s = String::from("hello");
    takes_ownership(s);
    // s 已移动,不可再使用

    let x = 5;
    makes_copy(x);
    // x 仍可用(i32 是 Copy 类型)
}

fn takes_ownership(some_string: String) {
    println!("{some_string}");
}   // some_string 离开作用域,drop 被调用,内存释放

fn makes_copy(some_integer: i32) {
    println!("{some_integer}");
}   // some_integer 离开作用域,无特殊操作

函数返回值与所有权转移

rust 复制代码
fn gives_ownership() -> String {
    let some_string = String::from("yours");
    some_string    // 所有权移动给调用者
}

fn takes_and_gives_back(a_string: String) -> String {
    a_string       // 原样返回所有权
}

fn main() {
    let s1 = gives_ownership();
    let s2 = String::from("hello");
    let s3 = takes_and_gives_back(s2);
    // s2 已移动到函数内,不可再用;s3 拿到了返回的所有权
}

2.2 引用与借用(References & Borrowing)

问题:所有权转移导致使用不便

rust 复制代码
fn calculate_length(s: String) -> (String, usize) {
    let length = s.len();
    (s, length)    // 必须同时返回所有权,才能继续使用
}

fn main() {
    let s1 = String::from("hello");
    let (s2, len) = calculate_length(s1);
    println!("'{}' 的长度是 {}", s2, len);
}

解决方案:引用(Reference)

rust 复制代码
fn calculate_length(s: &String) -> usize {  // 借用 String(不获取所有权)
    s.len()
}

fn main() {
    let s1 = String::from("hello");
    let len = calculate_length(&s1);   // 创建指向 s1 的引用
    println!("'{}' 的长度是 {}", s1, len);  // s1 仍然有效!
}

|----------------------------------|--------------|
| 术语 | 含义 |
| 借用 Borrowing | 创建一个引用的行为 |
| 借用者 | 拿到引用的函数/变量 |
| 所有者 | 原始变量,始终拥有所有权 |

可变引用(Mutable Reference)

rust 复制代码
fn main() {
    let mut s = String::from("hello");
    change(&mut s);
    println!("{s}");  // "hello, world"
}

fn change(s: &mut String) {
    s.push_str(", world");
}

可变引用的限制 ------ 同一时刻只能有一个

rust 复制代码
let mut s = String::from("hello");

let r1 = &mut s;
// let r2 = &mut s;    // 编译错误!不能同时有两个可变引用
// println!("{r1} {r2}");

// 好处:在编译期防止数据竞争(data race)
// 数据竞争的三个条件(Rust 在编译期全部排除):
// 1. 两个或更多指针同时访问同一数据
// 2. 至少有一个指针在写入
// 3. 没有同步机制

不可变引用 vs 可变引用的互斥

rust 复制代码
let mut s = String::from("hello");

let r1 = &s;         // 不可变引用 OK
let r2 = &s;         // 多个不可变引用 OK
println!("{r1} and {r2}");
                        // r1, r2 的最后一次使用结束于此

let r3 = &mut s;    // 可变引用 OK(r1/r2 已不再使用)
println!("{r3}");

// 关键:NLL(Non-Lexical Lifetimes,非词法生命周期)
// 作用域结束 ≠ 引用失效,引用在最后一次使用后就失效了

悬垂引用(Dangling References)------ Rust 在编译期防止

rust 复制代码
// 这段代码在 Rust 中无法编译(其他语言允许,是常见 bug 源)
fn dangle() -> &String {
    let s = String::from("hello");
    &s    // 返回指向局部变量的引用
}       // s 在此被 drop,引用变成悬垂指针
        // 编译器报错:missing lifetime specifier

// 正确写法:直接返回所有权
fn no_dangle() -> String {
    let s = String::from("hello");
    s      // 所有权移动出去,不会有悬垂引用
}

引用规则总结

复制代码
1. 在任意给定时间,要么只能有一个可变引用,要么只能有多个不可变引用
复制代码
2. 引用必须始终有效(不能悬垂)

2.3 切片(Slices)

字符串切片

rust 复制代码
let s = String::from("hello world");

let hello = &s[0..5];     // "hello"
let world = &s[6..11];    // "world"
let whole = &s[..];        // 等价于 &s[0..s.len()]
let half = &s[..5];       // 等价于 &s[0..5]

// 切片类型:&str(字符串字面量也是 &str)
let s: &str = "hello";    // 类型是 &str(不可变)

字符串切片的危险性 ------ UTF-8 边界

rust 复制代码
let s = String::from("你好");
// let h = &s[0..1];     // 运行时 panic!'你' 占 3 字节,不是 1 字节

// 正确做法:
let h = &s[0..3];         // "你"(3 字节)

函数参数中的字符串切片

rust 复制代码
// 接受 &String(仅持有 String 引用时可用)
fn first_word(s: &String) -> &str { ... }

// 接受 &str(&String 和字符串字面量都能用!更通用)
fn first_word(s: &str) -> &str { ... }

fn main() {
    let s = String::from("hello world");
    let word = first_word(&s);         // 传入 &String,自动解引用为 &str
    let word2 = first_word("literal");   // 传入 &str 字面量,也 OK
}

最佳实践:函数参数优先使用 &str****而非 &String**,更通用。**

数组切片

rust 复制代码
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];    // &[i32] 类型,值为 [2, 3]

切片在内存中的表示

复制代码
String:
复制代码
┌─────────┬─────┬───────────┐
复制代码
│ ptr     │ len │ capacity  │    ← 栈上
复制代码
├─────────┼─────┼───────────┤
复制代码
│         │     │           │
复制代码
│   ┌───────────────────┐  │
复制代码
│   │ h  e  l  l  o \0  │  │    ← 堆上
复制代码
│   └───────────────────┘  │
复制代码
└─────────┼─────┼───────────┘
复制代码
&str 切片:
复制代码
┌─────┬─────┐
复制代码
│ ptr │ len │     ← 栈上(没有 capacity!)
复制代码
└─────┴─────┘
复制代码
      │
复制代码
      └──→ 指向 String 堆数据的一部分

2.4 所有权决策树(速查)

复制代码
需要传递数据给函数吗?
复制代码
├── 函数需要修改数据?
复制代码
│   └── 是 → 传 &mut T(可变引用)
复制代码
└── 函数只需要读取?
复制代码
    ├── 数据较小(Copy 类型)→ 传值(自动复制)
复制代码
    └── 数据较大 → 传 &T(不可变引用)
复制代码
复制代码
需要从函数返回数据吗?
复制代码
├── 在函数内创建的数据 → 直接返回(所有权转移)
复制代码
└── 调用方仍需持有 → 先 clone 再传
复制代码
复制代码
不确定该用什么?
复制代码
→ 默认用引用(&T),编译器会告诉你何时需要所有权

学习目标检验

  • 能清晰解释所有权三条铁律
  • 能区分"移动"和"复制",知道哪些类型是 Copy
  • 能解释为什么 Rust 要禁止悬垂引用
  • 能写出正确的可变引用和不可变引用组合
  • 理解 NLL(非词法生命周期)的含义
  • 能正确使用字符串切片 &str 作为函数参数
  • 理解 String vs &str 的区别
相关推荐
草莓熊Lotso1 小时前
【LangChain】核心组件详解:文档加载器(Document Loaders)
开发语言·c++·python·langchain·软件工程
冻感糕人~1 小时前
大模型学习指南:收藏这份AI Agent四层工程地图(小白程序员必备)
java·大数据·人工智能·学习·大模型·agent·大模型学习
你怎么知道我是队长2 小时前
JavaScript的自适应效果
开发语言·javascript·ecmascript
曲三酒馆2 小时前
“强比命打,钱娃D写”——这8个字就是我人生的全部了![特殊字符]
学习·生活·娱乐
统计学小王子2 小时前
《数学少年-确定与不确定》(第四章:“骰子不骗人“)
学习·初中数学科普
其实防守也摸鱼2 小时前
前端应用的离线暂停更新策略:构建稳定可靠的渐进式部署方案
服务器·前端·数据库·学习·ai·命令行·linux系统
Dovis(誓平步青云)2 小时前
《如何在CentOS 7中添加Plex官方软件源:解决文件磁盘难管理难题》
linux·运维·服务器·后端·生成对抗网络·centos
mayaairi2 小时前
JS数组完全指南(含十大操作详解)
开发语言·前端·javascript
MartinYeung52 小时前
[论文学习]Mamba:具有选择性状态空间的线性时间序列建模
学习