Rust

2025年10月05日更新 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 并发编程三步曲:Join、Arc<Mutex> 与 mpsc 通道同步实战 Rust 声明宏实战进阶:从基础定义到 #[macro_export] 与多规则重载 Rust 类型转换实战:利用 From/Into Trait 实现带 Default 容错的安全转换 Rust 实战:实现 FromStr Trait,定制化字符串 parse() 与精确错误报告 Rust 实战:TryFrom Trait——如何在类型转换中强制执行业务逻辑检查 Rust 泛型编程基石:AsRef 和 AsMut 的核心作用与实战应用 揭秘 Rust Unsafe 编程:程序员接管内存安全的契约与实践 Rust FFI 入门:extern、ABI 与底层符号链接解析 Rust性能优化:零内存拷贝的链表合并技术实战 Rust 进阶:用 NonNull 裸指针实现高性能双向链表 O(N) 反转实战

Rust 进阶:用 NonNull 裸指针实现高性能双向链表 O(N) 反转实战

Rust进阶:用NonNull裸指针实现高性能双向链表O(N)反转实战Rust以其内存安全和零成本抽象闻名,但当我们需要构建如双向链表这类复杂的自引用数据结构,并追求极致的底层性能时,就必须深入unsafe的领域。本文将带你探索Rust的安全边界,实战一个基于NonNull

Rust 进阶:用 NonNull 裸指针实现高性能双向链表 O(N) 反转实战

Rust 以其内存安全零成本抽象闻名,但当我们需要构建如双向链表这类复杂的自引用数据结构,并追求极致的底层性能时,就必须深入 unsafe 的领域。

本文将带你探索 Rust 的安全边界,实战一个基于 NonNull 裸指针的高性能双向链表。我们将详细解析如何利用裸指针实现线性 O(N) 时间复杂度原地反转算法,性能直接对标 C/C++。

更重要的是,我们将重点展示 Rust 工程师如何负责任地管理内存:在 unsafe 环境下操作 Box::into_raw 放弃所有权后,如何通过严谨的 Drop Trait 实现来安全回收堆内存,完美地证明 “Fast and Safe” 在底层编程中是完全可兼得的。

实操

双向链表反转

/*
    double linked list reverse
*/

use std::fmt::{self, Display, Formatter};
use std::ptr::NonNull;
use std::vec::*;

#[derive(Debug)]
struct Node&lt;T> {
    val: T,
    next: Option&lt;NonNull&lt;Node&lt;T>>>,
    prev: Option&lt;NonNull&lt;Node&lt;T>>>,
}

impl&lt;T> Node&lt;T> {
    fn new(t: T) -> Node&lt;T> {
        Node {
            val: t,
            prev: None,
            next: None,
        }
    }
}
#[derive(Debug)]
struct LinkedList&lt;T> {
    length: u32,
    start: Option&lt;NonNull&lt;Node&lt;T>>>,
    end: Option&lt;NonNull&lt;Node&lt;T>>>,
}

impl&lt;T> Default for LinkedList&lt;T> {
    fn default() -> Self {
        Self::new()
    }
}

impl&lt;T> LinkedList&lt;T> {
    pub fn new() -> Self {
        Self {
            length: 0,
            start: None,
            end: None,
        }
    }

    pub fn add(&mut self, obj: T) {
        let mut node = Box::new(Node::new(obj));
        node.next = None;
        node.prev = self.end;
        let node_ptr = Some(unsafe { NonNull::new_unchecked(Box::into_raw(node)) });
        match self.end {
            None => self.start = node_ptr,
            Some(end_ptr) => unsafe { (*end_ptr.as_ptr()).next = node_ptr },
        }
        self.end = node_ptr;
        self.length += 1;
    }

    pub fn get(&mut self, index: i32) -> Option&lt;&T> {
        self.get_ith_node(self.start, index)
    }

    fn get_ith_node(&mut self, node: Option&lt;NonNull&lt;Node&lt;T>>>, index: i32) -> Option&lt;&T> {
        match node {
            None => None,
            Some(next_ptr) => match index {
                0 => Some(unsafe { &(*next_ptr.as_ptr()).val }),
                _ => self.get_ith_node(unsafe { (*next_ptr.as_ptr()).next }, index - 1),
            },
        }
    }
    pub fn reverse(&mut self) {
        // 只有 0 或 1 个元素的链表无需反转
        if self.length &lt;= 1 {
            return;
        }

        // 使用 unsafe 代码进行裸指针操作
        unsafe {
            let mut current = self.start;

            // 1. 遍历所有节点,交换 next 和 prev
            while let Some(mut node_ptr) = current {
                let node_ref = node_ptr.as_ptr();

                // 暂时保存原始的 next 指针,这是下一次循环要移动到的节点
                let original_next = (*node_ref).next;

                // 交换 next 和 prev 指针:
                // 新的 next 应该指向旧的 prev
                (*node_ref).next = (*node_ref).prev;
                // 新的 prev 应该指向旧的 next
                (*node_ref).prev = original_next;

                // 推进到下一个节点(即原始的 next 指针)
                current = original_next;
            }

            // 2. 交换链表的 start 和 end 指针
            std::mem::swap(&mut self.start, &mut self.end);
        }
    }
}

// ----------------------------------------------------
// ✅ 内存安全保障:实现 Drop Trait
// ----------------------------------------------------
impl&lt;T> Drop for LinkedList&lt;T> {
    fn drop(&mut self) {
        // 从链表头部开始,依次将裸指针转换回 Box,触发 Box 的析构函数,从而安全释放内存。

        // 1. 取出链表头指针。take() 将 self.start 置为 None,确保链表结构被清空。
        let mut current = self.start.take();

        // 2. 循环遍历所有节点
        while let Some(node_ptr) = current {

            // SAFETY: 
            // 1. 我们正在 Drop 链表,保证了对该内存的所有权是唯一的(因为 LinkedList 即将被销毁)。
            // 2. Box::from_raw() 恢复了 Rust 对该堆内存的所有权。
            let node_ptr_raw = node_ptr.as_ptr();
            let node = unsafe { Box::from_raw(node_ptr_raw) };

            // 3. 移动到下一个节点
            // node.next 是 Option&lt;NonNull&lt;Node&lt;T>>>。
            // 必须使用 take() 将其从当前节点中移出,这样 current 才能指向下一个节点。
            current = node.next.take();

            // 当前节点 'node'(一个 Box)在离开作用域时(此处循环结束)
            // 会被 Rust 自动调用析构函数(Drop),安全释放内存。
        }

        // 额外操作:由于 Drop 只能从 start 向后遍历,我们需要显式清除 end
        // 这一步虽然在逻辑上不严格必要(因为 start 已经接管了释放责任),
        // 但可以保证 LinkedList 实例在被销毁时是完全干净的。
        self.end.take(); 
        self.length = 0;
    }
}
// ----------------------------------------------------

impl&lt;T> Display for LinkedList&lt;T>
where
    T: Display,
{
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match self.start {
            Some(node) => write!(f, "{}", unsafe { node.as_ref() }),
            None => Ok(()),
        }
    }
}

impl&lt;T> Display for Node&lt;T>
where
    T: Display,
{
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match self.next {
            Some(node) => write!(f, "{}, {}", self.val, unsafe { node.as_ref() }),
            None => write!(f, "{}", self.val),
        }
    }
}

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

    #[test]
    fn create_numeric_list() {
        let mut list = LinkedList::&lt;i32>::new();
        list.add(1);
        list.add(2);
        list.add(3);
        println!("Linked List is {}", list);
        assert_eq!(3, list.length);
    }

    #[test]
    fn create_string_list() {
        let mut list_str = LinkedList::&lt;String>::new();
        list_str.add("A".to_string());
        list_str.add("B".to_string());
        list_str.add("C".to_string());
        println!("Linked List is {}", list_str);
        assert_eq!(3, list_str.length);
    }

    #[test]
    fn test_reverse_linked_list_1() {
        let mut list = LinkedList::&lt;i32>::new();
        let original_vec = vec![2, 3, 5, 11, 9, 7];
        let reverse_vec = vec![7, 9, 11, 5, 3, 2];
        for i in 0..original_vec.len() {
            list.add(original_vec[i]);
        }
        println!("Linked List is {}", list);
        list.reverse();
        println!("Reversed Linked List is {}", list);
        for i in 0..original_vec.len() {
            assert_eq!(reverse_vec[i], *list.get(i as i32).unwrap());
        }
    }

    #[test]
    fn test_reverse_linked_list_2() {
        let mut list = LinkedList::&lt;i32>::new();
        let original_vec = vec![34, 56, 78, 25, 90, 10, 19, 34, 21, 45];
        let reverse_vec = vec![45, 21, 34, 19, 10, 90, 25, 78, 56, 34];
        for i in 0..original_vec.len() {
            list.add(original_vec[i]);
        }
        println!("Linked List is {}", list);
        list.reverse();
        println!("Reversed Linked List is {}", list);
        for i in 0..original_vec.len() {
            assert_eq!(reverse_vec[i], *list.get(i as i32).unwrap());
        }
    }
}

🛠️ 裸指针双向链表:结构、性能与内存安全

这段 Rust 代码呈现了一个高性能的双向链表 (LinkedList&lt;T>) 实现,它通过使用 std::ptr::NonNull 裸指针,巧妙地规避了 Rust 所有权系统对链表这种自引用数据结构的限制。这种实现方式使我们能够实现与 C/C++ 媲美的底层性能,特别是在操作大型数据结构时。

1. 结构与所有权转移

  • 核心结构Node&lt;T> 包含了数据 val、指向下一节点的裸指针 next,以及指向上一节点的裸指针 prev
  • 节点添加 (add):该方法是所有权转移的关键。我们首先使用 Box::new() 安全地在堆上创建节点,然后通过 Box::into_raw(node)Box 智能指针的所有权彻底放弃,只返回一个原始指针。此后,这块内存的生命周期和安全释放完全由程序员手动负责。
  • 裸指针操作:在 add 中,我们通过 unsafe 块解引用 (*end_ptr.as_ptr()) 旧的尾指针,并修改其 next 字段,将新节点缝合到链表末尾,实现了O(1) 的高效尾部插入。

2. 核心算法:原地反转 (reverse)

reverse 方法实现了双向链表原地反转(In-Place Reversal)的经典算法,其时间复杂度为线性 O(N)。

  • 指针交换:算法的核心逻辑在于遍历链表中的每一个节点,并利用裸指针的优势,交换该节点的 next 指针prev 指针
    • unsafe 块中,我们首先缓存原始的 next 指针 (original_next),这是因为我们下一步需要用它来推进循环。
    • 随后,我们执行 (*node_ref).next = (*node_ref).prev (将 next 指向前一个节点) 和 *`(node_ref).prev = original_next** (将prev` 指向后一个节点) 的操作。
  • 零拷贝:这个过程不涉及任何数据复制或新的内存分配。我们只是在堆内存中修改了节点内部的指针地址,实现了极致效率的链表结构重排。
  • 头尾更新:遍历完成后,所有节点的局部指针已反转,但链表容器的全局指针 (self.startself.end) 仍指向旧的头尾。最后通过 std::mem::swap(&mut self.start, &mut self.end),以一个O(1) 的操作,交换头尾指针,完成整个链表的逻辑反转。

3. 内存安全保障 (Drop Trait)

由于 add 中使用了 Box::into_raw 放弃了所有权,Drop Trait 的正确实现是保证内存安全的关键,它负责回收所有被手动管理的堆内存,从而避免内存泄漏。

  • 恢复所有权:在 drop 方法中,我们从链表头部开始遍历。对于获得的每一个裸指针,我们调用 unsafe { Box::from_raw(node_ptr_raw) }。这一操作将裸指针指向的内存重新封装成一个临时的 Box&lt;Node&lt;T>> 智能指针,有效地将内存的所有权交还给 Rust。
  • 安全释放:当这个临时的 Box 变量在循环结束时离开其作用域,Rust 的析构系统会自动触发 Box 的内存释放机制,安全地回收了堆内存。
  • 链式析构:我们通过 current = node.next.take() 推进到下一个节点,并在前一个节点被安全释放后,对链上的每一个节点重复此过程,确保了整个链表的链式安全析构。正是这一严谨的机制,使得我们的 unsafe 代码依然能够兑现 Rust 对内存安全的承诺。

总结

本次实战成功展示了在 Rust 中利用 unsafe 裸指针实现一个高性能双向链表,并完成了经典的 O(N) 原地反转算法。该实现的核心在于在 unsafe 块内直接交换节点的 nextprev 裸指针,以零内存拷贝的方式完成了链表结构的重排,达到了算法的理论性能上限。

技术亮点回顾:

  1. 性能突破:利用 NonNull 绕过所有权检查,实现 O(1) 尾部插入和 O(N) 原地反转。
  2. 内存管理:通过 Box::into_raw 放弃所有权,并使用 Box::from_rawDrop Trait 中恢复所有权并触发安全析构。

这一实践是 Rust 开发者在追求底层极致性能时,如何恪守内存安全承诺的最佳示例。它表明,掌握 unsafe 并不意味着放弃安全,而是获得了在 Rust 框架下像 C 语言一样高效操作内存的能力。

参考

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

0 条评论

请先 登录 后评论