Rust

2025年09月25日更新 8 人订阅
原价: ¥ 6 限时优惠
专栏简介 Rust编程语言之错误处理 Rust 语言之 flod Rust编程语言之Cargo、Crates.io详解 Rust编程语言之枚举与模式匹配 Rust语言 - 接口设计的建议之受约束(Constrained) Rust编程语言之无畏并发 Rust语言 - 接口设计的建议之灵活(flexible) Rust语言 - 接口设计的建议之显而易见(Obvious) Rust语言 - 接口设计的建议之不意外(unsurprising) Rust 实战:构建实用的 CLI 工具 HTTPie Rust编程语言学习之高级特性 Rust内存管理揭秘:深度剖析指针与智能指针 解决Rust中数组和切片的编译时大小问题 《Rust编程之道》学习笔记一 Rust Async 异步编程 简易教程 使用 Async Rust 构建简单的 P2P 节点 Rust编程语言入门之模式匹配 Rust async 编程 Rust编程语言之编写自动化测试 Rust编程语言之函数式语言特性:迭代器和闭包 《Rust编程之道》学习笔记二 Rust Tips 比较数值 使用 Rust 开发一个微型游戏 Rust编程初探:深入理解Struct结构体 深入理解Rust中的内存管理:栈、堆与静态内存详解 深入理解 Rust 结构体:经典结构体、元组结构体和单元结构体的实现 深入掌握 Rust 结构体:从模板到实例化的完整指南 深入理解Rust中的结构体:逻辑与数据结合的实战示例 深入理解 Rust 枚举:从基础到实践 掌握Rust字符串的精髓:String与&str的最佳实践 全面解析 Rust 模块系统:实战案例与应用技巧 Rust 中的 HashMap 实战指南:理解与优化技巧 掌握Rust模式匹配:从基础语法到实际应用 Rust 中的面向对象编程:特性与实现指南 深入理解 Rust 的 Pin 和 Unpin:理论与实践解析 Rust Trait 与 Go Interface:从设计到实战的深度对比 从零开始:用 Rust 和 Axum 打造高效 Web 应用 Rust 错误处理详解:掌握 anyhow、thiserror 和 snafu Rust 如何优雅实现冒泡排序 链表倒数 K 节点怎么删?Python/Go/Rust 实战 用 Rust 玩转数据存储:JSON 文件持久化实战 Rust实战:打造高效字符串分割函数 如何高效学习一门技术:从知到行的飞轮效应 Rust 编程入门:Struct 让代码更优雅 Rust 编程:零基础入门高性能开发 用 Rust 写个猜数游戏,编程小白也能上手! Rust 入门教程:变量到数据类型,轻松掌握! 深入浅出 Rust:函数、控制流与所有权核心特性解析 从零开始:用 Rust 和 Axum 打造高效 Web 服务 Rust 集合类型解析:Vector、String、HashMap 深入浅出Rust:泛型、Trait与生命周期的硬核指南 Rust实战:博物馆门票限流系统设计与实现 用 Rust 打造高性能图片处理服务器:从零开始实现类似 Thumbor 的功能 Rust 编程入门实战:从零开始抓取网页并转换为 Markdown 深入浅出 Rust:高效处理二进制数据的 Bytes 与 BytesMut 实战 Rust智能指针:解锁内存管理的进阶之道 用 Rust 打造命令行利器:从零到一实现 mini-grep 解锁Rust代码组织:轻松掌握Package、Crate与Module Rust 所有权:从内存管理到生产力释放 深入解析 Rust 的面向对象编程:特性、实现与设计模式 Rust + Protobuf:从零打造高效键值存储项目 bacon 点燃 Rust:比 cargo-watch 更爽的开发体验 用 Rust 打造微型游戏:从零开始的 Flappy Dragon 开发之旅 函数式编程的Rust之旅:闭包与迭代器的深入解析与实践 探索Rust编程之道:从设计哲学到内存安全的学习笔记 精读《Rust编程之道》:吃透语言精要,彻底搞懂所有权与借用 Rust 避坑指南:搞定数值比较,别再让 0.1 + 0.2 != 0.3 困扰你! 告别 Vec!掌握 Rust bytes 库,解锁零拷贝的真正威力 告别竞态条件:基于 Axum 和 Serde 的 Rust 并发状态管理最佳实践 Rust 异步编程实践:从 Tokio 基础到阻塞任务处理模式 Rust 网络编程实战:用 Tokio 手写一个迷你 TCP 反向代理 (minginx) 保姆级教程:Zsh + Oh My Zsh 终极配置,让你的 Ubuntu 终端效率倍增 不止于后端:Rust 在 Web 开发中的崛起之路 (2024数据解读) Rust核心利器:枚举(Enum)与模式匹配(Match),告别空指针,写出优雅健壮的代码 Rust 错误处理终极指南:从 panic! 到 Result 的优雅之道 想用 Rust 开发游戏?这份超详细的入门教程请收好! 用 Rust 实现 HTTPie:一个现代 CLI 工具的构建过程 Rust 异步实战:从0到1,用 Tokio 打造一个高性能并发聊天室 深入 Rust 核心:彻底搞懂指针、引用与智能指针 Rust 生产级后端实战:用 Axum + sqlx 打造高性能短链接服务 深入 Rust 内存模型:栈、堆、所有权与底层原理 Rust 核心概念解析:引用、借用与内部可变性 掌握 Rust 核心:生命周期与借用检查全解析 Rust 内存布局深度解析:从对齐、填充到 repr 属性 Rust Trait 分派机制:静态与动态的抉择与权衡 Rust Thread::Builder 用法详解:线程命名与栈大小设置 Rust 泛型 Trait:关联类型与泛型参数的核心区别 Rust Scoped Threads 实战:更安全、更简洁的并发编程 Rust 核心设计:孤儿规则与代码一致性解析 Rust 实战:从零构建一个多线程 Web 服务器 Rust Web 开发实战:构建教师管理 API 硬核实战:从零到一,用 Rust 和 Axum 构建高性能聊天服务后端 Rust Web 开发实战:使用 SQLx 连接 PostgreSQL 数据库 硬核入门:从零开始,用 Actix Web 构建你的第一个 Rust REST API (推荐 🔥) Rust 并发编程:详解线程间数据共享的几种核心方法 Rust并发安全基石:Mutex与RwLock深度解析 Rust Web实战:构建优雅的 Actix Web 统一错误处理 煮咖啡里的大学问:用 Rust Async/Await 告诉你如何边烧水边磨豆 深入浅出:Rust 原子类型与多线程编程实践 Rust 并发编程利器:OnceCell 与 OnceLock 深度解析 Rust 懒人编程:LazyCell 与 LazyLock 的惰性哲学 Rust 入门精髓:详解 Enum 的三种魔法,从模式匹配到状态管理 Rust 字符串魔法:String 与 &str 的深度解析与实践 Rust 模块化编程:驾驭代码结构与可见性的三大法则 Rust 实用进阶:深度剖析 Rust 生命周期的奥秘 Rust 智能指针大揭秘:Box、Rc、Arc、Cow 深度剖析与应用实践

Rust 智能指针大揭秘:Box、Rc、Arc、Cow 深度剖析与应用实践

Rust智能指针大揭秘:Box、Rc、Arc、Cow深度剖析与应用实践在Rust的世界里,智能指针扮演着至关重要的角色,它们不仅是内存管理的安全卫士,更是构建复杂、高效程序的强大工具。不同于C++等语言的手动内存管理,Rust通过所有权系统和智能指针,在编译期保障内存安全,杜绝了悬垂

Rust 智能指针大揭秘:Box、Rc、Arc、Cow 深度剖析与应用实践

在 Rust 的世界里,智能指针扮演着至关重要的角色,它们不仅是内存管理的安全卫士,更是构建复杂、高效程序的强大工具。不同于 C++ 等语言的手动内存管理,Rust 通过所有权系统和智能指针,在编译期保障内存安全,杜绝了悬垂指针和内存泄漏等常见错误。

本文将通过四个经典代码示例,带你深入理解 Rust 中四种核心智能指针:BoxRcArcCow。从解决递归类型大小不确定性的 Box,到实现多所有权的 RcArc,再到优化性能的写时克隆智能指针 Cow,我们将逐一揭示它们的设计哲学、工作原理以及在实际编程中的应用场景,助你写出更安全、更高效、更具表现力的 Rust 代码。

实操

示例一

// box1.rs
//
// At compile time, Rust needs to know how much space a type takes up. This
// becomes problematic for recursive types, where a value can have as part of
// itself another value of the same type. To get around the issue, we can use a
// `Box` - a smart pointer used to store data on the heap, which also allows us
// to wrap a recursive type.
//
// The recursive type we're implementing in this exercise is the `cons list` - a
// data structure frequently found in functional programming languages. Each
// item in a cons list contains two elements: the value of the current item and
// the next item. The last item is a value called `Nil`.

#[derive(PartialEq, Debug)]
pub enum List {
    Cons(i32, Box<List>),
    Nil,
}

fn main() {
    println!("This is an empty cons list: {:?}", create_empty_list());
    println!(
        "This is a non-empty cons list: {:?}",
        create_non_empty_list()
    );
}

pub fn create_empty_list() -> List {
    List::Nil
}

pub fn create_non_empty_list() -> List {
    List::Cons(1, Box::new(List::Nil))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_create_empty_list() {
        assert_eq!(List::Nil, create_empty_list())
    }

    #[test]
    fn test_create_non_empty_list() {
        assert_ne!(create_empty_list(), create_non_empty_list())
    }
}

这段 Rust 代码展示了如何用 Box 智能指针来创建一个递归数据结构,即 cons list(链表)。由于递归类型(List)的大小在编译时是未知的(它可能包含无限个 Cons 变体),直接在栈上存储会引发编译错误。Box 通过将数据存储在堆上,并只在栈上保留一个固定大小的指针来巧妙地解决了这个问题。代码中的 List::Cons(i32, Box<List>) 变体就通过 Box<List> 来引用链表中的下一个元素,从而实现了递归定义。create_empty_list 函数返回一个 List::Nil 作为链表的末尾,而 create_non_empty_list 则创建了一个包含一个元素的链表,其末尾指向 List::Nil

示例二

// rc1.rs
//
// In this exercise, we want to express the concept of multiple owners via the
// Rc<T> type. This is a model of our solar system - there is a Sun type and
// multiple Planets. The Planets take ownership of the sun, indicating that they
// revolve around the sun.

use std::rc::Rc;

#[derive(Debug)]
struct Sun {}

#[derive(Debug)]
enum Planet {
    Mercury(Rc<Sun>),
    Venus(Rc<Sun>),
    Earth(Rc<Sun>),
    Mars(Rc<Sun>),
    Jupiter(Rc<Sun>),
    Saturn(Rc<Sun>),
    Uranus(Rc<Sun>),
    Neptune(Rc<Sun>),
}

impl Planet {
    fn details(&self) {
        println!("Hi from {:?}!", self)
    }
}

fn main() {
    let sun = Rc::new(Sun {});
    println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference

    let mercury = Planet::Mercury(Rc::clone(&sun));
    println!("reference count = {}", Rc::strong_count(&sun)); // 2 references
    mercury.details();

    let venus = Planet::Venus(Rc::clone(&sun));
    println!("reference count = {}", Rc::strong_count(&sun)); // 3 references
    venus.details();

    let earth = Planet::Earth(Rc::clone(&sun));
    println!("reference count = {}", Rc::strong_count(&sun)); // 4 references
    earth.details();

    let mars = Planet::Mars(Rc::clone(&sun));
    println!("reference count = {}", Rc::strong_count(&sun)); // 5 references
    mars.details();

    let jupiter = Planet::Jupiter(Rc::clone(&sun));
    println!("reference count = {}", Rc::strong_count(&sun)); // 6 references
    jupiter.details();

    let saturn = Planet::Saturn(Rc::clone(&sun));
    println!("reference count = {}", Rc::strong_count(&sun)); // 7 references
    saturn.details();

    let uranus = Planet::Uranus(Rc::clone(&sun));
    println!("reference count = {}", Rc::strong_count(&sun)); // 8 references
    uranus.details();

    let neptune = Planet::Neptune(Rc::clone(&sun));
    println!("reference count = {}", Rc::strong_count(&sun)); // 9 references
    neptune.details();

    assert_eq!(Rc::strong_count(&sun), 9);

    drop(neptune);
    println!("reference count = {}", Rc::strong_count(&sun)); // 8 references

    drop(uranus);
    println!("reference count = {}", Rc::strong_count(&sun)); // 7 references

    drop(saturn);
    println!("reference count = {}", Rc::strong_count(&sun)); // 6 references

    drop(jupiter);
    println!("reference count = {}", Rc::strong_count(&sun)); // 5 references

    drop(mars);
    println!("reference count = {}", Rc::strong_count(&sun)); // 4 references

    drop(earth); // 3 references
    println!("reference count = {}", Rc::strong_count(&sun)); // 3 references

    drop(venus); // 2 references
    println!("reference count = {}", Rc::strong_count(&sun)); // 2 references

    drop(mercury); // 1 reference
    println!("reference count = {}", Rc::strong_count(&sun)); // 1 reference

    assert_eq!(Rc::strong_count(&sun), 1);
}

这段 Rust 代码使用 Rc<T> 智能指针来模拟太阳系中“多所有权”的关系。Rc<T>,全称是引用计数(Reference Counted),允许一个数据拥有多个所有者。代码创建了一个 Sun 实例,并使用 Rc::new 将其包裹起来,使其可以被共享。接着,通过 Rc::clone 方法为每一颗行星 (Planet) 创建 Sun 的共享所有权。Rc::clone 仅仅是增加引用计数,而不是深度克隆数据,这使得内存开销非常小。每当一个行星被创建,太阳的引用计数(通过 Rc::strong_count 查看)就会增加,表示又多了一个“所有者”。当行星变量被销毁(例如通过 drop 或超出作用域),引用计数就会自动减少。只有当引用计数归零时,Rc<T> 才会释放其包裹的数据,完美地展示了多所有权以及安全的内存管理。

示例三

// arc1.rs
//
// In this exercise, we are given a Vec of u32 called "numbers" with values
// ranging from 0 to 99 -- [ 0, 1, 2, ..., 98, 99 ] We would like to use this
// set of numbers within 8 different threads simultaneously. Each thread is
// going to get the sum of every eighth value, with an offset.
//
// The first thread (offset 0), will sum 0, 8, 16, ...
// The second thread (offset 1), will sum 1, 9, 17, ...
// The third thread (offset 2), will sum 2, 10, 18, ...
// ...
// The eighth thread (offset 7), will sum 7, 15, 23, ...

#![forbid(unused_imports)] 
use std::sync::Arc;
use std::thread;

fn main() {
    let numbers: Vec<_> = (0..100u32).collect();
    let shared_numbers = Arc::new(numbers);
    let mut joinhandles = Vec::new();

    for offset in 0..8 {
        let child_numbers = Arc::clone(&shared_numbers);
        joinhandles.push(thread::spawn(move || {
            let sum: u32 = child_numbers.iter().filter(|&&n| n % 8 == offset).sum();
            println!("Sum of offset {} is {}", offset, sum);
        }));
    }
    for handle in joinhandles.into_iter() {
        handle.join().unwrap();
    }
}

这段 Rust 代码利用 Arc<T> 智能指针实现了安全的多线程数据共享Arc 代表 Atomic Reference Counted(原子引用计数),它类似于 Rc,但专为多线程环境设计,确保即使在多个线程同时访问时,引用计数的增减也是安全的。程序首先创建了一个包含 0 到 99 的数字向量,并使用 Arc::new 将其包裹,以便在线程间共享。在循环中,它生成了八个独立的线程,每个线程通过 Arc::clone 获取一个 Arc 智能指针的克隆。Arc::clone 同样只增加引用计数,不会复制底层数据,这使得多个线程能高效地共享同一份只读数据。每个线程根据自己的 offset 过滤并求和,最后 handle.join() 确保主线程会等待所有子线程执行完毕后再退出,完美展示了 Arc 在并发编程中提供安全、高效共享数据的功能。

示例四

// cow1.rs
//
// This exercise explores the Cow, or Clone-On-Write type. Cow is a
// clone-on-write smart pointer. It can enclose and provide immutable access to
// borrowed data, and clone the data lazily when mutation or ownership is
// required. The type is designed to work with general borrowed data via the
// Borrow trait.

use std::borrow::Cow;

fn abs_all<'a, 'b>(input: &'a mut Cow<'b, [i32]>) -> &'a mut Cow<'b, [i32]> {
    for i in 0..input.len() {
        let v = input[i];
        if v < 0 {
            // Clones into a vector if not already owned.
            input.to_mut()[i] = -v;
        }
    }
    input
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn reference_mutation() -> Result<(), &'static str> {
        // Clone occurs because `input` needs to be mutated.
        let slice = [-1, 0, 1];
        let mut input = Cow::from(&slice[..]);
        match abs_all(&mut input) {
            Cow::Owned(_) => Ok(()),
            _ => Err("Expected owned value"),
        }
    }

    #[test]
    fn reference_no_mutation() -> Result<(), &'static str> {
        // No clone occurs because `input` doesn't need to be mutated.
        let slice = [0, 1, 2];
        let mut input = Cow::from(&slice[..]);
        match abs_all(&mut input) {
            Cow::Borrowed(_) => Ok(()),
            _ => Err("Expected borrowed value"),
        }
    }

    #[test]
    fn owned_no_mutation() -> Result<(), &'static str> {
        // We can also pass `slice` without `&` so Cow owns it directly. In this
        // case no mutation occurs and thus also no clone, but the result is
        // still owned because it was never borrowed or mutated.
        let slice = vec![0, 1, 2];
        let mut input = Cow::from(slice);
        match abs_all(&mut input) {
            Cow::Owned(_) => Ok(()),
            _ => Err("Expected owned value"),
        }
    }

    #[test]
    fn owned_mutation() -> Result<(), &'static str> {
        // Of course this is also the case if a mutation does occur. In this
        // case the call to `to_mut()` returns a reference to the same data as
        // before.
        let slice = vec![-1, 0, 1];
        let mut input = Cow::from(slice);
        match abs_all(&mut input) {
            Cow::Owned(_) => Ok(()),
            _ => Err("Expected owned value"),
        }
    }
}

Cow 是 Rust 中的一个智能指针,全称为 Clone-on-Write。它在处理数据时非常灵活,可以根据需要自动在借用拥有两种状态间切换,从而优化性能。

Cow 类型解析

Cow 的核心思想是“写时克隆”

  • 当你只需要读取数据时,Cow 只会借用数据,避免不必要的内存分配和克隆。
  • 当你需要修改数据时,如果 Cow 借用着数据,它会先自动克隆一份,然后对克隆出的新数据进行修改。这样既保证了修改的安全性(不影响原始数据),又实现了性能优化(只有在需要修改时才进行克隆)。如果它已经拥有了数据(即数据是它自己的),则直接在原地修改,不会产生额外的克隆。

这段代码的核心在于 Cow 智能指针,它是 Clone-on-Write(写时克隆) 的缩写,旨在优化性能。

Cow 智能指针解释

Cow 能够根据需要,自动在借用(Borrowed)拥有(Owned)两种状态之间切换,从而避免不必要的内存分配。当你只需要读取数据时,Cow 会高效地借用数据,不进行克隆。但是,当你需要修改数据时,Cow 会检查当前状态:

  • 如果 Cow 借用着数据,它会立即自动克隆一份新的、可变的数据,然后对新数据进行修改。这种“写时克隆”的行为确保了原始数据的安全。
  • 如果 Cow 已经拥有了数据,它将直接在原地进行修改,不会产生任何克隆开销。

这段代码中的 abs_all 函数就是触发这个行为的关键。它通过 input.to_mut() 来获取一个可变引用,to_mut() 方法正是 Cow 判断是否需要克隆的地方。

四个测试用例详解

  1. reference_mutation
    • 初始状态: Cow 从一个切片 (&[i32]) 创建,处于 Cow::Borrowed 状态。
    • 行为: abs_all 函数找到负数 -1 并尝试修改。由于 Cow 处于借用状态,to_mut() 会触发克隆,将数据复制到堆上。
    • 最终状态: Cow 变为 Cow::Owned
  2. reference_no_mutation
    • 初始状态: Cow 从一个切片创建,处于 Cow::Borrowed 状态。
    • 行为: abs_all 函数中没有找到负数,因此 to_mut() 没有被调用
    • 最终状态: Cow 保持其初始的 Cow::Borrowed 状态。
  3. owned_no_mutation
    • 初始状态: Cow 直接从一个 Vec 创建,从一开始就处于 Cow::Owned 状态。
    • 行为: abs_all 没有找到负数,to_mut() 没有被调用。
    • 最终状态: Cow 依然是 Cow::Owned,因为其所有权从未改变。
  4. owned_mutation
    • 初始状态: Cow 直接从一个 Vec 创建,处于 Cow::Owned 状态。
    • 行为: abs_all 找到负数 -1 并调用 to_mut()。由于 Cow 已经拥有数据,to_mut() 不会进行克隆,而是直接返回一个可变引用。
    • 最终状态: Cow 依然是 Cow::Owned,数据被原地修改。

这段代码完美地展示了 Cow 如何通过惰性克隆,在保证数据安全和代码灵活性的同时,实现了高效的内存管理。

总结

通过对这四个智能指针的实战分析,我们看到 Rust 如何在语言层面提供强大的工具来解决复杂的内存管理问题。

  • Box 帮助我们处理大小不确定的递归数据结构,将数据从栈上转移到堆上,从而让代码得以编译和运行。
  • RcArc 完美解决了多所有权的场景,前者适用于单线程,后者则以原子操作确保了在多线程环境下的数据安全,使得共享数据变得简单而高效。
  • Cow 则以其独特的写时克隆机制,在需要可变性时才付出克隆的代价,这是一种优雅的性能优化方式,特别适合处理那些大多数时候只读、偶尔需要修改的数据。

掌握这些智能指针,不仅意味着你掌握了 Rust 内存管理的核心,更意味着你获得了编写高性能、安全、健壮程序的利器。在面对不同的编程挑战时,选择合适的智能指针将让你的代码更加优雅和高效。

参考

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论