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

MultiGenerator 使用文档

概述 MultiGenerator 作为早年我的试验项目,已不再适合于实际使用。如果想要生成数据,请使用性能更高且更简单的 Rust 实现:data-gen-rs。 MultiGenerator 是一个为 OI 而生的多线程并行数据生成库,基于 C++ 17,使用面向对象和泛型等 Morden C++ 高级特性,只需要添加最少的额外代码,就可以获得最高的性能。以下是一个能够指定数据范围的 A + B Problem 数据生成器的示例代码: #include <random> #include <MultiGenerator.hpp> using MultiGenerator::DataConfig; using MultiGenerator::GeneratingTask; using MultiGenerator::SolutionTask; using MultiGenerator::NormalTemplate; using MultiGenerator::entry; using MultiGenerator::testcase; /** 指定数据生成器,仅需继承一个抽象类和实现一个成员函数 */ class AddGenerator : public GeneratingTask { private: void generate(std::ostream &data, const DataConfig &config) override { /** DataConfig 为配置信息,可以用于储存数据范围等元信息 */ auto minValue = std::stoi(config.get("minValue").value()); auto maxValue = std::stoi(config.get("maxValue").value()); std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dist(minValue, maxValue); /** 像 cout 一样输出生成结果 */ data << dist(gen) << " " << dist(gen) << std::endl; } }; /** 指定数据求解器,也仅需继承一个抽象类和实现一个成员函数 */ class AddSolution : public SolutionTask { private: /** 假如你有标程,仅需要把程序用这个类包装起来,再把 main() 改为这个成员函数即可 */ void solve(std::istream &dataIn, std::ostream &dataOut, const DataConfig &) override { int a, b; /** 像 cin 一样读入数据 */ dataIn >> a >> b; /** 像 cout 一样输出答案 */ dataOut << a + b << std::endl; } }; int main() { constexpr int MAX_THREAD_COUNT = 8; constexpr int MAX_TESTCASE_COUNT = 20; constexpr char PROBLEM_NAME[] = "add"; /** 创建一个题目生成模板,指定数据文件名为 add#....

April 4, 2022 · 5 min · oosquare