文章《停止转发错误,开始设计错误》
核心思想: 错误处理不应只是简单的“向上抛(Forwarding)”,而应该经过“设计(Designing)”,以服务于“机器自动化恢复”和“人类排查代码”两个目标。
作者认为当前的 Rust 错误处理实践过于关注“类型如何匹配”,而忽视了错误的实际用途。
现有方案的局限性:
thiserror的弊端: 倾向于按**来源(Origin)**分类(如DatabaseError,SerdeError),但这不能告诉调用者“下一步该做什么”(该重试吗?该显示给用户吗?)。anyhow的弊端: 过于便捷,导致开发者容易跳过.context(),使得错误向上流转 20 层后变成毫无语义的原始底层报错(如:JSON 解析失败,但不知道是哪行业务代码引发的)。- 堆栈追踪(Backtrace)不是万能药: 在异步代码中,堆栈追踪往往包含大量 Future 轮询噪声,且捕获开销大。
核心理念:面向受众设计
- 面向机器(Automated Recovery): 错误需要是**扁平、可行动、按类别(Kind-based)**划分的。参考 OpenDAL 的设计:不仅有
ErrorKind(NotFound, RateLimited),还有显式的ErrorStatus(Permanent/永续失败, Temporary/可重试失败)。 - 面向人类(Debugging): 错误需要提供富上下文和逻辑路径。
- 减少摩擦: 使用
#[track_caller]自动捕获源码行号,而不是全量的堆栈追踪。 - 强制添加上下文: 在跨越模块边界时,利用类型系统强制要求添加业务语境(作者为此编写了
exncrate)。
- 减少摩擦: 使用
技术方案建议:
- 在模块边界使用类似于
exn的方式包装错误,使得调用方如果想获取底层错误,必须显式经过一次上下文转换。 - 避免深度嵌套的错误枚举,转向带有状态标记(如 retryable)的扁平结构。
Reddit 上的开发者们针对博文提出的激进观点进行了激辩:
关于重试逻辑的归属:
- 一些用户质疑在错误对象中嵌入
retryable(可重试性)标志。他们认为“是否重试”应由调用方根据其自身的策略决定。 - 作者回应:底层组件通常比调用方更了解失败的原因(例如,网络 404 与磁盘损坏)。返回一个建议性的
Permanent/Temporary状态是协助调用方决策的最佳平衡。
关于 exn 工具和上下文格式:
- 反对意见: 部分人不喜欢使用字符串插值来格式化错误,认为这会丢失结构化数据。
- 替代建议: 讨论中提到了
tracing::event!式的结构化记录,或使用rootcause、color-eyre、snafu等成熟库来增强可观察性。 - 技术细节: 有用户指出
exn似乎是在绕弯路重写还没稳定的 RustProvider API(RFC 3192)。
开发哲学与契约设计:
- 部分开发者推崇“设计契约(Design by Contract)”。他们认为过多的错误处理标记可能说明系统边界设计模糊。
- 赞同者认为文章点到了痛点:许多中大型 Rust 项目的代码库中充满了一堆由
thiserror生成的、除了解压到顶层打印之外别无他用的层叠枚举(The Onion of Enums)。
总结: 不要只做“中转站”用 ? 转发底层报错,每个模块都应为它抛出的错误定义明确的 性质 (对机器可见的状态码)和 情境 (对人可见的具体参数与操作目的)。这样在生产环境凌晨 3 点报警时,日志里显示的是 failed to fetch user_id 123 而不是一行冰冷的 serialization error。
阅读:https://fast.github.io/blog/stop-forwarding-errors-start-designing-them/
rtc:Sans-I/O 的 WebRTC 协议栈
rtc 库发布了 0.3.0 版本。该项目是 webrtc-rs 组织下的核心库,旨在用纯 Rust 构建一个高性能、Sans-I/O(无 I/O 相关性) 的 WebRTC 协议栈。
项目背景与愿景:从“Go 移植”到“Rust 原生”:
- 摆脱 Pion 的影子: 早期 Rust 的 WebRTC 实现(webrtc-rs)很大程度上是 Go 语言项目
pion/webrtc的直接移植。这导致了许多“不符合 Rust 习惯”的设计,如大量的通道(Channels)使用和复杂的异步运行环境耦合,使得性能调优和维护非常困难。 - 全新架构:
rtc项目(特别是 0.3.0 版)是对 WebRTC 栈的一次重新构思,完全使用 Rust 原生思维编写。
核心设计模式:Sans-I/O (无 I/O 架构)。这是该版本最大的亮点。所谓的 Sans-I/O 意味着:
- 逻辑与网络分离: 核心协议栈(ICE, DTLS, SCTP, SRTP 等)只是一个纯粹的状态机。它不直接操作网络套接字(Sockets),不直接读取系统时间,也不持有任何具体的异步 Runtime(如 Tokio 或 async-std)。
- 纯字节操作: 用户通过调用方法将原始字节推入状态机,并从中提取状态机产出的包。
- 带来的优势:
- 极高可测性: 无需网络模拟,即可在单线程中通过简单的内存缓存区测试整个协议逻辑。
- 极致复用性: 可以在任何环境运行(Tokio、smol、WASM,甚至是 embedded/no-std 环境,因为不需要标准库的网络 API)。
- 无锁并发: 这种模式避免了在内核态和用户态之间频繁加锁。
rtc 0.3.0 的技术重点:
- 模块化重构: 将复杂的 WebRTC 协议拆分为独立的组件(如
rtc-ice,rtc-dtls等),各组件保持 Sans-I/O 属性,并在rtc主库中组合。 - 改进的 API 设计: 采用了更轻量级的状态同步模型,而非庞大的全局对象模型。
- 高性能实现: 对缓冲区(Buffers)的重用进行了优化,减少内存分配(Allocations)。
- 全功能支持: 包括但不限于交互式连接建立 (ICE)、DTLS 数据安全连接、SCTP (Data Channels) 以及 SRTP (音视频加密传输)。
社区反馈:
- 开发者兴奋度: Reddit 用户普遍认为“Sans-I/O”是 Rust 实现网络协议的最优解,因为 WebRTC 协议本身异常复杂,解耦 I/O 能让问题定位变得清晰很多。
- 与主流竞争: 虽然此前也有类似思想的项目(如
str0m),但rtc依托于webrtc-rs组织,拥有更好的生态位和文档维护。 - 迁移挑战: 虽然新版本设计更先进,但从旧的类似 Go 风格的 API 迁移到这种底层状态机驱动的 API,需要开发者更深入地理解网络层工作机制。
总结:
rtc 0.3.0 标志着 Rust 已经具备了目前业界最成熟、架构最优雅的 Sans-I/O WebRTC 实现。 它的发布将极大地促进云游戏、流媒体分发和去中心化实时通信项目在 Rust 生态中的落地,特别是那些对低延迟和高定制化 I/O 运行时有极高要求的场景。
仓库:https://github.com/webrtc-rs/rtc
red-apple:用 Cargo 编译输出播放
red-apple 是一个极具创意(且充满技术冷幽默)的项目,它 通过 cargo 的构建输出来播放经典动画《Bad Apple!!》。
《Bad Apple!!》在互联网文化(尤其是二次元和程序员圈子)中是一个极其特殊的图腾。它已经从一首普通的同人歌曲,演变成了衡量一台设备(或一段代码)性能与展现力的“终极跑分测试”。《Bad Apple!!》之所以成为程序员的挚爱,是因为那个黑白影绘动画有两个技术上的“神级属性”:
- 极简的色彩(二进制): 只有黑色和白色,这完美对应了计算机逻辑里的 0 和 1。
- 极高的识别度: 即便把画面压缩到非常低的分辨率,观众依然能认出里面的角色和动作。
当一个开发者说他在搞 Bad Apple 时,他通常不是在推销这首歌,而是在进行一次技术炫技。他想证明:“我看,这个系统的操控精度已经高到能用来‘播放视频’了。”
项目核心概念:
- 不仅仅是字符画: 传统的“Bad Apple”终端项目通常是直接在终端打印 ASCII 字符。但
red-apple的不同之处在于,它是通过cargo build的过程动画来呈现画面的。 - “Cargo 为播放器”: 当你运行构建命令时,原本显示“正在编译/下载包(Compiling...)”的列表和进度条,会因为项目极其精巧的设计,排列成《Bad Apple!!》的每一帧画面。
技术实现原理:
- 数千个虚拟包(Crate Sprawl): 该项目生成了成千上万个极小的本地 Crates。每个 Crate 的名字、版本号或目录名被精心设计成了视频中的一像素(或一组像素)。
- 利用并行下载/编译进度:
cargo在处理依赖项时会并行更新终端行。开发者通过控制 Cargo 处理这些虚假依赖项的顺序和并行度,使终端上不断滚动的进度信息实时拼凑出了动画。 - 生成器工具: 仓库中包含了一个处理原视频文件的 Python/Rust 脚本,它会将视频帧转化为庞大的、层级极其复杂的 Cargo 依赖关系图(Dependency Tree)。
社区反应:
- “最 Rust 的 Bad Apple”: 网友一致公认这是将 Rust 工具链“玩坏”的最佳范例。大家笑称:“终于给高性能的并行编译找到了它最本真的用途。”
- 对 Linker 的怜悯: 许多讨论集中在:“这得给 linker(链接器)带来多大的压力?”、“跑一次这个项目我的磁盘写入量是不是要增加几个 GB?”。
- 性能玩笑: “如果
cargo是在 0.1s 内完成构建的,那是动画结束了吗?还是我需要一个更慢的 CPU 来享受高帧率?”
项目意义:
这属于编程圈中典型的 “只是因为我可以(Just because I can)” 系列作品。它展示了对 cargo 构建系统内部行为的极端控制,以及 Rust 社区深厚的“黑客文化”。
一句话吐槽: 以前我们抱怨 Cargo 编译慢是在浪费生命,现在你可以说:“我不是在等编译,我是在看大片。”
仓库:https://github.com/ALinuxPerson/red-apple
--
From 日报小组 苦瓜小仔
社区学习交流平台订阅:
评论区
写评论还没有评论