Rust 所有权与核心概念完整指南

2859 字
14 分钟
Rust 所有权与核心概念完整指南

1. 字符串字面量 &str#

let s = "hello"; // 类型:&str

特点:

  • 存储在栈上(实际在二进制文件的只读内存中)
  • 不可变
  • 编译时已知大小
  • 是对固定字符串的引用(借用)
  • 零开销,最高效

2. String 类型#

let s = String::from("hello"); // 类型:String

特点:

  • 数据存储在堆上
  • 可变(可以修改)
  • 运行时动态分配内存
  • 拥有数据的所有权
  • 离开作用域时自动释放内存

3. 两者对比#

特性&strString
存储位置栈(只读内存)堆
可变性不可变可变
大小编译时已知运行时动态
所有权借用拥有
性能最快稍慢(堆分配)

4. 相互转换#

// &str -> String
let s1 = "hello".to_string();
let s2 = String::from("hello");
// String -> &str
let s3 = String::from("hello");
let s4 = &s3[0..3]; // "hel"
let s5 = &s3; // 自动转换

第二部分:可变性(mut 关键字)#

1. 基本概念#

let x = 5; // 不可变(默认)
let mut y = 5; // 可变
y = 10; // ✅ 可以修改
// x = 10; // ❌ 编译错误!

2. 为什么默认不可变?#

  • 防止意外修改
  • 更容易推理代码
  • 编译器可以做更多优化
  • 内存安全保证

3. 可变性的应用场景#

// 1. 变量绑定
let mut count = 0;
count += 1;
// 2. 函数参数
fn increment(mut num: i32) {
num += 1;
}
// 3. String 的可变性
let mut s = String::from("hello");
s.push_str(" world"); // 可以修改
// 4. 结构体字段
struct User {
name: String,
age: u32,
}
let mut user = User { name: "Alice".to_string(), age: 30 };
user.age = 31; // 整个结构体必须是 mut

4. mut 与所有权的关系#

let mut s = String::from("hello");
let t = &mut s; // 可变引用
t.push_str(" world");
// let u = &mut s; // ❌ 不能同时有多个可变引用

第三部分:内存分配(栈 vs 堆)#

1. 栈(Stack)#

特点:

  • 后进先出(LIFO)
  • 存储已知大小的数据
  • 分配和释放极快
  • 函数调用时自动管理

存储内容:

let a = 5; // 整数
let b = true; // 布尔
let c = 'A'; // 字符
let d = [1, 2, 3]; // 数组(固定大小)
let e = (4, 5.6); // 元组(固定大小)
let f = "hello"; // 字符串字面量(&str)

2. 堆(Heap)#

特点:

  • 动态分配
  • 存储未知大小或需要增长的数据
  • 分配较慢
  • 需要手动或自动(Rust)管理

存储内容:

let s = String::from("hello"); // 字符串内容
let v = vec![1, 2, 3]; // 向量内容
let b = Box::new(42); // Box 数据

3. 内存布局图解#

let s = String::from("hello");
栈(Stack) 堆(Heap)
┌─────────────┐ ┌─────────────┐
│ s (指针) │────────>│ "hello" │
│ 长度: 5 │ │ h e l l o │
│ 容量: 5 │ └─────────────┘
└─────────────┘

4. 可变性与存储位置无关#

变量可变性存储位置示例
let x = 5不可变栈整数、布尔、字符
let mut x = 5可变栈同上,但可改值
let s = String::from("hi")不可变栈指针→堆数据字符串内容在堆上
let mut s = String::from("hi")可变栈指针→堆数据可以修改堆上内容

第四部分:Box<T> 智能指针#

1. 什么是 Box?#

Box<T> 是 Rust 提供的堆内存分配器:

let x = 5; // 存储在栈上
let y = Box::new(5); // 存储在堆上,栈上只存指针

2. 内存布局#

let boxed = Box::new(42);
栈(Stack) 堆(Heap)
┌─────────────┐ ┌─────────────┐
│ boxed │────────>│ 42 │
│ (指针) │ │ (实际数据) │
└─────────────┘ └─────────────┘

3. Box 的主要用途#

(1) 递归数据结构#

enum List {
Cons(i32, Box<List>), // Box 让递归成为可能
Nil,
}
// 如果不用 Box
// enum List {
// Cons(i32, List), // ❌ 编译错误:无限大小!
// Nil,
// }

(2) 特征对象(多态)#

trait Animal {
fn sound(&self);
}
struct Dog;
impl Animal for Dog {
fn sound(&self) { println!("汪汪!"); }
}
struct Cat;
impl Animal for Cat {
fn sound(&self) { println!("喵喵!"); }
}
let animals: Vec<Box<dyn Animal>> = vec![
Box::new(Dog),
Box::new(Cat),
];

(3) 让枚举大小固定#

enum Data {
Small(Vec<u8>),
Large(Box<Vec<u8>>), // Box 让枚举大小固定
}

4. Box 的自动解引用#

let mut boxed = Box::new(vec![1, 2, 3]);
boxed.push(4); // ✅ 自动解引用,直接使用
boxed[0] = 42; // ✅ 自动解引用
// 特殊情况需要手动解引用
let mut b = Box::new(5);
*b = 10; // ✅ 修改值需要 *

5. Box vs 普通变量#

特性普通变量Box
存储位置栈上堆上
内存大小实际数据大小8字节(指针)
所有权直接拥有数据拥有堆上数据
性能更快稍慢(间接访问)

第五部分:Vec<T> 泛型集合#

1. 基本概念#

Vec<u8> // 存储 u8 类型(无符号8位整数)
Vec<i32> // 存储 i32 类型
Vec<String> // 存储 String 类型
Vec<f64> // 存储 f64 类型

2. 类型大小#

类型单个元素大小范围
u81 字节0 ~ 255
i324 字节-2,147,483,648 ~ 2,147,483,647
f648 字节±1.8e308(双精度)
String24 字节(指针+长度+容量)动态

3. 内存占用#

// Vec 本身在栈上占 24 字节
// 堆上数据 = 元素个数 × 每个元素大小
let v1: Vec<u8> = vec![0; 1000]; // 堆上占 1000 字节
let v2: Vec<i32> = vec![0; 1000]; // 堆上占 4000 字节

4. 重要概念#

// Vec 的数据在堆上,不是栈上!
let huge_array = vec![0; 1000000]; // 数据在堆上
// 转移所有权只复制指针(高效)
let vec1 = vec![0; 1000000];
let vec2 = vec1; // 只复制了3个字节(指针、长度、容量)
// vec1 不再可用
// clone 会复制所有数据(慢)
let vec3 = vec![0; 1000000];
let vec4 = vec3.clone(); // 复制了100万个元素

第六部分:枚举(enum)#

1. Rust enum vs Python enum#

Python:简单枚举值

from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2

Rust:强大的标签联合(可以携带数据)

enum Color {
Red,
Green,
Blue,
}
enum Message {
Quit, // 无数据
Move { x: i32, y: i32 }, // 结构体数据
Write(String), // 单个数据
ChangeColor(u8, u8, u8), // 元组数据
}

2. 枚举变体#

enum List {
Cons(i32, Box<List>), // 变体1:包含值和下一个节点
Nil, // 变体2:空链表
}
// 使用变体
let node = List::Cons(5, Box::new(List::Nil));

3. 使用 use 简化路径#

// 完整路径
let list = List::Cons(1, Box::new(List::Nil));
// 导入后简化
use List::{Cons, Nil};
let list = Cons(1, Box::new(Nil));

4. 匹配枚举#

match small_data {
Data::Small(vec) => println!("小数据:{:?}", vec),
Data::Large(vec) => println!("大数据:长度 {}", vec.len()),
}

第七部分:Cons 链表#

1. Cons 是什么?#

Cons 来自 Lisp 语言,是 “Constructor” 的缩写:

  • Cons = 一个节点,包含当前值和指向下一个节点的指针
  • Nil = 空链表(结束标志)

2. 链表定义#

enum List {
Cons(i32, Box<List>), // 值 + 指向下一个节点的指针
Nil, // 空
}
// 链表:1 -> 2 -> 3 -> Nil
// Cons(1, Cons(2, Cons(3, Nil)))

3. 构建链表#

// 从尾部开始构建
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
// 逐步构建
let tail = Cons(3, Box::new(Nil));
let mid = Cons(2, Box::new(tail));
let head = Cons(1, Box::new(mid));

4. 内存布局#

栈 堆
┌──────────────┐ ┌──────────────┐
│ list (头部) │──────────>│ Cons(1) │
└──────────────┘ │ next ──────┐│
└─────────────┴│
▼
┌──────────────┐
│ Cons(2) │
│ next ──────┐│
└─────────────┴│
▼
┌──────────────┐
│ Cons(3) │
│ next ──────┐│
└─────────────┴│
▼
┌──────────────┐
│ Nil │
└──────────────┘

5. 更优雅的构建方式#

// 使用标准库
use std::collections::LinkedList;
let mut list = LinkedList::new();
list.extend([1, 2, 3]);
// 使用 Vec(推荐!)
let list = vec![1, 2, 3]; // 90% 的情况用 Vec 就够了

第八部分:操作符 . vs ::#

1. 核心规则#

// . 用于实例(已经创建的对象)
// :: 用于类型/模块(命名空间)

2. . 点操作符#

用于实例的字段和方法:

struct Person {
name: String,
age: u32,
}
impl Person {
fn greet(&self) {
println!("你好,我是{}", self.name);
}
}
let p = Person { name: "Alice".to_string(), age: 30 };
p.greet(); // ✅ 调用实例方法
println!("{}", p.name); // ✅ 访问实例字段

3. :: 双冒号操作符#

用于类型级别的东西:

// 1. 关联函数(类似静态方法)
let s = String::from("hello");
let v = Vec::new();
// 2. 枚举变体
let c = Color::Red;
// 3. 模块路径
use std::collections::HashMap;
// 4. 关联常量
struct Config;
impl Config {
const VERSION: u32 = 1;
}
println!("{}", Config::VERSION);

4. 完整对比表#

操作符使用场景示例
.访问实例字段person.name
.调用实例方法person.greet()
.访问元组索引tuple.0
::调用关联函数String::from("hi")
::访问关联常量MyStruct::VERSION
::访问枚举变体Color::Red
::模块路径std::collections::HashMap

5. 从 Python 角度理解#

class Config:
DEFAULT = 100 # 类属性(类似关联常量)
def __init__(self, value):
self.value = value # 实例属性
print(Config.DEFAULT) # 类属性访问
config = Config(42)
print(config.value) # 实例属性访问
struct Config {
value: i32,
}
impl Config {
const DEFAULT: i32 = 100; // 关联常量
}
println!("{}", Config::DEFAULT); // :: 访问
let config = Config { value: 42 };
println!("{}", config.value); // . 访问

第九部分:解引用运算符 *#

1. 基本概念#

* 是解引用运算符,用于”跟随指针拿到数据”。

2. 使用场景#

// 1. 栈上的引用
let x = 42;
let r = &x;
println!("{}", *r); // 解引用,拿到 42
// 2. 堆上的数据(Box)
let b = Box::new(100);
println!("{}", *b); // 解引用,拿出 100

3. 自动解引用#

Rust 会自动解引用,大多数情况不需要手动 *:

let mut boxed = Box::new(vec![1, 2, 3]);
boxed.push(4); // ✅ 自动解引用
println!("{}", boxed[0]); // ✅ 自动解引用

4. 何时需要手动 *#

// 1. 修改 Box 的值
let mut b = Box::new(5);
*b = 10; // ✅ 必须用 *
// 2. 取出所有权
let b = Box::new(String::from("hello"));
let s = *b; // ✅ 取出堆上的 String
// 3. 自定义类型的访问
struct Point { x: i32, y: i32 }
let p = Box::new(Point { x: 1, y: 2 });
println!("{}", (*p).x); // 可以,但 p.x 更常见

5. 解引用总结#

操作符作用何时需要
*解引用(跟随指针)修改 Box 的值、取出所有权
.自动解引用调用方法时自动处理
[]索引(自动解引用)访问 Vec 等容器元素

第十部分:完整代码示例#

// 1. 字符串类型
fn string_examples() {
let s1 = "hello"; // &str,栈上
let s2 = String::from("world"); // String,堆上
let mut s3 = String::from("hello");
s3.push_str(" world"); // 可以修改
println!("{}", s3);
}
// 2. Box 使用
fn box_examples() {
let b = Box::new(42);
println!("{}", *b); // 解引用
let mut b2 = Box::new(10);
*b2 = 20; // 修改
println!("{}", b2); // 自动解引用
}
// 3. 枚举和链表
enum List {
Cons(i32, Box<List>),
Nil,
}
fn list_examples() {
use List::{Cons, Nil};
let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
// 遍历
let mut current = &list;
while let Cons(value, next) = current {
print!("{} -> ", value);
current = next;
}
println!("Nil");
}
// 4. Vec 使用
fn vec_examples() {
let mut v = vec![1, 2, 3];
v.push(4);
v[0] = 10;
println!("{:?}", v); // [10, 2, 3, 4]
// Vec 数据在堆上
let big = vec![0; 1000000];
let moved = big; // 所有权转移,只复制指针
// println!("{}", big[0]); // ❌ big 已失效
}
// 5. 结构体和关联常量
struct Config {
pub value: i32,
}
impl Config {
const DEFAULT: i32 = 100;
fn new(value: i32) -> Self {
Config { value }
}
fn get_value(&self) -> i32 {
self.value
}
}
fn struct_examples() {
println!("{}", Config::DEFAULT); // :: 访问关联常量
let config = Config::new(42); // :: 调用关联函数
println!("{}", config.value); // . 访问字段
println!("{}", config.get_value()); // . 调用方法
}
// 6. main 函数
fn main() {
string_examples();
box_examples();
list_examples();
vec_examples();
struct_examples();
}

总结:核心要点速查#

内存管理#

  • 栈:固定大小,快速,自动管理
  • 堆:动态大小,较慢,需要显式管理(Rust 自动清理)

所有权规则#

  1. 每个值都有一个所有者
  2. 同一时间只能有一个所有者
  3. 所有者离开作用域时值被丢弃

可变性#

  • 默认不可变(let)
  • 需要可变时用 mut
  • 可变性与存储位置无关

字符串#

  • &str:不可变,栈上,借用
  • String:可变,堆上,拥有所有权

智能指针#

  • Box<T>:堆分配,拥有所有权
  • 用于递归类型、特征对象、固定枚举大小

集合#

  • Vec<T>:动态数组,数据在堆上
  • LinkedList:双向链表,标准库提供

操作符#

  • .:实例级别(字段、方法)
  • :::类型级别(关联函数、常量、枚举变体)
  • *:解引用(跟随指针)

枚举#

  • 强大的标签联合
  • 可以携带数据
  • 变体可以用 use 导入

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Rust 所有权与核心概念完整指南
https://guoxin.wiki/posts/rust-ownership-guide/
作者
Guoxin
发布于
2026-09-20
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Guoxin
写代码,也写思考。
公告
博客初建,笔记陆续搬家中。所有文章均经脱敏检查后发布。
分类
标签
最新动态
站点统计
文章
12
分类
6
标签
24
总字数
13,485
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Unknown CI
博客版本
Firefly v6.16.8
文章许可
CC BY-NC-SA 4.0
文章目录