【Rust日报】2023-07-05 让我们从 abandon 开始--用 rust 写链表

Rust 入门 基础单向链表 使用Rust完成单向链表     最近闲下来了,看到一门语言Rust兴起,工资也可观,linux听说有部分也开始rust了,但听说学习有曲线,当时本人是不信邪的,学了之后才知道是真的有曲线。。。光是链表就搞了好几天,主要国内这方面的文章还太少,甚至给我一种rust还不成熟的错觉。不过既然linux大佬都在用,且号称集各种语言优势的现代化语言,我就不由得想尝试一下。     先给大家分析一下为什么难,为什么有些人会觉得比C/ 阅读详情

让我们从 abandon 开始--用 rust 写链表

虽然 Rust 的标准库中已经有了一个LinkedList数据结构,但创建自己的数据结构是了解更多 Rust 的一种有趣的方式。

节点

让我们首先定义我们的 Node 结构来保存模版。我们的 Node 将包含一个数据项,以及 Node 可能存在或可能不存在的上一个和下一个数据项。

/// A node containing a data item and links to previous and next nodes.
struct Node<T> {
    data: Rc<T>,
    prev: Link<T>,
    next: Link<T>,
}

通过将数据项存储在引用计数器指针 ( Rc) 中,在创建新节点时无需克隆或复制数据。 Rust 需要在编译时知道结构的大小,这会导致自引用数据类型出现一些问题。prev如果我们将和字段的类型设置next为Node,我们会收到如下所示的编译器错误消息:

error[E0072]: recursive type `Node` has infinite size
   --> src/data_structures/linkedlist.rs:127:1
    |
127 | struct Node<T> {
    | ^^^^^^^^^^^^^^
128 |     data: Rc<T>,
129 |     prev: Node<T>,
    |           ------- recursive without indirection

编译器错误消息为我们提供了如何继续操作的提示。我们正在被推动使用间接,这是谈论指针的另一种方式。 为了处理这个编译器错误,我们需要提供某种指针来代替Node. 我们可以定义一个自定义类型Link作为我们的间接Node包装器:

type Link<T> = Option<Rc<RefCell<Box<Node<T>>>>>;

请注意,因为我们的Link类型使用Rc(引用计数器指针)而不是Arc(原子引用计数指针),所以我们的实现Node不是线程安全的。

节点构造

我们的每个Node方法都将包含在一个块中:impl Node { ... } 这允许我们的方法获取通用类型的数据项T,使我们Node非常灵活!

现在我们知道我们的意志如何Node链接到其他人,我们可以实现该new()方法。每个都Node将开始作为通用类型的数据项保存T,并且不连接到上一个或下一个Node。

/// Creates a new Node containing the given data item. The previous and next
/// node links are set to None.
fn new(data: T) -> Node<T> {
    Node {
        data: Rc::new(data),
        prev: None,
        next: None,
    }
}

Node setters

我们更新前一个和下一个节点的方法将相当简单。使用我们的引用计数Link,我们只需要在使用我们的设置方法时克隆另一个链接。确保彼此相邻的两个节点正确指向彼此的逻辑将由我们的LinkedList.

/// Updates the previous node.
fn set_prev(&mut self, other: &Link<T>) {
    self.prev = other.clone();
}

/// Updates the next node.
fn set_next(&mut self, other: &Link<T>) {
    self.next = other.clone();
}

Node getters

与我们的 setter 方法类似Node,我们的 getter 方法只需要克隆下一个或上一个链接。

/// Gets the previous link from the Node via cloning.
fn get_prev(&self) -> Link<T> {
    self.prev.clone()
}

/// Gets the next link from the Node via cloning.
fn get_next(&self) -> Link<T> {
    self.next.clone()
}

为了获取我们存储的数据Node而不需要克隆或复制数据,我们可以返回引用计数指针的克隆。 为了方便我们的实现,我们将定义一个静态方法来从数据项LinkedList创建新的:LinkT

/// Creates a new Link containing the given data item.
fn new_link(data: T) -> Link<T> {
    Some(Rc::new(RefCell::new(Box::new(Node::new(data)))))
}

实现了我们的Node结构及其方法后,我们可以继续我们的 LinkedList 实现。

LinkedList

我们 LinkedList 需要跟踪它的第一个和最后一个节点(头节点和尾节点)及其长度。

/// An implementation of a doubly linked-list. Not thread-safe. Note that the
/// data items contained within nodes cannot be changed after they have been
/// added to the linked-list.
pub struct LinkedList<T> {
    head: Link<T>,
    tail: Link<T>,
    len: usize,
}

虽然我们可以动态计算 的长度LinkedList,但在推送和弹出数据项时跟踪其长度会更有效。

链表构造函数

我们的LinkedList方法将包含在一个块中:impl LinkedList { ... }。与我们的Node方法一样,此实现块将允许我们的LinkedList方法接受通用数据类型的数据项T,使我们的方法LinkedList像我们的方法一样灵活Node!

新的LinkedList开始是空的,没有头节点或尾节点,长度为 0。

/// Creates an empty LinkedList.
pub fn new() -> LinkedList<T> {
    LinkedList {
        head: None,
        tail: None,
        len: 0,
    }
}

push 方法

我们的 LinkedList push 方法是这个项目开始变得真正有趣的地方! 按照惯例,“push”本身意味着 push 到 LinkedList 的末尾,而“ push 到前面”正是这个意思。

要将新节点 push 到列表末尾,我们首先创建另一个包含新数据项的节点。 如果 LinkedList 为空,则新 Node 成为新的头和尾。 否则,新节点成为新尾部。 我们的 push() 方法的最后一部分确保旧尾部和新尾部彼此指向,并且 LinkedList 正在跟踪新尾部。

在 Link 实例上调用的 as_ref() 方法允许我们借用 Option 实例中持有的 Rc,而不是取得它的所有权。 借用 Rc 后,我们可以通过对嵌套在 Link 中的 RefCell 调用的borrow_mut() 方法动态借用 Link 中包含的 Node。

/// Pushes the data item to the end of the LinkedList.
pub fn push(&mut self, data: T) {
    let new_node: Link<T> = Node::new_link(data);
    // Increase the len counter
    self.len += 1;
    // Handle case for empty list
    if self.head.is_none() && self.tail.is_none() {
        self.head = new_node.clone();
        self.tail = new_node;
        return;
    }
    // Update the tail to point at the new node and connect to the old tail
    self.tail.as_ref().unwrap().borrow_mut().set_next(&new_node);
    new_node.as_ref().unwrap().borrow_mut().set_prev(&self.tail);
    self.tail = new_node;
}

我们对称的最重要的部分LinkedList是我们的push_front()方法最终类似于push(),其中头/尾和上一个/下一个的引用被交换。

/// Pushes the data item to the front of the LinkedList.
pub fn push_front(&mut self, data: T) {
    let new_node: Link<T> = Node::new_link(data);
    // Increase the len counter
    self.len += 1;
    // Handle case for empty list
    if self.head.is_none() && self.tail.is_none() {
        self.head = new_node.clone();
        self.tail = new_node;
        return;
    }
    // Update the head to point at the new node and connect to the old head
    self.head.as_ref().unwrap().borrow_mut().set_prev(&new_node);
    new_node.as_ref().unwrap().borrow_mut().set_next(&self.head);
    self.head = new_node;
}

pop 方法

我们从数据项中弹出的值LinkedList取决于它是否包含节点。与我们的推送方法一样,“pop”本身是指从末尾(尾部)删除一个值,LinkedList而“从前面弹出”就是它所说的意思。

如果我们LinkedList至少包含一个Node,我们的pop()方法将返回包含在已删除节点中的值,该值包装在 中的 aSome中Option。否则,它将返回一个None值,表明没有Node被删除。

/// Removes the last node from the LinkedList. Returns Some containing the value
/// from the removed node, otherwise None.
pub fn pop(&mut self) -> Option<Rc<T>> {
    // Handle case for empty list
    if self.head.is_none() && self.tail.is_none() {
        return None;
    }
    // Update the tail to be the second-last node and return value contained in removed node
    let old_tail = self.tail.clone();
    self.tail = old_tail.as_ref().unwrap().borrow().get_prev();
    self.tail.as_ref().unwrap().borrow_mut().set_next(&None);
    let old_data = old_tail.as_ref().unwrap().borrow().get_data();
    // Decrease the len counter
    self.len -= 1;
    Some(old_data)
}

与push 方法一样,pop 方法也是对称的。对于该pop_front()方法,与该方法相比,对 head/tail 和 prev/next 的引用被交换pop()。

/// Removes the first node from the LinkedList. Returns Some containing the
/// value from the removed node, otherwise None.
pub fn pop_front(&mut self) -> Option<Rc<T>> {
    // Handle case for empty list
    if self.head.is_none() && self.tail.is_none() {
        return None;
    }
    // Update head to be second node and return value contained in removed node
    let old_head = self.head.clone();
    self.head = old_head.as_ref().unwrap().borrow().get_next();
    self.head.as_ref().unwrap().borrow_mut().set_prev(&None);
    let old_data = old_head.as_ref().unwrap().borrow().get_data();
    // Decrease the len counter
    self.len -= 1;
    Some(old_data)
}

Checking length

现在检查我们的长度LinkedList非常简单,因为我们一直在推送和弹出节点时跟踪它们。

/// Returns the number of items contained in the LinkedList.
pub fn len(&self) -> usize {
    self.len
}

为了完整起见,我们应该实现一个is_empty()方法,因为我们有一个len()方法。如果该字段等于 0,我们的LinkedList将为空。len

/// Checks if the LinkedList is empty.
pub fn is_empty(&self) -> bool {
    self.len == 0
}

测试

为代码编写和使用测试用例是一个很好的做法。这样您就可以知道您的实现是否按预期运行,并在代码更改发生重大变化时保持警惕。

下面是一些示例测试方法,我们可以使用它们来检查LinkedList. 这些测试方法绝不是排他性的——您可以随意自己编写更多测试用例。您可以使用命令执行测试方法cargo test。

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

    #[test]
    fn test_push_back_length() {
        let mut new_list = LinkedList::<i32>::new();
        let values = (0..10).collect::<Vec<i32>>();
        for i in values {
            new_list.push(i);
        }
        assert_eq!(new_list.len(), 10);
    }

    #[test]
    fn test_push_front_length() {
        let mut new_list = LinkedList::<i32>::new();
        let values = (0..10).collect::<Vec<i32>>();
        for i in values {
            new_list.push_front(i)
        }
        assert_eq!(new_list.len(), 10);
    }
}
  • 源码

  • https://cmooneycollett.github.io/2023/07/01/writing-a-linkedlist-in-rust


From 日报小组 Koalr

社区学习交流平台订阅:

  • Rustcc论坛: 支持rss

  • 微信公众号:Rust语言中文社区

Rust-实现链表 简介 实现单链表在别的语言里面可能是一件简单的事情,单对于Rust来说,绝对不简单。只有熟悉了Rust特有的所有权机制、生命周期和借用等概念才能实现出一个像样的单链表。 这篇笔记不会一步步教你如何用Rust一个单链表,因为这样的教程已经有人了,并且得很好。这里只记录一下使用Rust实现单链表时的一些注意事项,以及一些笔记。 注意事项 只有一个可变引用 在C里面,如果要在链表的头部插入元素,可以这样 Node* new_node = create_new_node(v); new_node->n 阅读详情

相关推荐

VisionMaster Qt二次开发:图像获取、COM组件渲染与本地化存储实战

本文详细介绍了基于VisionMaster进行Qt二次开发的核心实战流程,涵盖环境搭建、方案加载、图像数据获取、COM组件渲染与本地化存储。重点解析了如何通过COM组件在Qt界面中实时显示图像,以及将VM图像数据转换为QImage并保存为本地文件的关键步骤与常见问题,为工业视觉项目开发提供稳定高效的解决方案。

weixin_29229261的博客 177

Rust一个链表,非常详细,一遍看懂

Rust一个链表可不是一件容易的事,涉及到很多的知识点,需要熟练掌握之后才能出一个不错的链表。这篇文章主要介绍了如何一个Rust链表,并且补充了涉及到的很多的额外知识点,尤其是所有权问题。 首先,你需要明白,为什么Rust链表,同样的为什么C实现简单一点呢? 只能有一个引用!!!这是最关键的,然后就是Rust中是没有NULL指针的,这就需要用到Option枚举,在编译阶段必须知道类型的大小,这就需要使用Box智能指针。 在C语言实现一个简单的链表,可以这样: 这段代码里面,head和new

phthon1997的博客 5537

【ArcGIS Pro微课1000例】0047:深度学习--棕榈树提取全流程

对汤加科洛瓦伊种植园每棵棕榈树的健康状况进行清查和评估,这需要花费大量的时间和劳动力。为简化此过程,将在 ArcGIS Pro 中使用深度学习模型来识别树木,然后根据植被绿度的测量值计算其健康状况。第一步是找到显示汤加科洛瓦伊的影像,该影像具有足够精细的空间和光谱分辨率来识别树木。获得此影像后,将创建训练样本并将其转换为可供深度学习模型使用的格式。为了让模型识别出它的查找任务,需要定义棕榈树的影像,以便识别相似的像素和树木大小。

智慧空间实验室 2175

Rust】枚举类型创建单链表以及常见的链表操作方法

链表(Linked List)是一种线性数据结构,由一系列节点组成,每个节点包含一个数据元素和一个指向下一个节点的指针。单链表的特点是每个节点只能指向一个下一个节点,没有指向上一个节点的指针。

汉阳Hann's Home 4884

Rust中的链表实现】

Rust链表的实现,Push,Pop,Peek,Iter等实现方式,引用和生命周期

m0_37719524的博客 1678

Rust链表实现

【代码】Rust链表实现。

Zoe_Cpp的博客 346

rust学习(简单链表

我们发现,try_borrow_mut貌似只有refcell实现了,所以这个接口返回的是我们需要的值RefMut<>,但是注意一下,由于try_borrow_mut返回的是result,所以我们还需要unwrap一下。一个是BorrowMut,一个是RefCell的。目前我就遇到一个比较神奇的问题:使用borrow_mut之后,无法找到成员变量..仔细对比了2个borrow_mut函数,你会发现,我去,完全是2个接口~编一个简单链表,主要遇到的问题就是next指针(按照C的法)的数据如何定义。

一个白馒头的专栏 861

Rust日报】2024-03-06 Rust 版本的 PickleDB

Rust 版本的 PickleDBPickleDB 是一个用 Rust的轻量级且简单的键值存储,很大程度上受到 Python PickleDB 的启发。PickleDB 有趣且易于使用PickleDB 使用usepickledb::{PickleDb,PickleDbDumpPolicy,SerializationMethod}; fnmain(){ //create...

Rust语言学习交流 512

Rust 双向链表 LinkedList 和安全删除元素的方法

一旦找到要删除的元素,你可以通过调整相邻节点的指针来将其从链表中移除,这个过程的时间复杂度是O(1)。一种常见的做法是使用“安全”的删除方法,比如在迭代过程中收集要删除的元素的索引或引用,然后在循环结束后再进行实际的删除操作。请记住,双向链表的性能特征意味着在链表的中间插入或删除元素可能比在数组的对应位置进行相同的操作更加低效,因为链表必须遍历节点直到找到所需的位置。在双向链表的头部或尾部删除元素通常是一个相对高效的操作,因为这两个位置都保留了直接的指针引用,可以直接调整指针来移除元素,无需遍历整个链表

程序猿视角 2390

Rust开发链表的实现与基本操作(单链表、双向链表

在本案例中,我们完成了链表这一经典数据结构Rust 中的安全实现✅单向链表使用Box<T>和Option<T>实现,完全由编译器保证内存安全;✅双向链表借助和Weak<T>解决共享可变性问题,避免了循环引用导致的内存泄漏;✅ 掌握了take()upgrade()等关键方法的实际应用场景;✅ 通过对比表格明确了不同类型链表的优劣与适用场景;✅ 设计了分阶段的学习路径,帮助读者由浅入深掌握复杂数据结构开发技巧。

风中追风 1016

Rust日报】 征集 2025 年下半年 Rust 项目目标

阅读:https://blog.rust-lang.org/inside-rust/2025/06/23/project-goals-2025h2-call-for-submissions/MemTrace UI 是一个基于 Rust 的 GUI 工具,用于可视化堆内存消耗,支持 heaptrack 跟踪文件,目前仅适用于 MacOS。正如你所看到的,它有两个接口,带有驱动程序的是键盘,另一个是鼠标接口。是一个 Rust 库,提供基于偏移量的指针,用于创建可移动的自引用数据结构。它甚至能在真实硬件上运行!

Rust语言学习交流 1285

002 通过链表学习Rust笔记之链表布局

介绍 视频地址:https://www.bilibili.com/video/av78062009/ 相关源码:https://github.com/anonymousGiga/Rust-link-list Rust中最常见的链表 用函数式的语法定义一个链表如下: List a = Empty | Elem a (List a) 一个链表要么是空的类型,要么是一个值后面跟着一个链表,这种被称为递归定义类型,表示为和类型。Rust中的enum就是类型系统的和类型。所以最常见的Rust链表的定义如下: #

令狐一冲 492

探索Rust语言之实现单链表

Rust实现通用的单链表

Aspiresky的专栏 289

详解Rust标准库:LinkedList 链表

LinkedList节点定义// 下一个节点// 上一个节点// 当前节点值。

weixin_62799021的博客 782

Rust日报】2024-04-15 拯救地球,请使用Rust编程

拯救地球,请使用Rust编程本文讨论了如何通过在Rust编程语言中编码,可以更有效地利用现有资源以帮助保护我们的星球。通过在实际项目中将PHP应用重Rust,作者体验到了Rust不仅在维护性、开发效率和错误减少方面有优势,还在性能上大大优于PHP,证明了用Rust开发能够大幅减少资源使用。文章依据科学文献,并通过数据中心电力消耗的现状和趋势,强调了采用Rust这样高效的编程语言对减缓电力消耗增...

Rust语言学习交流 742

Rust日报】filtra 2024年9月份Rust语言Job报告(国外)

Fusio - 一个新的存储抽象库Fusio是一个为多种异步运行时(包括基于轮询的tokio和基于完成的tokio-uring、monoio)提供读多种存储后端(如本地磁盘、亚马逊S3等)Trait的Rust库。主要特点包括:体积小巧,二进制大小比其他类似库小14倍以上。与裸存储后端相比,Trait定义可无额外开销地分派文件操作。可扩展,允许第三方crate实现存储后端。Fusio目前处于预览版...

Rust语言学习交流 517

Rust 日报】2022-11-25 Rust 真的要上天了!

Rust 真的要上天了!Gama 将发射太阳帆宇宙飞船,并且是公开将 Rust 送入太空的公司之一。是的,我们在太空中!详情:https://twitter.com/AeroRust/status/1596052251650686976Redox OS 0.8.0 现已发布!自从 0.7.0 版本发布以来,我们有很多东西可以展示!在这个版本中,我们注意到了对 i686 的支持。在这个版本中,我们小...

Rust语言学习交流 932

Rust日报Rust 2024年下半年项目目标

Rust 2024年下半年项目目标Rust 计划在 2024 年发布第四版 Rust,旨在解决一些小的使用问题,使 Rust 更易用,主要包含以下改进:异步 Rust:计划推出异步闭包和 Send 边界等关键功能,提升异步 Rust 的使用体验。Linux 内核中的 Rust:努力解决阻碍 Rust 在 Linux 内核中使用的不稳定特性,推动 Rust 在低级系统应用中的发展。其他目标:除了旗舰...

Rust语言学习交流 667

Rust日报】2024年Rust回顾:接下来是什么?

Avian - 一个基于 ECS(实体组件系统)的2D和3D物理引擎Avian 是一个基于 ECS(实体组件系统)的2D和3D物理引擎,专为Bevy设计,Bevy是一个在Rust中构建的,非常简洁的数据驱动游戏引擎。Avian 注重人体工程学和模块化设计,重点提供一种原生的、由ECS驱动的用户体验。https://joonaa.dev/blog/07/avian-0-22024年Rust回顾:接下...

Rust语言学习交流 578

Rust 实现简单的双链表

简单实现了一个Rust的双链表 由于水平有限和rust的特性 并不推荐使用双链表

qq_43096296的博客 273

安东 LU-904M智能测控仪说明书.pdf

安东 LU-904M智能测控仪说明书pdf,安东 LU-904M智能测控仪说明书

上一篇: 【Rust日报】2023-07-04 Bevy XPBD:适用于Bevy游戏引擎的物理引擎
下一篇: 【Rust日报】2023-07-07 用 Rust 原生库为你的安卓应用增色
Rust语言中文社区
博客等级 码龄13年 1万+粉丝 2157原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值