Rust 所有权(Ownership)的使用小结

 更新时间:2026年01月14日 09:48:54   作者:FAFU_ppg  
Rust通过所有权系统管理内存,确保安全且无运行时性能损失,所有权系统的核心规则包括唯一所有者、所有权失效时值自动释放和值在任何时刻只能有一个所有者,本教程详细介绍了所有权的规则、栈与堆的区别以及所有权在变量赋值、函数传参和返回值中的应用

在rust编程中,内存管理是核心挑战之一。不同编程语言采用不同策略处理内存,而 Rust 独有的所有权系统,在编译期就能确保内存安全,且不影响运行时性能。本教程将从内存管理背景出发,逐步讲解所有权的核心概念、规则及实践应用。

一、内存管理的三种流派

所有程序都需与内存交互,如何申请、释放内存是语言设计的关键。目前主流内存管理方式分为三类:

管理方式核心逻辑典型语言优缺点分析
垃圾回收(GC)程序运行时自动扫描“不再使用的内存”并释放Java、Go优点:无需手动管理;缺点:运行时性能损耗
手动管理内存通过代码显式调用函数申请(如 malloc)和释放(如 free)内存C++优点:性能可控;缺点:易出现内存泄漏、野指针
所有权系统(Ownership)编译器通过预设规则在编译期检查内存使用,无运行时开销Rust优点:兼顾安全与性能;缺点:需理解新规则

Rust 选择所有权系统,核心优势是:内存安全检查仅在编译期执行,运行时无任何性能损失。

二、前置知识:栈(Stack)与堆(Heap)

Rust 的所有权规则与内存存储位置(栈/堆)紧密相关,理解二者差异是掌握所有权的基础。

1. 栈(Stack):有序、高效的内存区域

栈是后进先出(LIFO) 的数据结构,类似“叠盘子”——新数据放在顶部,取数据也从顶部开始,无法直接操作中间数据。

  • 存储要求:数据大小必须已知且固定(编译期可确定)。
  • 操作效率:极高。入栈(存数据)、出栈(取数据)仅需移动“栈指针”,无需复杂计算。
  • 典型存储数据:基本类型(如 i32、bool)、函数参数、局部变量。

2. 堆(Heap):灵活、无序的内存区域

堆用于存储大小未知或动态变化的数据。当需要存储数据时,程序会向操作系统申请一块“足够大的空闲内存”,操作系统标记该内存为“已使用”并返回内存地址(指针),程序再将指针存入栈中。

  • 存储流程:申请内存(分配)→ 操作系统返回指针 → 指针存入栈 → 通过指针访问堆数据。
  • 操作效率:较低。分配内存时需操作系统寻找空闲区域,访问数据需先通过栈指针跳转,步骤更多。
  • 典型存储数据:动态大小类型(如 String、数组)。

3. 栈与堆的性能对比

对比维度栈(Stack)堆(Heap)
分配速度极快(仅移动栈指针)较慢(需操作系统查找空闲内存)
访问速度快(直接访问)较慢(需通过栈指针跳转)
数据大小要求固定且已知(编译期确定)可动态变化(运行时确定)
自动释放函数结束后自动出栈释放需手动/所有权系统管理释放

三、所有权的核心规则

所有权是 Rust 管理堆内存的核心机制,必须牢记以下三条规则

  1. Rust 中每一个值都被一个变量“拥有”,该变量称为值的所有者
  2. 一个值同时只能有一个所有者(即“唯一所有权”)。
  3. 当所有者(变量)离开作用域时,该值会被自动丢弃(调用 drop 函数释放内存)。

1. 变量作用域:所有权的“生命周期”

作用域是变量的“有效范围”——变量从声明处开始生效,离开作用域后失效(触发 drop 释放内存)。这一点与其他语言(如 Java、C++)类似。

示例代码

#![allow(unused)]
fn main() {
    // 作用域1:s尚未声明,无效
    {                     
        let s = "hello";   // 作用域2:s声明,开始生效
        println!("{}", s); // 有效:可使用s
    }                      // 作用域2结束:s失效,自动释放内存
    // 作用域1:s已失效,无法使用
}

四、所有权的实践:以 String 类型为例

字符串是理解所有权的最佳案例——Rust 中有两种字符串类型:

  • 字符串字面值(&str):硬编码到程序中的不可变字符串(如 "hello"),存储在“常量区”,无需堆内存。
  • String 类型:动态可变字符串(如 String::from("hello")),存储在堆中,需所有权管理。

下面通过 String 类型,讲解所有权的核心交互场景。

1. 场景1:转移所有权(Move)

当变量绑定到“堆上数据”(如 String)时,赋值操作会触发所有权转移,而非数据拷贝。

为什么不直接拷贝?

String 由三部分组成(存储在栈中):

  • ptr:指向堆中字符串内容的指针;
  • len:字符串当前长度(已使用的内存);
  • capacity:字符串的总容量(分配的堆内存大小)。

若赋值时拷贝整个 String(包括堆中数据),会产生深拷贝,对性能消耗极大(尤其大字符串)。因此 Rust 采用“所有权转移”策略:

所有权转移示例

#![allow(unused)]
fn main() {
    let s1 = String::from("hello"); // s1 是 "hello" 的所有者
    let s2 = s1;                    // 所有权从 s1 转移到 s2
    // println!("{}", s1);          // 错误!s1 已失效,无法使用
    println!("{}", s2);             // 正确!s2 是当前所有者
}

转移后逻辑

  • s1 不再指向堆中数据,变为“无效状态”;
  • 只有 s2 拥有堆中数据的所有权;
  • 当 s2 离开作用域时,自动调用 drop 释放堆内存,避免“二次释放”(内存安全 bug)。

2. 场景2:克隆(Clone):主动深拷贝

若确实需要“完整拷贝堆中数据”(深拷贝),可使用 clone 方法。这是 Rust 中唯一主动触发深拷贝的方式,需显式调用(避免无意识的性能损耗)。

克隆示例

#![allow(unused)]
fn main() {
    let s1 = String::from("hello"); 
    let s2 = s1.clone(); // 深拷贝:栈中 String 结构 + 堆中字符串内容均拷贝
    
    println!("s1 = {}, s2 = {}", s1, s2); // 正确!s1 仍有效(未转移所有权)
}

注意:clone 性能开销较大,仅在必要时使用(如初始化程序、低频操作),避免在“热点代码”(高频执行逻辑)中调用。

3. 场景3:拷贝(Copy):栈上数据的自动浅拷贝

对于栈中存储的基本类型(如 i32、bool),赋值操作会自动触发“浅拷贝”——拷贝数据本身(而非转移所有权),原变量仍有效。

这是因为栈中数据大小固定、拷贝速度极快,无需通过“所有权转移”管理。Rust 为这类类型实现了 Copy 特征,标记其支持“自动拷贝”。

拷贝示例

#![allow(unused)]
fn main() {
    let x = 5; // 基本类型 i32,存储在栈中
    let y = x; // 自动拷贝:栈中数据 5 复制给 y,x 仍有效
    
    println!("x = {}, y = {}", x, y); // 正确!x 和 y 均有效
}

支持Copy特征的类型

满足“大小固定、无需堆内存”的类型均支持 Copy,常见类型包括:

  • 所有整数类型(u8、i32、u64 等);
  • 布尔类型(bool);
  • 所有浮点数类型(f32、f64);
  • 字符类型(char);
  • 元组(仅当所有元素均支持 Copy 时,如 (i32, bool) 支持,(i32, String) 不支持);
  • 不可变引用(&T,如 &str);

注意:可变引用(&mut T)不支持 Copy,避免多个可变引用修改同一数据导致冲突。

五、函数中的所有权

函数传参和返回值的过程,同样遵循所有权规则——本质与变量赋值一致(转移或拷贝)。

1. 函数传参:所有权的转移/拷贝

  • 若传入堆上类型(如 String):所有权从调用方转移到函数参数,调用方后续无法使用该变量;
  • 若传入栈上类型(如 i32):自动拷贝,调用方仍可使用原变量。

示例代码

fn main() {
    let s = String::from("hello");  // s 是 String 所有者(堆上数据)
    takes_ownership(s);             // 所有权转移到函数参数 some_string,s 失效
    // println!("{}", s);          // 错误!s 已失效

    let x = 5;                      // x 是 i32(栈上数据)
    makes_copy(x);                  // 自动拷贝,x 仍有效
    println!("x = {}", x);          // 正确!x 未转移所有权
}

// 接收 String 类型参数:参数进入作用域时获得所有权
fn takes_ownership(some_string: String) {
    println!("{}", some_string);
} // some_string 离开作用域,调用 drop 释放堆内存

// 接收 i32 类型参数:参数是 Copy 类型,拷贝后原变量仍有效
fn makes_copy(some_integer: i32) {
    println!("{}", some_integer);
} // some_integer 离开作用域,无堆内存需释放

2. 函数返回值:所有权的传递

函数返回值会将所有权传递给“接收返回值的变量”,本质是“转移所有权”。

示例代码

fn main() {
    // 1. 函数返回 String,所有权转移给 s1
    let s1 = gives_ownership();     

    // 2. s2 是 String 所有者
    let s2 = String::from("hello");  
    // 3. s2 所有权转移到函数,函数返回后转移给 s3
    let s3 = takes_and_gives_back(s2); 
    // println!("{}", s2);          // 错误!s2 已转移所有权
}

// 返回 String:将 some_string 的所有权转移给调用方
fn gives_ownership() -> String {
    let some_string = String::from("hello"); 
    some_string // 隐式返回,所有权转移
}

// 接收 String 并返回:先获得 a_string 的所有权,再转移给调用方
fn takes_and_gives_back(a_string: String) -> String {
    a_string // 所有权转移给调用方
}

六、总结

1. 核心要点回顾

  • 所有权是 Rust 解决内存安全的核心机制,编译期检查,无运行时开销;
  • 三条核心规则:唯一所有者、所有者失效则值释放、值仅一个所有者;
  • 堆上类型(如 String)赋值触发所有权转移,栈上类型(如 i32)赋值触发自动拷贝
  • 函数传参/返回值遵循所有权规则,本质是“转移或拷贝”。

到此这篇关于Rust 所有权(Ownership)的使用小结的文章就介绍到这了,更多相关Rust 所有权内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • rust 包模块组织结构详解

    rust 包模块组织结构详解

    RUST提供了一系列的功能来帮助我们管理代码,包括决定哪些细节是暴露的、哪些细节是私有的,以及不同的作用域的命名管理,这篇文章主要介绍了rust 包模块组织结构的相关知识,需要的朋友可以参考下
    2023-12-12
  • Rust生命周期之验证引用有效性与防止悬垂引用方式

    Rust生命周期之验证引用有效性与防止悬垂引用方式

    本文介绍了Rust中生命周期注解的应用,包括防止悬垂引用、在函数中使用泛型生命周期、生命周期省略规则、在结构体中使用生命周期、静态生命周期以及如何将生命周期与泛型和特质约束结合,通过这些机制,Rust在编译时就能捕获内存安全问题
    2025-02-02
  • 关于Rust命令行参数解析以minigrep为例

    关于Rust命令行参数解析以minigrep为例

    本文介绍了如何使用Rust的std::env::args函数来解析命令行参数,并展示了如何将这些参数存储在变量中,随后,提到了处理文件和搜索逻辑的步骤,包括读取文件内容、搜索匹配项和输出搜索结果,最后,总结了Rust标准库在命令行参数处理中的便捷性和社区资源的支持
    2025-02-02
  • Rust中的Drop特性之解读自动化资源清理的魔法

    Rust中的Drop特性之解读自动化资源清理的魔法

    Rust通过Drop特性实现了自动清理机制,确保资源在对象超出作用域时自动释放,避免了手动管理资源时可能出现的内存泄漏或双重释放问题,智能指针如Box、Rc和RefCell都依赖于Drop来管理资源,提供了灵活且安全的资源管理方案
    2025-02-02
  • Rust 所有权机制原理深入剖析

    Rust 所有权机制原理深入剖析

    这篇文章主要为大家介绍了Rust 所有权机制原理深入剖析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-01-01
  • 深入了解Rust的生命周期

    深入了解Rust的生命周期

    生命周期指的是引用保持有效的作用域,Rust的每个引用都有自己的生命周期。本文将通过示例和大家详细说说Rust的生命周期,需要的可以参考一下
    2022-12-12
  • 使用Rust语言管理Node.js版本

    使用Rust语言管理Node.js版本

    这篇文章主要介绍一个使用 Rust 进行编写的一体化版本管理工具 Rtx,比如使用它来管理 Node.js 版本,它很简单易用,使用了它,就可以抛弃掉 nvm 了,文中通过代码示例给大家介绍的非常详细,需要的朋友可以参考下
    2023-12-12
  • Rust 语言中符号 :: 的使用场景解析

    Rust 语言中符号 :: 的使用场景解析

    Rust 是一种强调安全性和速度的系统编程语言,这篇文章主要介绍了Rust 语言中符号 :: 的使用场景,本文给大家介绍的非常详细,需要的朋友可以参考下
    2024-03-03
  • rust闭包的使用

    rust闭包的使用

    闭包在Rust中是非常强大的功能,允许你编写更灵活和表达性的代码,本文主要介绍了rust闭包的使用,具有一定的参考价值,感兴趣的可以了解一下
    2023-12-12
  • Rust 文档注释功能示例代码

    Rust 文档注释功能示例代码

    Rust的文档注释使用特定的格式,以便通过 rustdoc工具生成 API 文档,本文给大家介绍Rust 文档注释功能,感兴趣的朋友跟随小编一起看看吧
    2024-04-04

最新评论