无需宏为 Trait Objects 实现 Any

std::any::Any 是 Rust 在运行时进行类型擦除和转换的工具,所有 'static 类型都实现了 Any,因此装箱为 Box<dyn Any> 后可以借助 Any::type_id() 获取 TypeId,还可以通过 dyn Any 的 downcast_*() 等方法再转换回具体类型。 然而问题也出在 dyn Any 的 downcast_*() 方法。这些方法是 dyn Any 的方法,而不是 Any trait 中的方法,所以其他任何 dyn Trait 都不会拥有这些方法,即使有 Trait: Any。另一方面,由于 trait upcasting 直到最近才成为稳定特性,且还没进入 stable 版本,所以依赖语言支持的 trait upcasting 来转换为 dyn Any 对于较早版本的项目并不合适。 因此,本文则通过纯 Rust 语法来扩展 trait object,以实现 Any 的所有功能。这些实现都可以在 better-as-any 找到。 现有解决方案 downcast downcast crate 通过宏来生成代码,直接为指定的 dyn Trait 添加 is()、downcast_*() 方法。这种做法在最终效果上与 dyn Any 完全一样,但是需要使用 impl_downcast!() 宏来实现。我个人则偏好能使用语言特性实现就不使用宏。...

March 17, 2025 · 4 min · oosquare

anyerr 上下文中的类型体操

在对 Rust 错误处理的思考和 anyerr一文中,我介绍了 anyerr 这一个错误处理库,其可以携带上下文信息,且储存上下文的数据结构是可定制的。本文则聚焦 anyerr 是如何实现这样的特性的。 上下文的核心特性 基本结构和表示 在设计之前,首先我们要明确需求。什么样的上下文数据结构是我们所需要的?携带上下文是为了能够记录某些变量所保存的值,我们需要记录变量的名称和其中的值。上下文可以有很多种类,但所有的上下文都可以表示为一个键值映射表。 所以以下是我们对一个上下文储存的基本特性的定义: pub trait AbstractContext: Default + Debug + Send + Sync + 'static { type Key; type Value; type Entry: Entry<Key = Self::Key, Value = Self::Value>; type Iter<'a>: Iter<'a, Entry = Self::Entry> where Self: 'a; fn iter(&self) -> Self::Iter<'_>; } 这样的一个 trait 定义仅仅规定了一个上下文的键值对类型和迭代其中元素的方法,却没有插入或者其他查询的方法。这是因为一个上下文不一定需要真的携带有信息,如果不需要上下文,那么一个不带有任何信息的上下文就可以非常好地适用于这种场景,这也是为什么这个 trait 叫做 AbstractContext。anyerr 针对这样的情况有特殊的优化,这些后面再说。 上下文的元素 AbstractContext::Entry 规定了上下文中每一个元素的类型,其应当实现 Entry trait。以下则是 Entry 的定义: pub trait Entry: Debug + Send + Sync + 'static { type Key: Borrow<Self::KeyBorrowed> + Debug + Send + Sync + 'static; type KeyBorrowed: Debug + Display + Eq + Hash + ?...

February 27, 2025 · 6 min · oosquare

对 Rust 错误处理的思考和 anyerr

错误处理是 Rust 中核心的一部分,从标准库中的 Result<T, E> 和 Error 到社区的 anyhow、thiserror、color-eyre、snafu 等 crates,可见其重要地位。但是在我看来,这些仅仅是错误处理机制的基础,而不是一个十分完备的框架,同时某些 crate 的设计,要么不能符合实际需要,要么使用起来很麻烦。本文将阐述我对 Rust 错误处理的理解和自己的实践 anyerr。 std 中的基础设施 在讨论我对错误处理的理解之前,有必要先回顾标准库中与错误处理相关的基础设施。 截至本文写作时间,Rust 的最新版本为 1.84.1,接下来将以此版本为基础进行讨论。 错误与结果 标准库中最广为人知的一个类型就是 Result<T, E>,用来表示一个可能成功或失败的结果。这是一个非常精妙的设计,主要体现其可以编码成功或失败中的一者,而不是将失败结果糅合进成功结果的值中。C 广泛采用后一种处理方式,导致 API 的混乱,当然这很大一部分是历史原因。在 Java 等以异常为主要错误处理机制的语言中,这一问题有了很大改善,但这又引入了隐式控制流的问题,Result<T, E> 则又避开了这个问题。所以,Result<T, E> 应该是一个很不错的机制。 尽管 Result<T, E> 中的 E 代表错误,但标准库对其具体应是什么类型没有什么限制。一般情况下,E 是一个实现了 Error trait 的类型,或者是其他可以间接地访问到内部的错误的类型,如 Box<dyn Error>。 错误类型的统一契约 若某类型实现了 Error,那么其就可以以一种标准的形式来被集成的错误处理的框架中。Error 规定了如何显示错误信息(通过 Display trait)和如何溯源错误(通过 <Self as Error>::source())。 在 Rust 早期,并没有 Error,错误处理是处于一种野蛮生长的状态。直到 Error 的引入,Rust 才可以算是有了一套标准的错误处理机制。 错误的传播 Result<T, E> 虽好,但在深层函数调用中,一次次手动向上传播错误却很麻烦。? 运算符则有效解决了这个问题,实现了把错误方便的提前返回,传播给调用方。 ? 的使用不要求被传播错误类型和接受的错误类型完全相同,只需要有 From 的联系,即如果 E1: From<E2>,那么对 Result<U, E2> 使用 ?...

February 20, 2025 · 5 min · oosquare

体验 Helix 编辑器

最近发现了一个用 Rust 写的 Vim-like 编辑器 Helix,用有强大的性能和各种开箱即用的功能。经过短暂时间的体验,我认为 Helix 已经可以在大部分领域替代 Vim/Neovim/VS Code。 性能 作为一款用 Rust 写的编辑器,Helix 自然拥有优异的性能。得益于其丰富的内置功能,Helix 无需插件就可以完成许多 Vim 只能用插件做到的事,并且还通过 Rust 获得了性能上的优势。现在我的 Neovim 安装有 coc.nvim、vim-visual-multi 等插件,共十个,启动需要延迟近 0.5 秒,虽然已经显著快于 VS Code,但相比 Helix 的小于 0.1 秒,还是稍显逊色。(当然这可能与我现在用的是 Windows 有关) 虽然现在(2023年4月8日)Helix 还没有插件系统,但未来的插件系统大概率会用 WASM 实现,相比 Vim Script 以及 Neovim 用的 Lua 等脚本语言会更快。 编辑体验 整体思路 首先 Helix 和 Vim 一样都是模态编辑器,具有多种模式,最基本的操作思路是一样的,比如都使用 hjkl 进行移动,都使用 i、a 等进行插入,都使用 y,d、p 进行复制删除粘贴。但是 Helix 采用了 selection -> action 模式,比如向右删除 3 个字符需要按 3ld 而不是 d3l,先选择在操作,就我个人而言,这种方式确实更舒适。 另外,Helix 的命令有丰富的提示,而且还可以通过 <space> ?...

April 8, 2023 · 5 min · oosquare

用 Rust 开发 Brainfuck 解释器

项目地址:https://github.com/oosquare/brainfuck-interpreter Brainfuck 是什么就不具体介绍了,可以看这里。以下简称 bf。 这个解释器实现总体上是比较简单的,但是相比其他的大多数解释器还是有比较多的不同之处,具体如下: 更多的配置选项 内存的长度与地址范围:可配置为负数 单个内存单元的数据类型 数据溢出处理机制:wrap 或错误 读到 EOF 的处理机制:返回 0、EOF 本身或不改变 优化指令 采用类似编译为字节码的机制 这个项目可以算是学习 Rust 的练手项目,尝试着用了如 clap 这样的 crate。接下来就介绍一些技术细节。 使用方法 具体说明在项目 README. 编译、安装、执行全过程: $ git clone https://github.com/oosquare/brainfuck-interpreter.git $ cd brainfuck-interpreter $ cargo install --path ./crates/bf-exec # The program will be installed to ~/.cargo/bin $ bf-exec ./examples/helloworld.bf Hello World! ./examples/helloworld.bf: ++++++++++[>+++++++>++++++++++>+++>+<<<<-]>++.>+.+++++++..+++.>++.<<+++++++++++++++.>.+++.------.--------.>+.>. 实现原理 目前,项目分为两个 crate:common 与 bf-exec,其中 common 实现了解释器的所有逻辑,而 bf-exec 是 common 的前端,负责处理输入和配置。 common 的模块树如下: complier:解析代码并转换为 IR (Intermediate Representation,中间表示) lexer:词法分析 parser:语法分析并优化,生成 AST SyntaxTree syntax:AST 生成 optimizer:AST 优化 instruction:根据 AST 生成 IR,同时也是最终执行的指令 execution:IR 的执行与相关环境 memory:按照 bf 的内存模型实现的可配置内存 strategy:基于策略模式实现的可配置组件 config:构建 Memory 的配置 stream:bf 的 IO 实现 config:构建 InStream、OutStream 的配置 context:Memory 与 InStream、OutStream 的组合 processor:运行指令,并调用 Context 实现细节 代码优化 同质代码合并 首先最简单的就是这个优化,它是指把相邻的 + 与 -、< 与 > 合并到一起并加上一个重复次数。...

January 26, 2023 · 7 min · oosquare