<rss version="2.0"><channel><title>Rust.cc</title><link>https://rust.cc</link><description>This Is Rust Crustacean Community RSS feed.</description><item><title>Looking for Execution Systems Dev</title><link>https://rustcc.cn/article?id=384d6f0a-8cb7-4904-b4ac-eb601ff874c0</link><description><![CDATA[<p>Requirements</p>
<ol>
<li>Programming &amp; Systems</li>
</ol>
<ul>
<li>熟悉 Rust ，精通 Python</li>
<li>对编译原理、Data Warehouse 有自己的理解</li>
</ul>
<ol start="2">
<li>Trading Pipeline Tooling
专注交易链路上的工具开发，包括：</li>
</ol>
<ul>
<li>data pipeline 、message bus 扩展</li>
<li>risk control tools</li>
<li>execution algo</li>
</ul>
<ol start="3">
<li>Framework</li>
</ol>
<ul>
<li>了解 Nautilus Trader 框架 https://github.com/nautechsystems/nautilus_trader</li>
</ul>
<p>Compensation</p>
<ul>
<li>Base Salary: $5,000 – $10,000 USD / month</li>
<li>Bonus: Project based bonus</li>
<li>Office Location: Hong Kong / Remote</li>
</ul>
<p>Contact Email: profilesai@proton.me
(请直接提交简历和个人 GitHub 数学，统计学专业优先考虑)</p>
]]></description><pubDate>2026-07-24 03:55:33</pubDate></item><item><title>【Rust日报】2026-07-24 Arctic 发布：无锁并发有序映射进了 Rust 生态</title><link>https://rustcc.cn/article?id=fe93c248-d1d7-449d-b4f1-d59222380c77</link><description><![CDATA[<h2>Arctic 发布：无锁并发有序映射进了 Rust 生态</h2>
<p><code>arctic</code> 这次值得关注，不只是因为它又给 Rust 生态加了一个并发容器，而是因为作者瞄准的是更难啃的一类基础设施：<strong>并发、有序、还要尽量无锁</strong>。项目来自 OSDI '26 论文背景，底层基于 <strong>adaptive radix tree (ART)</strong>，目标场景非常直接——像 LSM memtable、MVCC 数据库索引这类既要有序访问、又要高并发读写的数据结构。</p>
<p>作者给出的能力边界很硬：<strong>lock-free 的线性一致写入</strong>、<strong>wait-free 的线性一致读取</strong>，以及 <strong>wait-free 的 prefix / range scan</strong>。这不是那种只在 README 里写几句“高性能”就结束的发布，帖子里还直接拿 80 个物理核、95% 读 5% 写的 workload 去和一批学术界 C/C++ 系统以及 Rust crate 做横向比较，想表达的是：Rust 在这类高并发有序索引上，不一定只能当“安全但慢一点”的实现。</p>
<p>更重要的是，这个项目把很多 Rust 开发者平时只会在论文里看到的主题拉近了一步：内存序、指针 provenance、SIMD、测试，以及类型安全和编译复杂度之间的现实权衡。对做数据库、KV、存储引擎和系统并发结构的人来说，<code>arctic</code> 不是普通的新 crate，而更像是一个能拿来研究和试用的高阶样板。</p>
<p>原文链接：https://old.reddit.com/r/rust/comments/1v4f05y/announcing_arctic_a_lockfree_concurrent_ordered/</p>
<h2>oximo v0.5.0 发布：宏式建模、自动微分与锥优化一起补强</h2>
<p>做数学优化建模的人，往往一眼就能看出 <code>oximo</code> 0.5.0 这次升级的价值所在：它不只是加几个 solver backend，而是开始把“Rust 里写优化模型”这件事做得更像一门真正可用的 DSL。新版本最显眼的是 <strong>macro-based modeling API</strong>，思路明显借鉴了 JuMP 和 <code>good_lp</code> 这类成熟建模体验，让变量、约束、目标函数的表达更贴近建模语言，而不是手工在宿主语言里拼很多样板代码。</p>
<p>除此之外，0.5.0 还把能力边界实打实往前推了一截：<strong>自动微分</strong> 走上了 <code>std::autodiff</code> / Enzyme 路线（目前 nightly），solver backend 新增 <strong>Clarabel</strong> 和 <strong>Pounce</strong>，模型类型进一步扩到 <strong>SOCP</strong> 与 <strong>MISOCP</strong>。这意味着它已经不再只是“小而美的 LP/QP 玩具库”，而是在朝更完整的代数优化建模工具链发展。</p>
<p>对 Rust 生态来说，优化建模一直不是最热闹的赛道，但也正因为如此，像 <code>oximo</code> 这种把 DSL 可读性、自动微分和求解器接入一起往前推的项目，反而更值得留意。它很可能不会像通用 Web 框架那样瞬间刷屏，但对科研、运筹、工业优化和教学场景来说，这类库一旦成熟，黏性会非常强。</p>
<p>原文链接：https://old.reddit.com/r/rust/comments/1v4av6j/oximo_v050/</p>
<h2>Battery packs：把“该选哪些 crate”变成可发布、可演化的默认组合</h2>
<p>Niko Matsakis 这篇关于 <strong>battery packs</strong> 的文章，戳中的其实是 Rust 新用户和团队迁移者最常见的一个痛点：生态太丰富当然是好事，但每次从零比较 CLI、Web、嵌入式、错误处理、CI 配套 crate，也确实很消耗决策精力。battery pack 的想法，就是把一组围绕共同主题整理好的 crate 推荐集打包成一个可发布、可演化的“默认组合”，让大家先有一条靠谱起跑线，而不是每次都从 crates.io 的海里重新捞。</p>
<p>这个设计有意思的地方在于，它<strong>不是新的标准库，也不是强绑定的框架</strong>。battery pack 本身可以作为 crate 发布，里面的依赖、feature 和模板就代表推荐组合；用户最终依赖的依旧是里面的具体 crate，而不是对 battery pack 建一个硬耦合。这意味着它既能给新手和团队提供“先这样选通常没错”的默认答案，又不会把生态冻结成唯一正解。今天你跟着 pack 起步，明天发现更适合自己的替代品，完全可以换。</p>
<p>文章还把这件事往组织层面再推了一层：工作组、商业网络、垂直领域社区都可以发布自己的 battery pack，把“我们真正在生产里用什么”公开成一套共享建议。对 Rust 来说，这比单纯再多几篇“生态推荐清单”更进一步，因为它开始尝试把经验、默认实践和互操作方向，变成一套可分发、可更新、甚至能带模板和自动化动作的生态机制。</p>
<p>原文链接：https://smallcultfollowing.com/babysteps/blog/2026/07/15/battery-packs/</p>
<h2>coral-rs：纯 Rust 驱动把 Google Coral USB Accelerator 救活</h2>
<p><code>coral-rs</code> 的亮点很鲜明：作者没有去继续包一层早就摇摇欲坠的 C++ 旧栈，而是直接把 <strong>Google Coral USB Accelerator</strong> 的 userspace 协议在 Rust 里重新跑通。项目建立在 <code>nusb</code> 之上，整个推理路径做到<strong>进程里没有 C 依赖</strong>，从 DFU 刷固件、vendor control transfer 拉起设备，到从 <code>*_edgetpu.tflite</code> 里拆出模型、补设备地址、再经 bulk endpoint 把指令流送进去，整条链路都用纯 Rust 实现。</p>
<p>更妙的是，这不是“能跑起来就算成功”的移植。作者拿真实硬件验证后，给出的结果是：在同一设备和同一图片上，输出和官方栈 <strong>bit-identical</strong>，但性能居然还更快——帖子里提到 <code>mobilenet v1</code> 走 USB3 时，单次推理 <strong>6.8ms vs 12.1ms</strong>。也就是说，它不仅把一个被官方半放弃、生态逐渐发霉的设备重新带回现代环境，还顺手证明了 Rust 直接写这类硬件侧运行时并不只是“可行”，而是有机会做得更轻、更快。</p>
<p>当然限制也讲得很实在：当前只支持 <strong>USB Accelerator</strong>，M.2 / PCIe 不是同一条路径；模型编译仍然离不开 Google 的闭源编译器。但即便如此，对关心边缘推理、USB 设备栈、推理 runtime 或纯 Rust 硬件接入的人来说，<code>coral-rs</code> 这类项目的示范意义已经很强了。</p>
<p>原文链接：https://old.reddit.com/r/rust/comments/1v48nwq/coralrs_a_pure_rust_driver_for_the_google_coral/</p>
]]></description><pubDate>2026-07-24 01:07:08</pubDate></item><item><title>感觉现在是rust写软件的最好时候，AI 写的蛮好的</title><link>https://rustcc.cn/article?id=68e5126e-adc1-4525-97e6-f634353f42e8</link><description><![CDATA[<p>之前手写了几年的rust。近一年都是让AI来写了，效果蛮好的。享受了rust语言的红利，规避了rust最大的学习和编码成本。</p>
]]></description><pubDate>2026-07-23 10:08:06</pubDate></item><item><title>GUI 依赖图的另一种答案：用 Rust 的 Drop 做零成本清理</title><link>https://rustcc.cn/article?id=c7b9bc5e-2fad-42fc-983e-5302127be8ca</link><description><![CDATA[<p>做一个 GUI 框架，最难的不是画像素，而是回答一个问题——</p>
<blockquote>
<p>"状态变了，谁该更新？"</p>
</blockquote>
<p>这是所有 GUI 框架的共同敌人。过去四十年，每一代框架都在用不同的方式跟它搏斗。</p>
<p>Win32 / Swing / Qt 选择<strong>命令式手动更新</strong>——状态变了，你负责找到每一个依赖它的 UI 元素并手动更新。没有 diff 开销，但心智负载随应用规模指数增长。调了一个变量，忘了更新对应按钮的 enabled 状态——这类 bug 每个人都被咬过。</p>
<p>Flutter / SwiftUI / Jetpack Compose 选择<strong>声明式树 diff</strong>——你声明 UI 应该长什么样，框架在运行时比较新旧树，算出最小更新。状态同步自动化了，但 diff 本身的开销不可预测，而且整个对账过程是个黑盒——你不知道哪个元素脏了，框架替你决定的。</p>
<p>Slint 选择<strong>编译时依赖分析</strong>——编译器在编译期消解 property binding 之间的依赖关系，生成直接更新的代码。运行时不追踪依赖，效率极高。代价是 UI 必须用 <code>.slint</code> 文件编写，不能纯 Rust 组合，而且运行时动态性受限。</p>
<p>Iced 选择 <strong>Elm 架构</strong>——<code>Model → update(Msg) → view()</code> 的闭环，类型安全的消息分发。但在 Rust 里，消息枚举必须集中定义，每个新 widget 都要往同一份枚举里加变体。小应用优雅，大应用单点耦合。</p>
<p>Xilem 选择<strong>视图树重建 + 手动 diff</strong>——每次状态变化重跑整个 app 逻辑，生成新视图树，然后每个 View 的 <code>rebuild()</code> 方法逐字段比较新旧值。这套机制本身没问题，但它是在 Rust 里重现 React/SwiftUI 的思路。</p>
<p>这些全都是正确答案。但它们都在回答同一个问题："怎么把某个已有的 GUI 范式搬进 Rust？"</p>
<p>我换个问题试了试——</p>
<blockquote>
<p>"如果不用 Rust 去适配某个已有的 GUI 范式，而是让 GUI 范式从 Rust 中长出来，会长什么样？"</p>
</blockquote>
<p>三个月后，<a href="https://github.com/chh-itt/burin" rel="noopener noreferrer">Burin</a> 出现了。</p>
<hr>
<h2>三个东西：Drop、ownership、async</h2>
<p>Rust 有三个东西，是 C++ / Dart / Swift / Kotlin 都没有原语级支持的：</p>
<ol>
<li><strong><code>Drop</code></strong>——值在离开作用域时自动执行清理逻辑。不需要 <code>try-finally</code>，不需要 <code>defer</code>，不需要 GC。</li>
<li><strong>ownership</strong>——每个值有唯一的所有者，所有者的生命周期决定值的生命周期。引用不会悬垂，借用检查器在编译期就给你保证了。</li>
<li><strong><code>async</code></strong> / <code>.await</code>——异步任务的自然暂停点。配合 executor，延迟回调自动批处理，天然避免回调重入。</li>
</ol>
<p>这三个放在一起，暗示了一套 GUI 架构——</p>
<hr>
<h2>信号 → 脏标记 → 渲染：一条没有岔路的管线</h2>
<p>Burin 的核心机制只做了一件事：</p>
<p><strong>把 Signal 的订阅闭包和 Element 的脏标记直接焊在一起。</strong></p>
<h3>挂载时</h3>
<p>当一个元素绑定到 <code>Signal</code>，订阅闭包在创建时就已经知道了三样东西：</p>
<ul>
<li>目标 <code>ElementId</code></li>
<li>要设置的脏标记（<code>REPAINT</code>）</li>
<li>信号本身</li>
</ul>
<pre><code>signal.subscribe(|| register_dirty(element_id, REPAINT))
</code></pre>
<p>这不是"通过依赖图查找谁依赖了我"——闭包在创建时，目标 ElementId 是<strong>写死的</strong>。</p>
<h3>Signal 写入时</h3>
<p><code>Signal::set()</code> 触发，遍历 subscriber 列表，调用每个闭包。每个闭包直接就位——<code>register_dirty(element_id, REPAINT)</code>，一步到位。</p>
<p>不查图。不遍历。不拓扑排序。</p>
<p>整个过程从 Signal 写入到脏标记到位，是一条直线：</p>
<pre><code>Signal::set()
  → register_dirty(O(1))
  → process_dirty_set(O(k) 祖先上行，在 containment boundary 停止)
  → Taffy 增量布局（4 路径：MEASURE / REPOSITION / 跳过）
  → SubtreeCache 检查（未变更子树直接回放）
  → 仅绘制脏子树
  → GPU（wgpu）或 CPU（tiny-skia）
</code></pre>
<p>每一层都知道什么变了、什么没变。没变的完全跳过。</p>
<h3>元素销毁时</h3>
<p>Element 持有 <code>LifecycleComponent</code>，里面存着所有订阅句柄。元素从 arena 移除时，<code>LifecycleComponent</code> 被 <code>Drop</code>——所有订阅闭包自动退订。</p>
<p>不需要手动取消订阅。不需要 GC。不需要图遍历。</p>
<p><strong>Signal 只管推，Element 只管活，活够了就走。一切订阅关系在编译期已经被 Rust 的所有权模型静态化了。</strong></p>
<hr>
<h2>那 Flutter 是怎么做的？</h2>
<p>写完这套机制后，我不放心——是不是我想多了？我去读了 Flutter、Slint、Xilem 的源码（对，本文的每个描述都是源码验证过的）。</p>
<p>Flutter 的 <code>BuildOwner._dirtyElements</code> + <code>PipelineOwner._nodesNeedingLayout</code> 用的是一个<strong>完全相同的脏标记模型</strong>：O(1) 标记 + O(k) 处理 + relayout/repaint boundary 短路。</p>
<p>Slint 的 <code>Property&lt;T&gt;</code> 在运行时通过 thread-local <code>CURRENT_BINDING</code> 自动注册依赖，和 Burin 的 Signal 订阅底层机制几乎一致。</p>
<p>Xilem 的 <code>View::rebuild()</code> 做的是逐字段 diff，但它的下层 Masonry 有一个保留模式 widget 树，脏标记系统也是同样的 O(1) 标记 + O(k) 处理。</p>
<p><strong>三者殊途同归。</strong></p>
<p>区别不在于"谁更先进"——</p>
<ul>
<li>Flutter 靠 Google 工程团队多年迭代，百万行代码</li>
<li>Slint 靠 DSL 编译器生成使用 <code>Property&lt;T&gt;</code> 的代码</li>
<li>Burin 用 Rust 的 Signal + ownership + Drop，15 万行</li>
</ul>
<p>这不是设计上的巧思。是这三种语言的能力不同，导致了工程成本的数量级差距。</p>
<hr>
<h2>代码长什么样</h2>
<p>纯 Rust，无 DSL，无宏 DSL，无代码生成。IDE 的补全、跳转定义、重构全部开箱即用：</p>
<pre><code>use burin::prelude::*;
use auralis_signal::Signal;

fn counter() -&gt; impl Widget {
    Compositor::new(|_scope| {
        let count = Signal::new(0i32);
        let label = Signal::new("0".to_string());

        VStack::new()
            .gap(12.0)
            .push(Text::new("Counter").font_size(24.0))
            .push(Text::new("0").bind(label.clone()).font_size(48.0))
            .push(Button::new("+1").on_click({
                let c = count.clone();
                let l = label.clone();
                move || {
                    let n = c.read() + 1;
                    c.set(n);
                    l.set(n.to_string());
                }
            }))
    })
}

fn main() {
    App::new()
        .window(WindowConfig::default(), counter())
        .run()
        .unwrap();
}
</code></pre>
<hr>
<h2>目前能做什么</h2>
<ul>
<li><strong>60 个内置 Widget</strong>：布局（VStack / HStack / Grid / ScrollView / SplitPane...）、输入（Button / TextInput / Checkbox / Slider / ComboBox...）、显示（Text / Image / Table / Tree / Chart...）、覆盖层（Modal / Popover / Tooltip / ContextMenu...）</li>
<li><strong>双渲染后端</strong>：wgpu（GPU）和 tiny-skia（CPU）共享同一套 <code>Painter</code> API，GPU 不可用时自动回退</li>
<li><strong>手势竞技场</strong>：7 种 Recognizer（Tap / Drag / EagerDrag / LongPress / DoubleTap / Scroll / Custom），一个 PointerDown 不会同时触发点击和拖拽</li>
<li><strong>Material 3 主题</strong>：HCT 色彩引擎，单种子色 → 完整亮/暗调色板</li>
<li><strong>TestHarness</strong>：无窗口全帧测试，快照回归，录制回放</li>
<li><strong>DevTools</strong>：运行时检查运行中 GUI 的 Signal 状态、元素树、性能面板</li>
</ul>
<hr>
<h2>不适合的场景</h2>
<ul>
<li><strong>快速原型</strong> → 用 Egui。即时模式的简洁无法被保留模式超越。</li>
<li><strong>嵌入式 / MCU</strong> → Slint 的编译时优化 + <code>#![no_std]</code> 是正确答案。</li>
<li><strong>已有 Dart/JS 团队的跨平台项目</strong> → Flutter / React Native 生态不可替代。</li>
</ul>
<hr>
<h2>还在早期</h2>
<p>Burin 是我和 <a href="https://chat.deepseek.com" rel="noopener noreferrer">DeepSeek</a> 共同开发的——三个月，15 万行 Rust，从 Signal 到 GPU 一条管线。能跑，但不代表它不会出错。</p>
<p>如果你是 Rust 开发者，觉得这个方向有意思，欢迎来试试。也欢迎指出哪里还不成熟——issue、PR，或者只是想聊聊，都可以。</p>
<p>项目地址：<a href="https://github.com/chh-itt/burin" rel="noopener noreferrer">github.com/chh-itt/burin</a></p>
]]></description><pubDate>2026-07-23 08:26:02</pubDate></item><item><title>【Rust日报】2026-07-22 Wasmtime 47 默认启用 Wasm GC 与异常支持</title><link>https://rustcc.cn/article?id=3621442a-7d01-40f7-8b3b-1cf7e83fb2ee</link><description><![CDATA[<h2>Wasmtime 47 默认启用 Wasm GC 与异常支持：高阶语言进军 WebAssembly 又少了一层自带运行时包袱</h2>
<p>Bytecode Alliance 这次放出的信号很强：<strong>Wasm GC</strong> 和 <strong>异常处理（exceptions）</strong> 已经在 <strong>Wasmtime 47</strong> 中默认开启。对 WebAssembly 生态来说，这不是单纯“多支持两个 proposal”，而是意味着更多高阶语言终于可以更自然地把对象模型、引用类型和异常机制直接带进 Wasm，而不必再在 <code>.wasm</code> 包里自带一套沉重的垃圾回收器或手搓异常调用约定。</p>
<p>文章把这两条能力讲得很透。GC 这一侧，Wasm 程序可以直接定义自己的 struct / array 类型和子类型关系，让运行时负责对象生命周期；异常这一侧，则不必再让每个函数额外返回“是否抛错”的状态位，能把 try/catch 风格重新交回给运行时实现。这两件事叠在一起，带来的不只是模型更优雅，也会直接影响 <strong>二进制体积、正常路径运行开销以及语言接入 Wasm 的工程复杂度</strong>。</p>
<p>更值得 Rust 社区关注的是 Wasmtime 自己的实现路线。团队没有把 GC 做成一层脱离现有架构的特例，而是用 <strong>Cheney 风格半空间复制 GC</strong>，并把 GC heap 建在 WebAssembly 线性内存之上，继续吃到 Wasmtime 已有的安全、可移植和虚拟内存护栏能力。再配合针对 Wasm GC 扩展过的 fuzzing 基建，这次默认启用更像是“多年工程收口后的里程碑”，而不是实验性开关。对做运行时、编译器、Wasm 平台的人来说，这条线的长期影响会比单次版本更新大得多。</p>
<p>原文链接：https://bytecodealliance.org/articles/wasmtime-gc</p>
<h2>Syn 3.0.0 发布：Rust 宏解析事实标准库为新语法和未来 RFC 预留出更大空间</h2>
<p><code>syn</code> 3.0.0 的份量，不在于“又发了个大版本”，而在于它更新的是 Rust 生态里最基础也最容易被忽视的一层：<strong>宏与语法树解析基础设施</strong>。过去三年 Rust 语言本身一直在持续演进，而 <code>syn</code> 这次把大量已经落地、正在推进、以及需要提前留接口空间的语法变化统一吸收进来，等于是在为整个 procedural macro 生态做一次底座级升级。</p>
<p>这版 release notes 最醒目的点，是为了给未来语言发展留足余量，新增了一批 <strong>非穷举的 <code>*Modifiers</code> 结构</strong>，把原本容易被写死的语法位置改成“允许未来扩展但当前默认无 token”的设计。与此同时，类型、表达式、pattern、item、generics 等多块 AST 也都有实质性调整：像 <code>Type::BareFn</code> 改名成 <code>Type::FnPtr</code>、<code>Arm</code> 的 guard 改由新的 <code>Pat::Guard</code> 承载、类型和 where 谓词上的 attributes 保留得更完整，都是会波及大量宏工具链的真实变化。</p>
<p>换句话说，<code>syn 3.0</code> 不是那种“表面版本号很大、实则只是清理 API”的版本，而是一次面向未来 Rust 语法形态的系统性整队。对维护 derive 宏、代码生成器、静态分析工具或者 DSL 的开发者来说，这类升级往往比普通应用层库更新更关键，因为它直接决定你能不能稳稳接住接下来几轮语言演进。</p>
<p>原文链接：https://github.com/dtolnay/syn/releases/tag/3.0.0</p>
<h2>mmap behind the scenes in a database：Append-only 数据库为什么反而适合把页缓存交给内核</h2>
<p>这篇长文的价值，在于它不是泛泛而谈“<code>mmap</code> 好不好”，而是把争议重新放回<strong>具体数据库模型</strong>里讨论。作者过去几年一直在做 TB 级 append-only 数据库，核心观点很鲜明：如果你的存储段在 seal 之后就是<strong>只读不可变文件</strong>，那么很多传统数据库里让 <code>mmap</code> 挨骂的问题，根本就不是同一个问题。</p>
<p>文章把这件事拆得很细。对 append-only 引擎来说，查询阶段面对的是已经落盘、不会原地修改的 segment；新写入数据靠普通文件 I/O 和 WAL 保证；而 <code>mmap</code> 只负责把这些只读 segment 映射进来，让随机访问直接站在 <strong>内核 page cache</strong> 之上。这样做的直接好处是：热页保留、冷页回收、缺页再拉回，全部交给操作系统处理，应用层不必再自己发明一套页级缓存淘汰策略。</p>
<p>更重要的是，它顺手把一个经常被混在一起的概念讲清了：<strong>文件映射页</strong> 和 <strong>匿名内存</strong> 的回收成本完全不同。只读 <code>mmap</code> 背后的 file-backed page 在内存紧张时可以被内核直接丢弃，之后按需再 fault 回来；但查询执行时自己在堆上建出来的 hash table、buffer、临时结果集，依旧是应用必须严控的 anonymous memory。也就是说，<code>mmap</code> 不是“数据库内存管理万能药”，但对于 immutable segment 这条线，它确实能把一大块复杂度安全地下放给内核。这种把争论从口号拉回 workload 形状本身的写法，很值得做存储和系统的人细看。</p>
<p>原文链接：https://savannahar68.medium.com/mmap-behind-the-scenes-in-a-database-263186dda699</p>
]]></description><pubDate>2026-07-22 01:07:03</pubDate></item><item><title>Rust 不必取代 Java：进入大型商业软件的一条现实路径</title><link>https://rustcc.cn/article?id=382de853-b38b-4a60-99ef-cff38b9de480</link><description><![CDATA[<blockquote>
<p><strong>Java 处理业务复杂度，Rust 承担运行时关键性。不同的微服务可以拥有不同的领域模型，但工程团队仍然可以拥有一致的开发体验。</strong></p>
</blockquote>
<p>Rust 的优点已经不需要反复证明。</p>
<p>内存安全、零成本抽象、可预测的运行时表现、优秀的工具链，以及对并发错误更强的编译期约束，使它非常适合构建基础设施、高性能网络服务和可靠性要求较高的软件。Rust 也长期保持着很高的开发者认可度。</p>
<p>但如果把视线从开源项目和基础设施软件转向金融、支付、供应链、制造、保险等大型商业系统，就会看到另一个现实：</p>
<p><strong>Rust 的实际覆盖面仍然远小于 Java。</strong></p>
<p>这并不意味着 Rust 不适合企业软件，也不意味着企业技术负责人没有看到 Rust 的优势。更重要的原因是，大型商业系统选择技术时，考虑的从来不只是语言本身。</p>
<p>他们还需要考虑：</p>
<ul>
<li>已经运行多年的 Java 系统；</li>
<li>Spring、数据库、消息队列、工作流和第三方 SDK 生态；</li>
<li>数十人甚至数百人的现有工程团队；</li>
<li>招聘、培训、运维和故障排查体系；</li>
<li>监管、审计和长期维护要求；</li>
<li>无数已经被生产事故验证过的异常路径。</li>
</ul>
<p>因此，我逐渐形成了一个判断：</p>
<blockquote>
<p><strong>Rust 进入大型商业软件的最好方式，可能不是替代 Java，而是进入 Java 已经建立起来的生态。</strong></p>
</blockquote>
<p>不是要求企业先建立一个完整的 Rust 世界，而是在现有系统中找到真正适合 Rust 的边界，让它逐步成为系统中不可替代的一部分。</p>
<hr>
<h2>一、Rust 面对的主要障碍，不一定是技术能力</h2>
<p>讨论 Rust 的企业落地时，我们经常从语言优点开始：</p>
<ul>
<li>Rust 没有 GC；</li>
<li>内存占用更低；</li>
<li>性能更可预测；</li>
<li>编译器可以发现更多错误；</li>
<li>容器镜像可以更小；</li>
<li>启动速度更快。</li>
</ul>
<p>这些都很重要，但它们并不能自动转化为企业采用。</p>
<p>大型商业系统的真实决策公式更接近：</p>
<pre><code>技术收益
- 迁移成本
- 人才成本
- 生态缺口
- 组织认知成本
- 生产风险
= 是否值得采用
</code></pre>
<p>一项技术即使在局部指标上更优秀，只要进入成本过高，企业仍然会选择继续优化现有系统。</p>
<p>Java 的优势也不仅仅是“历史悠久”。它已经形成了一整套企业级生产能力：</p>
<ul>
<li>Spring Boot 与 Spring Cloud；</li>
<li>成熟的 ORM、事务、消息和缓存生态；</li>
<li>数据库、支付、身份认证和企业中间件 SDK；</li>
<li>大规模团队协作经验；</li>
<li>监控、诊断、性能分析和运维体系；</li>
<li>大量理解 Java 的架构师、工程师和服务商。</li>
</ul>
<p>所以，对 Rust 社区而言，真正值得回答的问题不是：</p>
<blockquote>
<p>Rust 是否比 Java 更先进？</p>
</blockquote>
<p>而是：</p>
<blockquote>
<p><strong>如何让企业在不放弃现有 Java 资产的情况下，低风险地获得 Rust 的价值？</strong></p>
</blockquote>
<hr>
<h2>二、最困难的路线：先说服企业重写</h2>
<p>一种常见的技术叙事是：</p>
<pre><code>现有 Java 系统
    ↓
发现性能或可靠性问题
    ↓
用 Rust 重写核心系统
    ↓
获得更高性能和更低资源占用
</code></pre>
<p>这条路线在少数边界清晰的基础设施组件上可能成立，但对大型商业系统往往并不现实。</p>
<p>一个运行多年的支付或金融系统，其价值不仅存在于设计文档中，还存在于大量看似不优雅的代码里：</p>
<ul>
<li>某个银行接口偶发返回的特殊状态；</li>
<li>某个国家节假日造成的清算例外；</li>
<li>某类商户需要额外的合规审核；</li>
<li>某个历史版本遗留下来的兼容逻辑；</li>
<li>某次事故后增加的保护分支；</li>
<li>某项监管要求形成的审计流程。</li>
</ul>
<p>这些知识很难通过一次重写完整迁移。</p>
<p>重写还会带来新的风险：</p>
<ul>
<li>原有异常场景是否被完整覆盖？</li>
<li>新旧系统并行期间如何对账？</li>
<li>Rust 团队是否理解全部业务语义？</li>
<li>原有运维工具能否继续使用？</li>
<li>出现事故时，组织是否有足够的人能够排查？</li>
</ul>
<p>因此，Rust 的企业落地不应该建立在“推倒重来”之上。</p>
<blockquote>
<p><strong>企业真正需要的不是一次语言革命，而是一条可以逐步验证的采用路径。</strong></p>
</blockquote>
<hr>
<h2>三、更现实的入口：从微服务边界进入</h2>
<p>微服务提供了一个非常现实的机会。</p>
<p>只要服务边界足够清晰，企业就不必为整个系统选择唯一语言。不同微服务可以根据自己的工作负载、变化速度和运行时要求选择不同技术。</p>
<p>以一个支付平台为例，它可能同时包含两类性质差异很大的服务。</p>
<h3>1. 业务与集成复杂度较高的服务</h3>
<p>例如：</p>
<pre><code>Java Services
├── Merchant Onboarding
├── KYC / KYB
├── Compliance Workflow
├── Account Maintenance
├── Document Processing
├── Partner Integration
├── Reconciliation Investigation
└── Operations &amp; Administration
</code></pre>
<p>这类服务的主要难点通常是：</p>
<ul>
<li>流程长；</li>
<li>状态多；</li>
<li>外部集成多；</li>
<li>异常分支复杂；</li>
<li>业务变化频繁；</li>
<li>需要运营人员介入；</li>
<li>需要大量企业级组件。</li>
</ul>
<p>Java 的成熟生态在这里非常有价值。</p>
<p>我们可以把这一类复杂度称为：</p>
<blockquote>
<p><strong>Business Complexity：业务复杂度。</strong></p>
</blockquote>
<h3>2. 运行时关键性较高的服务</h3>
<p>例如：</p>
<pre><code>Rust Services
├── Client-facing Payment API
├── Payment Execution
├── Payment Routing
├── Real-time Pricing
├── Transaction Validation
├── Idempotency Control
├── Gateway Callback Processing
└── High-volume Event Processing
</code></pre>
<p>这类服务往往具有不同特征：</p>
<ul>
<li>面向最终用户；</li>
<li>请求量大或者流量波动明显；</li>
<li>延迟直接影响支付体验；</li>
<li>需要快速横向扩容；</li>
<li>对单实例内存和部署密度敏感；</li>
<li>服务边界相对清晰；</li>
<li>关注 P99、P99.9 等尾延迟指标。</li>
</ul>
<p>我们可以把这一类问题称为：</p>
<blockquote>
<p><strong>Runtime Criticality：运行时关键性。</strong></p>
</blockquote>
<p>这不是按照“重要”和“不重要”来划分系统。</p>
<p>商户入驻和 KYC 并不比支付执行不重要；合规流程甚至可能决定企业能否继续经营。两者只是面对不同类型的复杂度。</p>
<p>因此，更准确的表达是：</p>
<blockquote>
<p><strong>Java 吸收业务与集成复杂度，Rust 承担运行时关键性。</strong></p>
</blockquote>
<hr>
<h2>四、Rust 的价值不应被简化为“比 Java 快”</h2>
<p>在这种架构中使用 Rust，不能只依赖一句“Rust 性能更好”。</p>
<p>现代 JVM 已经非常成熟。经过良好设计和调优的 Java 服务，同样可以获得优秀的吞吐和延迟表现。许多所谓的性能问题，真正的瓶颈也可能位于：</p>
<ul>
<li>数据库；</li>
<li>网络调用；</li>
<li>锁竞争；</li>
<li>消息系统；</li>
<li>不合理的数据模型；</li>
<li>过多的服务跳转；</li>
<li>缺乏缓存；</li>
<li>错误的容量规划。</li>
</ul>
<p>把这些问题简单归因于 GC，通常是不严谨的。</p>
<p>Rust 更适合作为候选方案的情况是：</p>
<ol>
<li>服务边界已经比较稳定；</li>
<li>运行时成本确实是主要矛盾；</li>
<li>已经完成数据库、网络和架构层面的基本优化；</li>
<li>对尾延迟、内存密度、冷启动或弹性扩容有明确目标；</li>
<li>能通过基准测试和生产指标验证收益。</li>
</ol>
<p>例如，一个面向用户的支付执行服务，在活动期间可能需要迅速从少量实例扩容到大量实例。此时，Rust 的潜在收益可能不只是平均响应时间，而是：</p>
<ul>
<li>更低的单实例资源需求；</li>
<li>更高的容器部署密度；</li>
<li>更快的启动速度；</li>
<li>更可预测的运行时行为；</li>
<li>在高并发下更稳定的尾延迟；</li>
<li>编译期内存安全带来的可靠性收益。</li>
</ul>
<p>但这些收益必须通过真实工作负载验证，而不能只通过一个脱离业务的 Hello World benchmark 宣布。</p>
<blockquote>
<p><strong>Rust 应该通过可测量的运行时价值进入系统，而不是通过语言偏好进入系统。</strong></p>
</blockquote>
<hr>
<h2>五、不要为了双栈而共享一个“超级领域模型”</h2>
<p>当 Java 和 Rust 同时出现在一个系统中时，一个很自然的想法是：</p>
<blockquote>
<p>能否让两边共享完全相同的业务模型？</p>
</blockquote>
<p>在单体系统、SDK 或跨平台客户端中，这种方式可能有价值。但在微服务场景下，强行共享完整领域模型往往不是好主意。</p>
<p>例如，商户服务可能拥有：</p>
<pre><code>Merchant
BeneficialOwner
KycCase
ComplianceReview
MerchantDocument
</code></pre>
<p>支付执行服务可能拥有：</p>
<pre><code>PaymentIntent
Quote
PaymentRoute
Execution
GatewayCallback
PaymentStatus
</code></pre>
<p>它们属于不同的 bounded context，拥有独立的：</p>
<ul>
<li>数据库；</li>
<li>生命周期；</li>
<li>部署节奏；</li>
<li>内部状态；</li>
<li>领域约束。</li>
</ul>
<p>为了追求“Java 和 Rust 一致”而合并这些模型，反而可能重新制造强耦合。</p>
<p>在微服务边界上，真正需要共享的是有限且稳定的契约，例如：</p>
<pre><code>MerchantApproved
MerchantSuspended
PaymentRequested
PaymentAccepted
PaymentCompleted
PaymentFailed
</code></pre>
<p>因此，更合适的原则是：</p>
<blockquote>
<p><strong>不同微服务各自建模，只在 API 与事件边界共享必要语义。</strong></p>
</blockquote>
<p>也就是说：</p>
<pre><code>Different service models.
Clear interface contracts.
</code></pre>
<hr>
<h2>六、双技术栈真正的风险，是工程方式碎片化</h2>
<p>即使服务边界设计合理，引入 Rust 仍然会产生一个组织问题。</p>
<p>传统情况下，Java 团队和 Rust 团队很容易分别形成两套体系：</p>
<pre><code>Java Team
├── 一套查询方式
├── 一套审计方案
├── 一套错误模型
├── 一套测试规范
├── 一套项目结构
└── 一套 AI Coding 指南

Rust Team
├── 另一套查询方式
├── 另一套审计方案
├── 另一套错误模型
├── 另一套测试规范
├── 另一套项目结构
└── 另一套 AI Coding 指南
</code></pre>
<p>局部性能可能提高了，但整个组织的认知成本也提高了：</p>
<ul>
<li>Java 工程师很难阅读 Rust 服务；</li>
<li>Rust 工程师需要重新理解 Java 项目的工程约定；</li>
<li>架构规范在两个团队中逐渐分叉；</li>
<li>审计和可观测性语义不一致；</li>
<li>AI Agent 需要学习两套完全不同的开发方式；</li>
<li>一项跨服务需求更容易在交接处丢失语义。</li>
</ul>
<p>因此，多语言架构真正需要解决的，不只是“服务之间如何通信”，还包括：</p>
<blockquote>
<p><strong>如何在保留语言选择的同时，避免工程体验和组织认知被撕裂？</strong></p>
</blockquote>
<p>我认为这里应该追求的不是：</p>
<blockquote>
<p>One shared domain model.</p>
</blockquote>
<p>而是：</p>
<blockquote>
<p><strong>One shared way of building.</strong></p>
</blockquote>
<hr>
<h2>七、TeaQL 的尝试：不同模型，相同的工程语法</h2>
<p>TeaQL 同时提供 Java 和 Rust 实现。</p>
<p>我们的目标不是让 Java 服务和 Rust 服务共享一个巨大的模型，也不是让所有代码逐行一致，而是让两边拥有相似的工程表达方式。</p>
<p>例如，在 Java 中，一个带业务目的的查询可能是：</p>
<pre><code>Q.payments()
    .comment("Find payments requiring reconciliation")
    .withStatusIsCompleted()
    .withReconciliationStatusIsPending()
    .purpose("prepare reconciliation batch")
    .executeForList(ctx);
</code></pre>
<p>在 Rust 中，可以采用相似的表达：</p>
<pre><code>Q::payments()
    .comment("Find payments requiring reconciliation")
    .with_status_is_completed()
    .with_reconciliation_status_is_pending()
    .purpose("prepare reconciliation batch")
    .execute_for_list(ctx)
    .await?;
</code></pre>
<p>这两段代码：</p>
<ul>
<li>不一定属于同一个微服务；</li>
<li>不一定使用相同的领域模型；</li>
<li>不一定访问相同的数据库；</li>
<li>不需要共享同一个运行时。</li>
</ul>
<p>但工程师和 AI 看到的是相似的结构：</p>
<pre><code>选择业务对象
→ 表达业务条件
→ 说明操作目的
→ 在上下文中执行
→ 形成审计与追踪信息
</code></pre>
<p>TeaQL 希望统一的是：</p>
<ul>
<li>业务意图的表达；</li>
<li>查询和数据访问的基本形态；</li>
<li><code>purpose</code>、<code>comment</code>、<code>audit</code> 等工程语义；</li>
<li>状态与约束的类型化表达；</li>
<li>代码生成方式；</li>
<li>测试与质量门禁；</li>
<li>AI Coding 所需的指南和上下文；</li>
<li>服务开发的整体工作模式。</li>
</ul>
<p>这可以概括为：</p>
<blockquote>
<p><strong>不同的运行时，相同的工程体验。</strong></p>
</blockquote>
<p>或者：</p>
<blockquote>
<p><strong>TeaQL 不统一微服务内部模型，而是统一开发微服务的方式。</strong></p>
</blockquote>
<hr>
<h2>八、一个可运行的 Java + Rust 支付参考实现</h2>
<p>为了验证这种思路，我们实现了一个小型参考项目：</p>
<p><strong>polyglot-payment-with-teaql</strong></p>
<p>项目地址：</p>
<p><a href="https://github.com/teaql/polyglot-payment-with-teaql" rel="noopener noreferrer">https://github.com/teaql/polyglot-payment-with-teaql</a></p>
<p>它包含两个独立微服务。</p>
<h3>Java Merchant Service</h3>
<p>基于 Spring Cloud 与 TeaQL Java，负责：</p>
<ul>
<li>商户注册；</li>
<li>KYC 信息；</li>
<li>审批与暂停；</li>
<li>商户状态变更；</li>
<li>Transactional Outbox；</li>
<li>服务发现和状态同步。</li>
</ul>
<h3>Rust Payment Service</h3>
<p>基于 Axum 与 TeaQL Rust，负责：</p>
<ul>
<li>面向用户的支付创建；</li>
<li>支付回调；</li>
<li>状态查询；</li>
<li>商户状态本地缓存；</li>
<li>高并发请求处理；</li>
<li>独立扩容与部署。</li>
</ul>
<p>整体结构如下：</p>
<pre><code>+---------------------------------------------------+
|              Java Merchant Service                |
| Merchant Registry / KYC / Approval / Integration  |
+-------------------------+-------------------------+
                          |
              Merchant status contract
                          |
                          v
+-------------------------+-------------------------+
|               Rust Payment Service                |
| Checkout / Payment / Callback / High Concurrency  |
+-------------------------+-------------------------+
</code></pre>
<p>两个服务：</p>
<ul>
<li>各自拥有领域模型；</li>
<li>各自拥有数据库；</li>
<li>各自决定部署生命周期；</li>
<li>只在服务边界交换必要信息；</li>
<li>使用相似的 TeaQL 工程方式。</li>
</ul>
<p>Java 侧使用 <code>merchant_db</code>，Rust 侧使用 <code>payment_db</code>。Rust 支付服务会维护本地的商户可用状态，避免每一次支付请求都同步调用 Java 服务，从而减少用户请求关键路径上的跨服务依赖。</p>
<p>这个项目并不是一个完整的支付产品，也不是要证明所有支付服务都应该使用 Rust。</p>
<p>它想验证的是一个更有限、也更现实的命题：</p>
<blockquote>
<p><strong>Java 和 Rust 可以根据不同工作负载共同构建大型商业系统，而不必让工程组织陷入完全割裂的双技术栈。</strong></p>
</blockquote>
<hr>
<h2>九、Rust 进入 Java 企业系统的四步路径</h2>
<p>基于这个思路，我认为更稳妥的落地方式可以分成四步。</p>
<h3>第一步：寻找运行时价值明确的边界</h3>
<p>不要从“哪个 Java 服务看起来最旧”开始，而要从指标开始：</p>
<ul>
<li>哪条链路的 P99/P99.9 最重要？</li>
<li>哪个服务的流量波动最大？</li>
<li>哪个服务扩容最频繁？</li>
<li>哪个服务的内存成本最高？</li>
<li>哪个服务的边界最清晰？</li>
<li>哪个服务失败后最容易隔离和回滚？</li>
</ul>
<p>理想的第一个 Rust 服务应该：</p>
<ul>
<li>边界清晰；</li>
<li>业务变化相对可控；</li>
<li>能够独立部署；</li>
<li>可以逐步切流；</li>
<li>收益可以量化。</li>
</ul>
<h3>第二步：先统一契约，不急于统一实现</h3>
<p>在编码之前，先定义：</p>
<ul>
<li>API schema；</li>
<li>事件格式；</li>
<li>幂等语义；</li>
<li>错误码；</li>
<li>超时和重试规则；</li>
<li>版本兼容策略；</li>
<li>审计字段；</li>
<li>可观测性要求。</li>
</ul>
<p>Java 与 Rust 不需要共享内部对象，但必须对边界语义形成一致理解。</p>
<h3>第三步：建立统一的工程约定</h3>
<p>至少需要统一：</p>
<ul>
<li>Trace ID 和业务上下文；</li>
<li>日志字段；</li>
<li>审计语义；</li>
<li>错误分类；</li>
<li>指标命名；</li>
<li>测试分层；</li>
<li>安全检查；</li>
<li>API 兼容策略；</li>
<li>发布和回滚流程。</li>
</ul>
<p>只有这样，Rust 才是现有工程体系中的新成员，而不是一个孤立的技术岛。</p>
<h3>第四步：用生产指标决定是否扩大</h3>
<p>可以比较：</p>
<ul>
<li>P50、P95、P99、P99.9 延迟；</li>
<li>单实例吞吐；</li>
<li>CPU 与内存消耗；</li>
<li>容器启动时间；</li>
<li>扩容响应时间；</li>
<li>错误率；</li>
<li>事故恢复时间；</li>
<li>开发周期；</li>
<li>缺陷密度；</li>
<li>团队维护成本。</li>
</ul>
<p>只有当综合收益成立，才继续把 Rust 扩展到更多服务。</p>
<hr>
<h2>十、哪些情况下不应该使用 Rust？</h2>
<p>主张 Java + Rust 混合，并不意味着应该主动增加技术栈。</p>
<p>以下场景通常不适合作为 Rust 的第一入口：</p>
<h3>1. 业务规则每天变化</h3>
<p>如果一个服务的主要问题是产品需求频繁变化、第三方 SDK 很多、流程持续调整，那么迁移到 Rust 不一定能改善核心矛盾。</p>
<h3>2. 性能瓶颈主要在数据库</h3>
<p>如果请求的大部分时间都花在慢 SQL 或远程服务上，更换语言通常不会带来预期收益。</p>
<h3>3. 团队缺乏维护能力</h3>
<p>如果只有一名工程师能理解 Rust 服务，那么它可能成为新的组织风险。</p>
<h3>4. 服务边界尚未稳定</h3>
<p>在领域边界仍然剧烈变化时过早拆分，可能同时承担微服务复杂度和新语言复杂度。</p>
<h3>5. 只是为了“技术先进”</h3>
<p>缺少可衡量目标的 Rust 项目，很容易在内部变成昂贵的技术展示。</p>
<p>因此，我更倾向于一个克制的原则：</p>
<blockquote>
<p><strong>Java by default, Rust by justification.</strong></p>
</blockquote>
<p>Java 可以继续作为大量企业业务服务的默认选择；Rust 在存在明确运行时价值的地方进入。</p>
<hr>
<h2>十一、AI 编程让统一工程方式变得更重要</h2>
<p>在 AI Coding 时代，多语言开发的门槛似乎正在降低。</p>
<p>AI 可以帮助工程师：</p>
<ul>
<li>生成 Rust 代码；</li>
<li>理解所有权和生命周期错误；</li>
<li>编写 Java 与 Rust 之间的接口；</li>
<li>生成测试；</li>
<li>完成重复性迁移。</li>
</ul>
<p>但 AI 同样会放大工程碎片化。</p>
<p>如果每个服务都有不同的：</p>
<ul>
<li>API 风格；</li>
<li>数据访问方式；</li>
<li>审计方法；</li>
<li>错误约定；</li>
<li>目录结构；</li>
<li>验收标准；</li>
</ul>
<p>AI 就必须反复阅读大量上下文，并且更容易产生局部正确、整体不一致的代码。</p>
<p>因此，统一工程体验的价值在 AI 时代反而更大：</p>
<pre><code>Language choice remains flexible
            +
Engineering grammar remains stable
            =
Lower cognitive cost for humans and AI
</code></pre>
<p>多语言并不可怕。</p>
<p>真正危险的是：</p>
<blockquote>
<p><strong>每增加一种语言，就增加一套完全不同的软件工程体系。</strong></p>
</blockquote>
<hr>
<h2>结语</h2>
<p>Rust 要进入金融和大型商业软件，未必需要先证明自己能够取代 Java。</p>
<p>更现实的路径，是承认 Java 已经建立起来的企业生态，在其中找到真正适合 Rust 的服务边界：</p>
<ul>
<li>面向最终用户；</li>
<li>性能与资源效率敏感；</li>
<li>需要快速弹性扩容；</li>
<li>运行时可预测性很重要；</li>
<li>边界足够清晰；</li>
<li>收益能够被验证。</li>
</ul>
<p>Java 和 Rust 不需要共享一个代码库，也不需要共享一个巨大的领域模型。</p>
<p>它们需要的是：</p>
<ul>
<li>清晰的服务契约；</li>
<li>一致的业务语义；</li>
<li>相近的工程表达；</li>
<li>统一的审计与可观测性；</li>
<li>可重复的开发、测试和交付方式。</li>
</ul>
<p>最终，我们希望实现的不是：</p>
<pre><code>Java or Rust
</code></pre>
<p>而是：</p>
<pre><code>Java for business complexity.
Rust for runtime criticality.
One consistent engineering experience.
</code></pre>
<p><strong>Rust 不必推倒 Java 的世界。它可以先成为这个世界里不可替代的一部分。</strong></p>
<hr>
<h2>参考资料</h2>
<ol>
<li>
<p>TeaQL Java + Rust 支付微服务参考实现<br>
<a href="https://github.com/teaql/polyglot-payment-with-teaql" rel="noopener noreferrer">https://github.com/teaql/polyglot-payment-with-teaql</a></p>
</li>
<li>
<p>2025 Stack Overflow Developer Survey — Technology<br>
<a href="https://survey.stackoverflow.co/2025/technology" rel="noopener noreferrer">https://survey.stackoverflow.co/2025/technology</a></p>
</li>
<li>
<p>2025 State of Rust Survey Results<br>
<a href="https://blog.rust-lang.org/2026/03/02/2025-State-Of-Rust-Survey-results/" rel="noopener noreferrer">https://blog.rust-lang.org/2026/03/02/2025-State-Of-Rust-Survey-results/</a></p>
</li>
<li>
<p>GitHub Octoverse 2025<br>
<a href="https://github.blog/news-insights/octoverse/octoverse-a-new-developer-joins-github-every-second-as-ai-leads-typescript-to-1/" rel="noopener noreferrer">https://github.blog/news-insights/octoverse/octoverse-a-new-developer-joins-github-every-second-as-ai-leads-typescript-to-1/</a></p>
</li>
</ol>
]]></description><pubDate>2026-07-21 03:43:03</pubDate></item><item><title>【Rust日报】2026-07-21 SecretSpec 0.15 发布：Provider credentials、Azure Key Vault、Gopass 和 PHP SDK 一次补齐</title><link>https://rustcc.cn/article?id=a871ae9e-a013-411e-b3cc-1d25d2f2caec</link><description><![CDATA[<h2>SecretSpec 0.15 发布：Provider credentials、Azure Key Vault、Gopass 和 PHP SDK 一次补齐</h2>
<p><code>SecretSpec 0.15</code> 这次更新的分量不小，因为它不是只加一个 provider，而是把<strong>凭证引导、云密钥库、密码存储和多语言接入</strong>几条线一起往前推。最核心的新能力是 <strong>provider credentials</strong>：现在一个 secret provider 的认证信息可以安全地存放在另一个 provider 里，由 SecretSpec 在内存里取用，而不是先把凭证暴露到应用环境变量。这对多环境、多账户、要做最小暴露面的团队来说，是很实打实的一步。</p>
<p>围绕这套能力，0.15 同时把 <strong>Azure Key Vault</strong>、<strong>Gopass</strong> 和 <strong>PHP SDK</strong> 接进来了：前者补上 service principal、Azure CLI、managed identity、AKS workload identity 等认证路径；后者让 GPG 加密、git 同步的密码库进入统一解析链；PHP 侧则开始能把同一个 Rust resolver 复用到 PHP-FPM、Laravel、Symfony 和 CLI 应用里。再加上 <code>secretspec export</code>、AWS Secrets Manager 创建 guardrails、provider chain 和 profile override 的一批修补，这更像一次平台层面的扩展，而不是普通的小版本迭代。</p>
<p>如果你把 Rust 看成“拿来做一套跨环境 secrets control plane”的底层语言，SecretSpec 这次更新很能说明它的野心：不是只做一个 CLI，而是在持续把配置、认证、审计、多 SDK 和 provider 生态拼成一整套可运营的秘密管理系统。</p>
<p>原文链接：https://secretspec.dev/blog/secretspec-0-15-provider-credentials-azure-key-vault-gopass-and-php-sdk/</p>
<h2>ctt 0.5.0：把 GPU 压缩纹理制作、mipmap 和多编码器接口统一进一套 Rust 工具链</h2>
<p><code>ctt</code> 的价值，在于它不是又一个只服务某种纹理格式的小工具，而是试图把 <strong>GPU 压缩纹理制作</strong> 这件一直很碎的工程活，系统地收进一套 Rust 库 + CLI 里。作者把 <code>bc7enc-rdo</code>、Intel ISPC Texture Compressor、<code>etcpak</code>、AMD Compressonator、<code>astcenc</code> 这些现成编码器统一在同一个接口后面，再把 <strong>mipmap 生成、色彩空间、alpha 处理、容器输出（KTX2 / DDS）</strong> 一起接好，等于把“素材进来、压缩纹理出去”的流水线一次理顺。</p>
<p>从 README 和作者的 Reddit 说明看，这个项目不是纸上谈兵。它已经支持 BCn、ETC/EAC、ASTC 等多类格式，还能根据格式自动挑优先编码器，也允许显式指定某个后端。CLI 层面又把线程数、supercompression、cubemap、2D array、volume texture 这些生产里真的会遇到的参数都补齐了。对做游戏引擎、实时渲染或工具链的人来说，这种项目最吸引人的地方不是“会压缩”，而是 <strong>终于有人把跨平台压缩纹理流程做成可复用的标准件</strong>。</p>
<p>作者还提到它已经被 <strong>Bevy</strong> 和 Esoterica 引擎方向的工作使用，这点也挺关键：说明它不是只停在一个技术展示仓库，而是在往可落地的生态部件走。Rust 在图形工具链里一直缺少这种“脏活累活有人认真整理”的项目，<code>ctt</code> 这次算是补上了一个很实用的坑位。</p>
<p>原文链接：https://github.com/cwfitzgerald/ctt</p>
<h2>pg_otel：把 PostgreSQL 慢查询计划直接导成 OpenTelemetry traces 的 Rust 插件</h2>
<p><code>pg_otel</code> 这类项目的看点不在“它是 Rust 写的”，而在它试图把很多系统里原本断掉的一段可观测性补上：应用侧 trace 能看到 HTTP、RPC、队列，但一到数据库内部，通常只剩慢查询日志或零散指标。这个插件走的路线是：挂到 PostgreSQL executor 的 start / end hook 上，拿到查询计划和 instrumentation 数据，再把结果作为 <strong>OpenTelemetry spans</strong> 送出去，让 trace 不再停在数据库调用的边界上。</p>
<p>从仓库说明看，作者做得还挺务实：它默认会参考 <code>log_min_duration_statement</code>，只为超过阈值的查询生成 spans，避免把 trace 系统直接打爆；导出部分则交给后台 worker 做，尽量减轻查询线程本身的负担。再加上 <code>traceparent</code> 可以通过 GUC 或 SQL comment 传进来，应用侧和数据库侧的链路就有机会真正串起来。</p>
<p>这项目显然还早，像队列长度、span 名称和 query 文本截断都还有硬编码限制，但方向挺明确：Rust + <code>pgrx</code> 已经不只是“能写 Postgres 扩展”，而是在开始切入数据库观测和诊断这类更靠近生产一线的场景。如果后面继续补稳定性和配置面，这条线很可能会被不少做平台工程的人盯上。</p>
<p>原文链接：https://github.com/w1ll-i-code/pg_otel</p>
<h2><code>no_std</code> for Wgpu &amp; Winit：wasm32v1-none 跑通示例，WebGPU 的无标准库路线往前挪了一步</h2>
<p>这条分享最有意思的地方，不是“有人搞 <code>no_std</code>”本身，而是作者把目标对准了 <strong>Wgpu + Winit</strong> 这样更偏应用层的基础设施，并且已经给出一个实际可跑的 <code>wasm32v1-none</code> 示例仓库。按照作者在 Reddit 的说明，这套 demo 基本直接沿用官方 <code>wgpu_examples</code>，改动并不激进，主要是把环境切到了 <code>Winit 0.31</code> 加上一组补丁，来证明 <strong>no_std WebGPU 应用</strong> 不是停留在概念层面。</p>
<p>这背后真正值得关注的是生态信号。<code>no_std</code> 过去更多停留在嵌入式、底层 crate 或小范围实验里，但如果 Wgpu / Winit 这类图形和窗口基础设施也能逐步打开这条路，那么像 <code>wasm32v1-none</code> 这种更受限、更可控的目标环境就会变得更现实。作者还提到自己和其他人已经在这件事上折腾了很久，现在终于看到“隧道尽头的光”，这也说明相关工作并不是一次性的玩票 demo。</p>
<p>对关心 WebAssembly、图形基础设施或者 Rust <code>no_std</code> 边界的人来说，这条线暂时还谈不上成熟，但它已经足够证明：Wgpu / Winit 这类重量级生态组件，开始有人认真往 <code>no_std</code> 能跑的方向推了。</p>
<p>原文链接：https://github.com/bushrat011899/wasm32v1-none-wgpu-example</p>
]]></description><pubDate>2026-07-21 01:06:54</pubDate></item><item><title>【Rust日报】2026-07-20 Keel 0.3 发布：Rust 写的静态类型解释语言补上标准库、Map 和文档站</title><link>https://rustcc.cn/article?id=02e755f6-48e6-416c-9b1c-6e18c0f22b72</link><description><![CDATA[<h2>Keel 0.3 发布：Rust 写的静态类型解释语言补上标准库、Map 和文档站</h2>
<p>Keel 0.3 这次更新挺像一个“从实验玩具往可用语言继续推进”的节点。作者把它定位成一门<strong>用 Rust 编写、语法接近 Rust、使用体验又想靠近 Python</strong>的静态类型解释语言，而且继续强调它在解释执行场景下的速度目标：大约能做到 <strong>Python 的 2 到 10 倍</strong>，同时维持零注解式的类型推断体验。对 Rust 社区来说，这类项目的看点不只是“又造了一门语言”，而是作者正在认真把 Rust 的编译器工程思路往运行时语言工具链上迁移。</p>
<p>0.3 最值得记的不是一句抽象口号，而是一批很实的工程补丁：<strong>标准库正式进包</strong>，目前先覆盖 <code>std/math</code>、<code>std/time</code>、<code>std/random</code>；新增 <strong>Map 类型</strong> 和对应语法；错误信息做了明显强化；编译器和 VM 也补了不少优化，像统一编译路径、统一字面量编译与寄存器分配、移除冗余边界检查之类，都说明作者已经在认真收敛内部结构，不只是往外堆功能。</p>
<p>另一个信号是项目的外围配套也在变完整：二进制和动态库产物一起发，文档网站和在线 playground 都补上了，说明作者已经在把 Keel 往“可以被试、可以被嵌入、也可以被讨论”的工具链方向推。虽然它离成熟生态显然还早，但如果你关心 <strong>Rust 写语言实现</strong>、<strong>轻量脚本语言嵌入</strong> 或 <strong>解释器性能路线</strong>，Keel 0.3 是这两天值得留意的一条线。</p>
<p>原文链接：https://github.com/horacehoff/keel</p>
<h2>ExImg：egui/eframe 做出可点击穿透的参考图覆盖层工具</h2>
<p>ExImg 这个项目很讨巧，因为它解决的是一个非常具体、但很多画图和设计场景都真的会遇到的小痛点：参考图总得放在旁边看，可一旦不停切窗口，流程就很碎。作者用 <strong>egui/eframe</strong> 做了一个小型图片查看器，图片可以直接拖进去或从剪贴板贴进去，窗口会自动适配尺寸、始终置顶，还能切到<strong>无边框 + 全透明背景</strong>模式，让画布上方只剩图片本身。</p>
<p>真正有意思的是这个“像真覆盖层一样工作”的细节处理。作者提到，为了让用户还能继续操作底下的绘图软件，项目用了 <code>ViewportCommand::MousePassthrough</code> 做<strong>点击穿透</strong>；但这样一来窗口失焦后，普通快捷键就很难把模式切回来，于是又借助 <code>global-hotkey</code> 注册系统级热键去收回控制权。这种问题不大、但一旦真做桌面工具就一定会撞上的边角料，恰恰最能说明一个 Rust GUI 项目是不是停留在 demo。</p>
<p>再往下看，ExImg 还把安装和发布链路顺手理了一遍：从手写 Inno Setup 转到 <code>cargo-packager</code>，再接 GitHub Actions 自动出 Release。它现在还是 Windows-only，但作为一个围绕 <code>egui</code>、窗口层级、透明窗口和系统热键组织起来的小工具，已经很像一份“可抄作业”的桌面应用样板了。</p>
<p>原文链接：https://github.com/NetoTTT/eximg</p>
<h2>cair-rs：内容感知图像缩放工具把 seam carving 带回 Rust</h2>
<p><code>cair-rs</code> 走的是一条老算法重新做现代实现的路子：它把 <strong>seam carving / content-aware image resizing</strong> 这类“按内容重要性删像素列”的图像处理方法，做成了一个 Rust 项目，而且还附了在线 demo。对很多开发者来说，图像缩放早就不是新话题，但真正把“低能量接缝删除”这种内容感知路径重新做成可玩的工具，依然挺能勾起兴趣。</p>
<p>从仓库说明看，作者没有把它包装成特别大的平台，而是很务实地先把核心能力、示例和参考论文链接整理出来：包括 2007 年的 seam carving 论文、后续实时化研究，以及网页演示入口。这种项目的价值不在于它立刻能替代成熟图像软件，而在于它很适合作为 Rust 图像处理和算法实现的一个干净样板。</p>
<p>如果后面再补 benchmark、并行特性或者目标保留掩码之类能力，<code>cair-rs</code> 会更像一个完整的算法工具链；但就今天这个状态，它已经足够说明：Rust 社区里对“经典视觉算法重新工程化”这条路，还是有人在认真往前推。</p>
<p>原文链接：https://github.com/sermuns/cair-rs</p>
<h2>Rooc：把 MILP 建模语言、Rust API 和浏览器求解工具打成一套</h2>
<p>Rooc 的有意思之处，在于它不是只给出一个 Rust crate，然后把“更完整的用户体验”留给别人补。作者这次做的是一整套围绕<strong>线性 / 混合整数优化建模</strong>的组合：既有 Rust 侧的 fluent API，也有一门独立的 ROOC 建模语言，还有能在浏览器里直接跑的 Web 平台和 TypeScript 包。换句话说，它想做的不是单点库，而是一整条从模型描述到求解执行的通道。</p>
<p>从 README 能看出来，Rust 侧 API 走的是比较顺手的 builder 路线，布尔变量、整数变量、约束、目标函数这些核心拼装都做得很直接；而 ROOC 语言层则开始支持图结构、集合、索引约束、<code>abs</code> / <code>min</code> / <code>max</code> 这类块式表达，显然不是只想停在教学级 LP 示例。作者还把 WebAssembly 求解链路一起接进了 TypeScript 包，这意味着前端或浏览器侧的优化建模试验也能沿着同一套语义走下去。</p>
<p>对 Rust 圈来说，这类项目的价值不只在“又一个求解器封装”，而在它把<strong>语言层、库层、Web 交互层</strong>放到一个仓库里统一推进。要是后面文档、示例和求解后端继续补强，Rooc 很可能会变成 Rust 在运筹优化 / OR 建模方向里一个挺有辨识度的入口项目。</p>
<p>原文链接：https://github.com/Specy/rooc</p>
]]></description><pubDate>2026-07-20 01:08:09</pubDate></item><item><title>【Rust日报】2026-07-19 Tokio 团队开源 Topcoat：Rust 全栈 Web 框架把 SSR、响应式交互和无 Wasm 客户端一起打包</title><link>https://rustcc.cn/article?id=f2f7becb-c42f-4f34-9edb-7f2d29c131cd</link><description><![CDATA[<h2>Tokio 团队开源 Topcoat：Rust 全栈 Web 框架把 SSR、响应式交互和无 Wasm 客户端一起打包</h2>
<p><code>tokio-rs/topcoat</code> 这次很容易吸引 Rust Web 圈的注意，不只是因为它挂在 <strong>tokio-rs</strong> 名下，更因为它试图把“全栈 Rust Web 框架”这件事重新捏成一套更轻的工程模型。项目主张的是：<strong>HTML 仍然由服务端主导渲染</strong>，组件可以直接写成 <code>async fn</code>，数据库查询和页面拼装不再被 API 层强行隔开；与此同时，浏览器里的局部交互又不必次次回服务器，而是通过 <code>$(...)</code> 这类表达式在初次渲染时跑服务端逻辑、在前端交互时再翻译成 JavaScript 立即重跑。也就是说，它想做的不是“再来一个模板引擎”，而是把 SSR、响应式交互和 Rust 本体语言尽量压到同一套语义里。</p>
<p>从 README 看，Topcoat 最有辨识度的地方有三块。第一是 <strong>不靠 Wasm 客户端包</strong> 来换取基本交互能力，按钮点击、显隐切换之类的状态更新可以直接走浏览器侧的表达式执行；第二是当交互确实需要重新命中服务端时，可以把组件标成 <code>#[shard]</code>，让参数变化触发服务端重渲染并只替换那一块 HTML；第三是它连路由、资源打包和常见 UI 组件都一起往“开箱可用”方向做，连模块结构自动推导路由树、<code>asset!</code> 静态资源扫描和 <code>topcoat fmt</code> 宏格式化都一并给了。</p>
<p>当然，项目自己也写得很坦白：<strong>这是 early-stage、experimental 的框架</strong>，预期会有 breaking changes。但正因为如此，这个仓库更值得关注——Tokio 生态现在不只是继续做 runtime 和基础设施，而是在尝试定义一套新的 Rust Web 开发体验。如果后续这套“服务端 Rust 为中心、前端交互按需下沉”的模型跑顺了，Topcoat 很可能会成为今年 Rust Web 方向里最值得追的一条新线。</p>
<p>原文链接：https://github.com/tokio-rs/topcoat</p>
<h2>prost-protovalidate 0.6：把 buf.validate 规则直接落到 Rust，运行时 CEL 与编译期直生校验并存</h2>
<p><code>prost-protovalidate</code> 0.6 这次更新很扎实，因为它瞄准的是很多 Rust 服务端团队都会碰到的一个老问题：<strong>校验规则到底写在哪一层，才能不在 schema、服务代码和网关之间来回重复？</strong> 这个项目的答案是，尽量把规则继续留在 <code>.proto</code> 里，用 <code>buf.validate</code> 作为单一事实来源，然后在 Rust 侧把执行路径补全。作者给出的能力模型相当清楚：一条路是基于 <code>prost-reflect</code> 的 <strong>运行时验证器</strong>，可以完整处理包括 <strong>CEL</strong> 在内的复杂规则；另一条路则是 <code>prost-protovalidate-build</code> 在编译期直接生成 <code>impl Validate</code>，让常规规则走静态代码路径，在热路径上不再依赖反射和解释器。</p>
<p>0.6 最显眼的硬指标，是它已经通过了上游 <strong>protovalidate conformance 2872/2872</strong> 全量测试。这不是 README 式的“理论支持”，而是已经把兼容面拿测试套件压过一轮。与此同时，项目还把生态接入做得比较实用：可选 <code>tonic</code> / <code>tonic-types</code> 特性可以把验证错误映射成 gRPC 的 <code>InvalidArgument</code>，并附带 <code>google.rpc.BadRequest</code> 级别的字段错误细节；对 <code>buffa</code> 后端也留了统一桥接路径，说明作者考虑的不是单一 demo，而是真想把这套规则执行层嵌进不同 protobuf 生成方案里。</p>
<p>更值得注意的是它对“性能”和“覆盖率”没有二选一。作者没有强迫所有人都走运行时引擎，而是把 <strong>编译期直生校验</strong>、<strong>运行时全规则校验</strong> 和 <strong>buffa runtime_bridge</strong> 这些模式拆开，让团队按二进制体积、构建时间和热路径性能自己取舍。对于已经在 Rust 里大量使用 <code>prost</code>、又不想把字段校验散落到 handler 里的服务项目来说，<code>prost-protovalidate</code> 0.6 很可能是近期最值得试的一类基础设施升级。</p>
<p>原文链接：https://github.com/zs-dima/prost-protovalidate</p>
<h2>Singularity：Rust + WGPU 做出可点击穿透的黑洞桌面覆盖层，逐像素广义相对论直接跑进 shader</h2>
<p>这个叫 <strong>Singularity</strong> 的项目虽然看起来更像“桌面玩具”，但它背后的工程味其实相当重。作者做的不是一段预渲染动画，也不是只在某个窗口里演示特效，而是一个 <strong>全屏、置顶、可点击穿透</strong> 的桌面覆盖层：运行后黑洞会在桌面上缓慢漂移，你底下的窗口仍然可以正常操作，而覆盖层实时抓取屏幕内容，再把整张桌面当成被引力透镜扭曲的背景去渲染。这个产品形态本身就不太像普通图形 demo，它更接近一个真正可运行的桌面效果程序。</p>
<p>更有意思的是，作者没有只用“近似长得像”的 shader 糊弄过去。README 和帖子都明确写到，黑洞附近的每个像素都会在 <strong>fragment shader</strong> 里积分自己的 <strong>Schwarzschild null geodesic</strong>，用 leapfrog 方案迭代几十步；远离黑洞的区域则切到解析化的弱场近似，再做无缝衔接。再加上吸积盘的黑体辐射、相对论多普勒效应、引力时间膨胀、爱因斯坦环和镜像次级成像，这个项目虽然是视觉作品，但它选的是一条很“Rust 图形工程”的硬路线：把物理、GPU 管线和桌面捕获真正拧在一起。</p>
<p>实现层面也有不少值得 Rust 开发者留意的细节。Windows 侧桌面捕获走的是 <strong>GPU 零拷贝路径</strong>，帧直接复制进共享纹理避免 CPU 参与；macOS 端则已经补上基于 <code>objc2</code> 的 ScreenCaptureKit 绑定，虽然作者还没有真机验证，但说明跨平台接口层也在认真搭。项目还支持多种视觉 preset、托盘菜单切换、空闲触发 screensaver 模式，以及交叉编译到 Windows 的路径。对做 <code>wgpu</code>、桌面图形甚至系统级 UI 特效的人来说，Singularity 并不只是“看起来很酷”，它本身就是一个相当完整的 Rust 图形样板。</p>
<p>原文链接：https://github.com/GreenScreen410/singularity</p>
<h2>diffable：微分几何 trait 体系搬进 Rust，从流形、度量到李群层层自动派生</h2>
<p><code>diffable</code> 这个新库走的是一条很不大众、但相当有辨识度的路线：它不是给你几个数值算法函数就完事，而是试图把 <strong>微分几何里的结构层级直接编码成 Rust trait 系统</strong>。项目文档里把 <code>Point</code>、<code>Field</code>、<code>Metric</code>、<code>Chart</code>、<code>TangentBundle</code>、<code>LieGroup</code>、<code>PseudoRiemannian</code> 等一整套概念都拆成独立 trait，然后通过 blanket impl 把“实现了更高阶结构，就自动继承周边几何能力”这件事做成类型层级规则。换句话说，它追求的不是“在 Rust 里算几何”，而是“让 Rust 类型本身携带可验证的几何结构”。</p>
<p>这种设计的吸引力，在于它把很多平时只会出现在论文或数学笔记里的关系，转成了程序接口。例如一个类型如果实现 <code>Smooth&lt;V&gt;</code>，就能顺着链条拿到 <code>Chart</code>、<code>ExpMap</code>、<code>TangentBundle</code>；如果再具备 <code>LieGroup</code> 结构，又可以把群操作和流形结构自然叠起来。文档里还给了 <code>Sphere</code>、<code>S3</code>、<code>So3</code>、<code>Minkowski</code>、矩阵、复数和离散结构等实现，说明作者不是只想搭一层抽象壳，而是已经开始把真正可算的对象接进这套系统。</p>
<p>另一个很 Rust 的点，是项目把“这些数学公理是否真的成立”尽量往 <strong>property testing</strong> 上落。文档专门提供了大量 <code>test_*</code> 宏，去验证群、公理、度量、切丛、指数映射等结构承诺，而不是只把 trait 名字写得很漂亮。对大多数应用开发者来说，这个库未必会马上进生产，但对做科学计算、图形、机器人、物理建模，或者单纯关心“Rust 类型系统还能往哪里卷”的人来说，<code>diffable</code> 是个相当值得收藏的方向性项目。</p>
<p>原文链接：https://docs.rs/diffable/0.1.1/diffable/index.html</p>
]]></description><pubDate>2026-07-19 01:06:33</pubDate></item><item><title>【Rust日报】2026-07-18 8× faster binary search：从编译代码一路挖到机械同理心</title><link>https://rustcc.cn/article?id=4d4a772b-2689-4d9c-a780-ac7d78629141</link><description><![CDATA[<h2>8× faster binary search：从编译代码一路挖到机械同理心</h2>
<p>这篇性能文章很适合给写 Rust 的人当一堂“CPU 现实主义”复习课。作者面对的不是玩具问题，而是 scikit-learn 直方图梯度提升里一个很实际的步骤：把大批浮点数分桶到 0–254 的整数范围。原始做法已经不差——算法是标准二分查找，代码跑在编译型语言里，还能并行——但作者继续往下挖，最后把实现从“够快”推进到 <strong>约 8× 加速</strong>，核心手段不是换算法，而是让代码别再和 CPU 的分支预测打架。</p>
<p>文中最有价值的地方，在于它把“为什么慢”拆得很具体。传统二分查找里，<code>if boundaries[middle] &lt; value</code> 这种判断对 CPU 来说非常难猜，因为桶边界本来就是为了把输入尽量均匀摊开；结果就是大量 <strong>branch misprediction</strong>。作者给出的改法，是把可变次数的 <code>while</code> 循环改成固定轮数迭代，再用 Rust 的 <code>std::hint::select_unpredictable()</code> 把条件分支改写成更接近 branchless 的条件选择，让 CPU 能把更多指令并行吃下去。文章里给出的硬件计数结果也很直观：分支失预测几乎被打掉，IPC 明显升高，单核执行效率跟着上来。</p>
<p>更妙的是，这篇文章没有停在“branchless 就赢了”这种结论上，而是继续往 bounds check、编译优化和剩余无效工作上抠细节。对 Rust 开发者来说，它的启发不只是二分查找本身，而是一个很具体的提醒：当你已经有了还不错的算法以后，真正限制性能的，往往是 CPU 如何看待你的控制流，而不是源码表面上“做了几次比较”这么简单。</p>
<p>原文链接：https://pythonspeed.com/articles/branchless-binary-search/</p>
<h2>x-nes v0.1 发布：cycle-accurate NES 模拟器核心用 Rust 跑到 no_std 和 FFI</h2>
<p><code>x-nes</code> 这个新项目很对底层和模拟器爱好者的胃口：它不是一个成品模拟器，而是一个 <strong>可嵌入的 NES 模拟器核心库</strong>，重点放在 Rust API、C FFI 和跨环境可集成性上。作者给出的目标相当明确：同一套核心既能跑在桌面，也能压到微控制器等更受限的环境里，因此项目一开始就把 <strong><code>no_std</code>、FFI、静态/动态库产物</strong> 这些路线一起考虑进来了。</p>
<p>从目前披露的能力看，v0.1 的基础已经不算浅。它实现了 <strong>cycle-accurate 的 6502 CPU</strong>，覆盖 56 条指令和 13 种寻址模式；PPU 方面做到 <strong>scanline-accurate</strong> 并支持 NMI；APU 也已经有 pulse channel 和 sample buffer。卡带这边则支持 iNES 解析和 <strong>6 种 mapper</strong>。对很多“想做前端壳、调试器、教育项目或嵌入式移植”的开发者来说，这种库形态比单体模拟器更有二次开发价值。</p>
<p>作者也没有把它包装成“已经完美”的项目。当前准确率写得很坦白：<strong>AccuracyCoin 141 项里通过 86 项（61%）</strong>，Blargg 测试则是 <strong>20/21 通过</strong>。已知问题集中在音频时序、PPU 边界行为、MMC3 支持缺失，以及 DMA / 中断 / 假读等更细碎的硬件细节。正因为这些短板讲得很清楚，反而让这个项目看起来更像一个值得持续跟踪的工程起点，而不是只靠截图和 GIF 吸引眼球的展示帖。</p>
<p>原文链接：https://old.reddit.com/r/rust/comments/1uyune4/xnes_v01_cycleaccurate_nes_emulator_library_in/</p>
<h2>Roc 编译器 Rust→Zig 重写阶段性过线：功能对齐已完成，热重载与跨编译路径一并落地</h2>
<p>Roc 团队这篇长文之所以在 Rust 社区引发讨论，不只是因为它在讲“从 Rust 重写到 Zig”，而是因为它给出了一个已经跑到 <strong>feature parity</strong> 的阶段性结果。作者回顾说，团队把大约 <strong>30 万行 Rust 编译器代码</strong> 重写到 Zig，整个过程持续了 <strong>487 天</strong>，如今新的编译器已经能把示例项目 Rocci Bird 跑起来，而且 <code>roc build --opt=size</code> 生成的 wasm 产物压到了 <strong>31KB</strong>，比旧编译器时代小了不止一点。</p>
<p>文章里真正值得 Rust 读者细看的，其实是“为什么他们宁愿重写”。作者并没有把原因简化成语言口味，而是明确提到原编译器里一些架构性问题，尤其是围绕 lambda set specialization / defunctionalization 的实现复杂度，已经到了继续局部修补不划算的程度。也就是说，这不是“Rust 不行”的情绪化叙述，而是一个编译器团队在大规模演进中，对历史结构债、目标能力和重写成本做出的工程判断。</p>
<p>与此同时，新编译器也不是只换了语言表皮。Roc 现在把 <strong>hot code loading</strong>、跨平台交叉编译，以及更激进的零分配字符串模式匹配能力一起往前推。文章里展示的方向很清楚：他们想把高性能编译语言常见的部署能力，和解释型语言常见的开发体验揉进同一条工具链里。对 Rust 社区来说，这篇文章最有讨论价值的点，不是“要不要学 Zig”，而是大型编译器项目在遇到架构拐点时，到底该怎么判断“渐进式重构”和“重写一次”各自的边界。</p>
<p>原文链接：https://rtfeldman.com/rust-to-zig</p>
<h2>Async scoped task 的“三难困境”新解：用 MustCompleteFuture 把“不能忘掉 future”显式写进类型</h2>
<p>这篇 self-post 提的问题相当 Rust：如果你想做类似 <code>std::thread::scope</code> 的 <strong>async scope task</strong>，最大的麻烦之一在于——一个 future 即使已经被 poll 过，只要还没完成，理论上依然可能被 <code>mem::forget</code>，而这会直接冲击那些借用外部数据的 scoped task 设计。作者把这件事概括成一个 scoped task 的“三难困境”，并尝试提出一种更类型系统导向的解决方案。</p>
<p>核心想法是引入一个 <code>MustCompleteFuture</code> trait，把“这个 future 最终必须被 drop 或 poll 到完成”这件事，从口头约束提升成类型层面的显式信息。这样一来，像 <code>scope(...)</code> 这类能力返回的就不再只是普通 future，而是带着“必须完成”语义的对象；调用者再通过 <code>unsafe into_future()</code> 或宏展开后的受控写法，把这一约束沿调用链往上传递。作者也很坦白地指出，这种设计依然会像 async 本身一样 <strong>viral</strong>，也就是要求整条调用栈配合标注，但它至少把原本分散在文档和人为约定里的安全前提，移动到了更可讨论、更可静态表达的层面。</p>
<p>这类帖子未必会直接变成标准库 API，但它很适合 Rust 社区持续讨论，因为它正好踩在语言设计、异步运行时和安全边界三者交界处。对写库的人来说，文章最大的价值不一定是照搬 <code>MustCompleteFuture</code>，而是它把“future 可以被忘掉”这个经常被忽视的事实，再次拖回了 scoped async 设计的正中央。</p>
<p>原文链接：https://old.reddit.com/r/rust/comments/1uzfv30/a_solution_to_the_scoped_task_trilemma/</p>
]]></description><pubDate>2026-07-18 01:08:20</pubDate></item><item><title>GitBundle v3.5</title><link>https://rustcc.cn/article?id=4f9a98e9-2ea8-4029-b455-1da1e040702a</link><description><![CDATA[<p>大家好, 我是一名独立开发者, 同时也是 <a href="https://github.com/gitbundle/gitbundle" rel="noopener noreferrer">GitBundle</a> 的项目作者, 在这个项目上持续投入了巨量的时间和精力, 经过持续的迭代和打磨，GitBundle 终于迎来了 v3.5 版本。这次更新在安全性、CI 交互和用户体验上都做了重点提升，希望给大家带来更好的自托管 Git 体验。</p>
<p>🔐 安全性大幅增强</p>
<ul>
<li>移除 SHA-1，新增 SSH layer，支持后量子密钥交换算法 mlkem768x25519，彻底修复 SSH 安全警告</li>
<li>修复了 git clone 无法安全断开 TCP 连接的问题</li>
</ul>
<p>⚙️ 后台管理优化</p>
<ul>
<li>支持用户软删除，数据管理更灵活</li>
<li>支持用户安全更新邮箱</li>
</ul>
<p>🚀 CI 与体验提升</p>
<ul>
<li>支持 cursor-based CI 日志拉取，交互更顺畅</li>
<li>UI 全面打磨，修复了多项历史遗留问题，视觉和操作更一致流畅</li>
</ul>
<p>为什么要做这个项目:
第一点: 肯定是因为兴趣爱好, 因为我喜欢写代码, 喜欢做这个事情
第二点: 我见识过类似的各种平台, 但都是差强人意, 体验很糟糕
第三点: 的的确确我找不到工作, 失业了, 职场远远不是你想写代码那么简单, 这是一个很痛的现实, 但我必须要接受, 因为我还想继续写代码直到写不动的那一天, 可现实不允许我这样</p>
<p>欢迎大家下载试用，也期待大家的反馈和建议！ 🙏</p>
<p>关于大家关心的源代码开源问题, 目前有计划在将来进行开源, 但具体开源时间还不确定.</p>
<p>详细发布日志:</p>
<p>https://github.com/gitbundle/gitbundle/releases/tag/server-v3.5.0</p>
]]></description><pubDate>2026-06-11 11:48:16</pubDate></item><item><title>A high-performance async Rust implementation of KCP - A Fast and Reliable ARQ Protocol built on top of Tokio.</title><link>https://rustcc.cn/article?id=29969d7b-6ba8-4f9e-908c-4004a893fee0</link><description><![CDATA[<p>https://github.com/leihuxi/rust-kcp
A high-performance async Rust implementation of KCP - A Fast and Reliable ARQ Protocol built on top of Tokio.</p>
<p>Features
Async-First Design: Built from ground up for async/await with Tokio integration
Zero-Copy: Efficient buffer management using the bytes crate
Lock-Free Buffer Pool: High-performance memory management with crossbeam
Connection-Oriented: High-level connection abstractions (KcpStream, KcpListener)
Protocol Compatible: Compatible with original C KCP implementation
Observability: Integrated tracing and metrics support
Memory Efficient: Object pooling and buffer reuse
Multiple Performance Modes: Normal, Fast, Turbo, Gaming presets
Installation
Add this to your Cargo.toml:</p>
<p>[dependencies]
kcp-tokio = "0.4"
tokio = { version = "1.0", features = ["full"] }
Quick Start
Client
use kcp_tokio::{KcpConfig, KcpStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};</p>
<p>#[tokio::main]
async fn main() -&gt; Result&lt;(), Box&gt; {
let config = KcpConfig::new().fast_mode();
let mut stream = KcpStream::connect("127.0.0.1:12345".parse()?, config).await?;</p>
<pre><code>// Send data
stream.write_all(b"Hello, KCP!").await?;

// Receive response
let mut buffer = [0u8; 1024];
let n = stream.read(&amp;mut buffer).await?;
println!("Received: {}", String::from_utf8_lossy(&amp;buffer[..n]));

Ok(())
</code></pre>
<p>}
Server
use kcp_tokio::{KcpConfig, KcpListener};
use tokio::io::{AsyncReadExt, AsyncWriteExt};</p>
<p>#[tokio::main]
async fn main() -&gt; Result&lt;(), Box&gt; {
let config = KcpConfig::realtime();
let mut listener = KcpListener::bind("127.0.0.1:12345".parse()?, config).await?;</p>
<pre><code>println!("Server listening on 127.0.0.1:12345");

while let Ok((mut stream, addr)) = listener.accept().await {
    println!("New connection from {}", addr);
    tokio::spawn(async move {
        let mut buf = [0u8; 1024];
        while let Ok(n) = stream.read(&amp;mut buf).await {
            if n == 0 { break; }
            let _ = stream.write_all(&amp;buf[..n]).await;
        }
    });
}

Ok(())
</code></pre>
<p>}
Architecture
┌─────────────────────────────────────────────────────────────┐
│                    Application Layer                         │
│              (User code using KcpStream/KcpListener)         │
├─────────────────────────────────────────────────────────────┤
│                    High-Level API Layer                      │
│                  KcpStream    KcpListener                    │
│           (AsyncRead/AsyncWrite, TCP-like interface)         │
├─────────────────────────────────────────────────────────────┤
│                    Protocol Core Layer                       │
│                       KcpEngine                              │
│        (ARQ logic, congestion control, retransmission)       │
├─────────────────────────────────────────────────────────────┤
│                    Common Layer                              │
│         KcpSegment, KcpHeader, BufferPool, Constants         │
├─────────────────────────────────────────────────────────────┤
│                    Transport Layer                           │
│          Generic Transport trait (UDP default)               │
└─────────────────────────────────────────────────────────────┘
Configuration
Performance Presets
// Gaming - ultra-low latency (3ms update interval)
let config = KcpConfig::gaming();</p>
<p>// Real-time communication (8ms update interval)
let config = KcpConfig::realtime();</p>
<p>// File transfer - high throughput
let config = KcpConfig::file_transfer();</p>
<p>// Testing with packet loss simulation
let config = KcpConfig::testing(0.1); // 10% packet loss
Performance Modes
Mode	Update Interval	Resend	Congestion Control	Use Case
Normal	40ms	0	Yes	General purpose
Fast	8ms	2	Yes	Low latency
Turbo	4ms	1	No	Maximum speed
Gaming	3ms	1	No	Real-time games
Custom Configuration
use std::time::Duration;</p>
<p>let config = KcpConfig::new()
.fast_mode()
.window_size(128, 128)
.mtu(1400)
.connect_timeout(Duration::from_secs(10))
.keep_alive(Some(Duration::from_secs(30)))
.stream_mode(true);
Examples</p>
<h1>Run performance test server</h1>
<p>cargo run --example perf_test_server -- 127.0.0.1:12345 gaming</p>
<h1>Run performance test client</h1>
<p>cargo run --example perf_test_client -- 127.0.0.1:12345</p>
<h1>Run simple echo example</h1>
<p>cargo run --example simple_echo
Testing</p>
<h1>Run all tests</h1>
<p>cargo test</p>
<h1>Run resilience tests (packet loss, reorder, concurrent connections)</h1>
<p>cargo test --test resilience_test</p>
<h1>Run benchmarks</h1>
<p>cargo bench</p>
<h1>Run with logging</h1>
<p>RUST_LOG=debug cargo test -- --nocapture</p>
<h1>Run clippy</h1>
<p>cargo clippy --all-targets -- --deny clippy::all
Documentation
Detailed documentation is available in the doc/ directory:</p>
<p>Document	Description
ARCHITECTURE.md	System architecture and design
MODULES.md	Module reference and APIs
USAGE.md	Usage guide and examples
TESTING.md	Testing guide
Performance
KCP provides significant latency improvements over TCP:</p>
<p>30-40% lower latency in typical network conditions
Better performance on lossy networks
Configurable trade-offs between latency and bandwidth
Optimizations in this Implementation
Actor-based lock-free architecture: KcpEngine runs in a single dedicated tokio task, eliminating Arc&lt;Mutex&lt;&gt;&gt; contention
Generic Transport trait: Associated Addr type with RPITIT — zero heap allocation on hot path (no Pin&lt;Box&gt;)
DashMap for packet routing: Listener uses lock-free concurrent hashmap on the hot path
Lock-free buffer pools: crossbeam::queue::ArrayQueue for zero-allocation fast path
BTreeMap receive buffer: O(log n) insertion for out-of-order packets (vs O(n) linear scan)
Zero-copy segment encoding: Flush avoids cloning segments, encodes by reference
Cached timestamps: Single syscall per input() call instead of 3+
Pre-allocated buffers: VecDeque::with_capacity based on window sizes, avoiding grow overhead on send burst
Zero-copy packet handling with bytes crate
Grouped state structs for better cache locality
Configurable update intervals (3-40ms)
Batch ACK processing
Use Cases
Gaming: Ultra-low latency for real-time multiplayer
VoIP/Video: Real-time communication
Live Streaming: Low-latency data delivery
File Transfer: Reliable bulk data transfer
IoT: Efficient communication for constrained devices
Compatibility
Protocol: Compatible with original C KCP
Rust: Edition 2021, stable toolchain
Tokio: 1.0+
License
MIT License - see LICENSE file.</p>
<p>Contributing
Contributions are welcome! Please feel free to submit a Pull Request.</p>
<p>Resources
Original KCP Protocol
KCP Protocol Documentation
Tokio Documentation
Benchmarks
Criterion benchmarks measure engine-level throughput and latency:</p>
<p>cargo bench
Benchmark	Description
engine_throughput	10/100/500 x 1KB messages
engine_small_messages	1000 x 64B messages
engine_large_message	Single 16KB/64KB message fragmentation + reassembly
Version History
v0.4.0: Extract kcp-core as standalone protocol crate, restructure source layout (src/ → kcp/, flatten async_kcp/)
v0.3.7: Fix ACK window/UNA fields, generic Transport trait with RPITIT, resilience tests, criterion benchmarks
v0.3.4: Engine refactoring, lock-free buffer pools, documentation
v0.3.3: Performance optimizations, sub-millisecond latency
v0.3.1: Full async support, comprehensive configuration
v0.2.x: Performance improvements and bug fixes
v0.1.x: Initial implementation</p>
]]></description><pubDate>2026-05-11 07:11:35</pubDate></item><item><title>mace：又一个嵌入式 key-value 存储</title><link>https://rustcc.cn/article?id=e2ec9976-8f93-4c2e-b63e-5d4419f55631</link><description><![CDATA[<p>mace 是一个 Rust 实现的嵌入式 KV 引擎，结合了 B+ 树的读性能和 LSM 树的写吞吐，在读多写少和扫描场景下有明显的性能优势。</p>
<hr>
<h2>核心能力</h2>
<ul>
<li><strong>混合架构</strong>：兼顾 B+ 树读速与 LSM 树写吞吐</li>
<li><strong>MVCC 并发</strong>：非阻塞的并发读写</li>
<li><strong>闪存优化</strong>：面向 SSD/NVMe 的 log-structured 设计</li>
<li><strong>大值分离</strong>：独立 Blob 存储，减少写放大</li>
<li><strong>ACID 事务</strong>：完整的事务支持</li>
</ul>
<hr>
<h2>性能数据</h2>
<table>
<thead>
<tr>
<th>场景</th>
<th>吞吐量提升</th>
</tr>
</thead>
<tbody>
<tr>
<td>随机读</td>
<td>2.4x</td>
</tr>
<tr>
<td>范围扫描</td>
<td>3.5x</td>
</tr>
<tr>
<td>读 heavy 混合负载</td>
<td>2.3x</td>
</tr>
<tr>
<td>写 heavy 混合负载</td>
<td>0.76x</td>
</tr>
</tbody>
</table>
<blockquote>
<p>注：以上为与 RocksDB 对比的中位数倍数。</p>
</blockquote>
<hr>
<h2>适用场景</h2>
<ul>
<li>需要高并发读写的嵌入式服务（尤其是 mixed/read-heavy 负载）</li>
<li>写入吞吐敏感的本地存储层（中小 value 场景优势更明显）</li>
<li>混合读写 + 扫描的业务</li>
<li>需要本地事务和 MVCC 的 Rust 应用</li>
</ul>
<hr>
<h2>地址</h2>
<ul>
<li>源码：<a href="https://github.com/abbycin/mace" rel="noopener noreferrer">https://github.com/abbycin/mace</a></li>
<li>Benchmark 脚本：<a href="https://github.com/abbycin/kv_bench" rel="noopener noreferrer">https://github.com/abbycin/kv_bench（scale 分支）</a></li>
</ul>
<blockquote>
<p>mace 还在非常早期的阶段，目前还在努力提升稳定性以及对特定workload进行优化...</p>
</blockquote>
<p><strong>0.0.29 版更新</strong></p>
<table>
<thead>
<tr>
<th>Workload</th>
<th align="right">Mace胜OPS</th>
<th align="right">OPS中位数比 (Mace/RocksDB)</th>
<th align="right">Mace胜p99</th>
<th align="right">p99中位数比 (Mace/RocksDB)</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>W1</code> (95R/5U, uniform)</td>
<td align="right">16 / 16</td>
<td align="right"><strong>2.3x</strong></td>
<td align="right">5 / 16</td>
<td align="right"><strong>1.0x</strong></td>
</tr>
<tr>
<td><code>W2</code> (95R/5U, zipf)</td>
<td align="right">16 / 16</td>
<td align="right"><strong>1.5x</strong></td>
<td align="right">11 / 16</td>
<td align="right"><strong>0.5x</strong></td>
</tr>
<tr>
<td><code>W3</code> (50R/50U)</td>
<td align="right">15 / 16</td>
<td align="right"><strong>1.4x</strong></td>
<td align="right">9 / 16</td>
<td align="right"><strong>0.5x</strong></td>
</tr>
<tr>
<td><code>W4</code> (5R/95U)</td>
<td align="right">12 / 16</td>
<td align="right"><strong>1.3x</strong></td>
<td align="right">7 / 16</td>
<td align="right"><strong>1.0x</strong></td>
</tr>
<tr>
<td><code>W5</code> (70R/25U/5S)</td>
<td align="right">15 / 16</td>
<td align="right"><strong>2.1x</strong></td>
<td align="right">16 / 16</td>
<td align="right"><strong>0.2x</strong></td>
</tr>
<tr>
<td><code>W6</code> (100% scan)</td>
<td align="right">16 / 16</td>
<td align="right"><strong>4.6x</strong></td>
<td align="right">15 / 16</td>
<td align="right"><strong>0.2x</strong></td>
</tr>
</tbody>
</table>
]]></description><pubDate>2026-03-09 11:56:39</pubDate></item><item><title>🌱 Rudis 0.4.0 发布，一个高性能内存数据库</title><link>https://rustcc.cn/article?id=682d0f5e-15ec-4138-aff6-d045fb529a7e</link><description><![CDATA[<p>项目介绍：</p>
<p>Rudis 是一个采用 Rust 语言编写得高性能键值存储系统，旨在利用 Rust 语言的优势来重新复现 Rudis 的核心功能，以满足用户对高性能、可靠性和安全性的需求，同时保证与 Rudis API 的兼容。</p>
<p>跨平台，兼容 windows、linux 系统架构。 兼容 字符串、集合、哈希、列表、有序集合数据结构。 提供 rdb 与 aof 机制以支持数据备份和恢复。 拥有卓越的处理速度和即时响应能力。 兼容 Rudis 的命令和协议规范。</p>
<p>欢迎在 GitHub 上关注我们的项目发展轨迹：</p>
<p>👉 https://github.com/lunar-landing/rudis</p>
<p>更新日志：</p>
<ul>
<li>新增 List 数据结构 Blpop、Brpop 命名。</li>
<li>新增 Hash 数据结构 HSCAN 命令，支持 MATCH 和 COUNT 参数。</li>
<li>新增 String 数据结构 SETEX、PSETEX、SETNX、SETBIT、GETBIT、BITCOUNT、BITOP 命令。</li>
<li>新增 Set 数据结构 SRANDMEMBER、SDIFFSTORE、SINTERSTORE、SMOVE 命令。</li>
<li>新增 HyperLogLog 数据结构及 PFADD、PFCOUNT、PFMERGE 命令。</li>
<li>重构 SortedSet 底层实现，采用 HashMap + SkipList 架构提升性能，并支持 bincode 序列化。</li>
<li>修复 SETEX/PSETEX 过期记录清理逻辑以及系统时间倒退导致的 RDB 调度 Panic 问题。</li>
</ul>
]]></description><pubDate>2026-02-03 03:12:19</pubDate></item><item><title>我做了一个独立开发者行情板，想试着对抗一次内卷</title><link>https://rustcc.cn/article?id=7a4bcdcd-4650-425b-92a4-6ef65838534b</link><description><![CDATA[<h1>接私活这几年，我发现我们根本不知道「合理报价」是多少</h1>
<p>这几年接私活、做外包、做独立项目，有一个问题一直困扰我：</p>
<blockquote>
<p><strong>我们其实不知道一个项目「合理的价格」是多少。</strong></p>
</blockquote>
<p>不是技术难度不知道，而是——<br>
你不知道别人真实成交是多少，只能靠猜、靠平台最低价、靠「听说」。</p>
<p>需求方会说：</p>
<blockquote>
<p>「别人比你便宜一半。」</p>
</blockquote>
<p>开发者只能纠结：</p>
<blockquote>
<p>「我是报高了，还是别人报低了？」</p>
</blockquote>
<p>时间久了，就变成大家都在往下试探，<br>
<strong>内卷不是某个人的选择，而是信息不透明的结果。</strong></p>
<hr>
<h2>我已经做了什么</h2>
<p>我先从自己开始。</p>
<p>我把自己这几年做过的一些真实项目整理出来，包括：</p>
<ul>
<li>项目类型</li>
<li>实际成交价格</li>
<li>大概工期</li>
<li>是否反复改需求</li>
<li>是否包含售后</li>
</ul>
<p>做成了一个 <strong>独立开发者行情板</strong>。</p>
<p>目前一共 <strong>23 个案例</strong>：</p>
<ul>
<li>大部分是我自己的真实成交</li>
<li>少部分是朋友的</li>
<li>也有几个是匿名提交的</li>
</ul>
<p>我不回避这个事实：<br>
<strong>数据现在还很少，而且并不「漂亮」。</strong></p>
<p>但它至少是真实的。</p>
<hr>
<h2>为什么我需要更多人，而不是「更多数据」</h2>
<p>我一个人的案例，其实没什么意义。</p>
<p>但如果有：</p>
<ul>
<li>50 个</li>
<li>100 个</li>
<li>200 个</li>
</ul>
<p>来自不同背景、不同技术栈、不同城市的真实案例，<br>
至少可以做到一件事：</p>
<blockquote>
<p><strong>让后来的人，在报价时有一个不被平台最低价绑架的参考。</strong></p>
</blockquote>
<p>你不需要证明你多厉害，<br>
也不需要报一个「体面」的价格，<br>
<strong>真实比好看重要。</strong></p>
<hr>
<h2>关于匿名和安全</h2>
<p>我知道大家最担心什么，所以我一开始就做了两件事：</p>
<ul>
<li>提供 <strong>匿名提交</strong></li>
<li>不要求任何可追溯身份信息</li>
</ul>
<p>目前有两个入口：</p>
<p><a href="https://test-cigsro9bfq3z.feishu.cn/share/base/form/shrcnoJFwnYGX1E8NKW6qjpNJ6X?from=navigation" rel="noopener noreferrer">飞书表单</a>
<a href="https://market.fxlogo.site" rel="noopener noreferrer">行情板网站</a></p>
<p>不署名、不展示来源、不做商业售卖。<br>
你可以只写你愿意写的字段。</p>
<hr>
<h2>说一句更远一点的想法（不画饼）</h2>
<p>行情板不是终点。</p>
<p>我真正想做的，是一个 <strong>不竞价、不抽佣、不负责售后</strong> 的撮合平台，<br>
只做一件事：</p>
<blockquote>
<p><strong>把预算真实的需求方，和愿意按合理价格做事的开发者，匹配到一起。</strong></p>
</blockquote>
<p>行情板只是前战，是定价共识的基础。<br>
如果连「合理价格区间」都没有，<br>
任何撮合都会退化成比谁便宜。</p>
<p>我不确定这条路能走多远，<br>
但至少想先试一次。</p>
<hr>
<h2>最后</h2>
<p>如果你愿意贡献一个案例：</p>
<ul>
<li>成功的</li>
<li>失败的</li>
<li>觉得自己报低了的</li>
<li>或者被压价压得很难受的</li>
</ul>
<p>都可以。</p>
<p>如果你不想提交，也没关系，<br>
<strong>至少希望这个东西能让你下次报价时，心里多一点底气。</strong></p>
]]></description><pubDate>2026-02-02 10:25:00</pubDate></item><item><title>低成本 AI 赋能首选！算纽 GPUNexus 聚合全球算力，MaaS 服务直达业务核心</title><link>https://rustcc.cn/article?id=d599b7f7-9c0b-4fe6-8396-133b85abbe30</link><description><![CDATA[<p>算纽GPUNexus定位全球 GPU 资源智能调度枢纽，致力于构建低成本、高弹性的下一代分布式 AI 计算生态。我们的核心服务模式：</p>
<ul>
<li>
<p>算力层聚合：广泛接入全球闲散 GPU 算力资源，通过标准化调度技术实现算力的统一管理与高效利用；</p>
</li>
<li>
<p>服务层赋能：在聚合算力之上深度部署 MaaS 模型服务，客户无需投入高昂成本搭建算力与模型架构，只需通过简洁的大模型接口，即可按需调用 AI 能力，快速赋能业务创新。</p>
</li>
</ul>
<p>算纽（GPUNexus）打通算力资源与模型应用的壁垒，让 AI 服务更便捷、更普惠。</p>
<h1>2. 产品形态</h1>
<h2>2.1. 算力资产分享</h2>
<p>算纽算力资产分享产品，核心打破算力孤岛，依托智能调度技术，实现各类计算资源一键接入、整合与统一调度，激活分散算力价值。</p>
<p>产品支持全场景接入，覆盖算力中心、企业服务器等专业设备及个人电脑、手机等终端，实现“云-边-端”全域覆盖。无论闲置算力拥有方（企业/机构/个人）还是算力需求方，均可通过平台精准匹配、高效流转。</p>
<p>无需复杂配置即可快速上线，智能调度实现供需实时匹配，既提升算力利用率，又帮助需求方降本、分享方变现，构建互利共赢的算力生态。</p>
<h2>2.2. MAAS服务</h2>
<p>算纽 MaaS服务，一站式整合30 余款主流开源大模型矩阵，囊括 DeepSeek、Qwen、GLM、Kimi、MiniMax 等明星模型，深度覆盖编程开发、学术研究与论文创作、数学推理、视觉处理与多模态交互、对话与长文本处理五大核心场景。</p>
<h2>2.3. 开发者套餐</h2>
<p>算纽开发者套餐，专为学生、独立开发者及中小团队量身定制，以超高性价比解锁顶级大模型编程能力，让每一份开发需求都能高效落地。</p>
<p>套餐核心优势直击开发痛点：成本颠覆性降低，计费低至传统tokens计费的一折，大幅压缩开发成本；模型自由切换，无需冗余购买多平台会员，一键直达GLM-4.7、MiniMax-M2.1、Kimi-K2三大顶级编程模型，最新最强的模型能力随心选；高效创作不等待，生成速度媲美同类高级套餐，助力快速完成代码编写、调试、优化等核心工作。</p>
<p>更有7天免费体验限时开启！零成本即可抢先体验顶级模型的强悍编程能力，轻松开启高效开发新体验。</p>
<p>​</p>
<ul>
<li>官方网址：<a href="https://gpunexus.com/signup?aff=c1xh" rel="noopener noreferrer">https://gpunexus.com/</a></li>
<li>咨询电话：010-53650986</li>
<li>联系邮箱：data@chengfangtech.com</li>
</ul>
]]></description><pubDate>2026-01-14 02:18:35</pubDate></item><item><title>helix-kanban 终端内的多窗口看板</title><link>https://rustcc.cn/article?id=56234088-880c-4fc8-8281-726abca68b8a</link><description><![CDATA[<h1>Kanban</h1>
<p>一个终端看板应用，灵感来自 <a href="https://helix-editor.com/" rel="noopener noreferrer">Helix 编辑器</a>的键位设计。</p>
<h2>预览</h2>
<p><img src="https://raw.githubusercontent.com/menzil/helix-kanban/master/screenshoot.png" alt="Kanban TUI 截图"></p>
<h2>特性</h2>
<ul>
<li>📁 <strong>基于文件存储</strong> - 使用 Markdown 文件和 TOML 配置，易于版本控制</li>
<li>🎯 <strong>多项目支持</strong> - 支持全局项目和本地项目（<code>.kanban/</code>）</li>
<li>⌨️  <strong>Helix 风格键位</strong> - 符合直觉的键盘快捷键</li>
<li>🪟 <strong>窗口管理</strong> - 支持垂直/水平分屏，同时查看多个项目，自动保存和恢复工作区布局</li>
<li>🎨 <strong>现代 TUI</strong> - 基于 ratatui 的美观终端界面</li>
<li>📝 <strong>Markdown 支持</strong> - 任务使用 Markdown 格式，支持外部编辑器</li>
<li>🔍 <strong>任务预览</strong> - 内置预览和外部预览工具支持</li>
<li>⚙️  <strong>自动配置</strong> - 首次运行自动检测编辑器和预览器</li>
</ul>
<h2>安装</h2>
<h3>从 crates.io 安装</h3>
<pre><code>cargo install helix-kanban
</code></pre>
<h3>从源码构建</h3>
<pre><code>git clone https://github.com/menzil/helix-kanban.git
cd helix-kanban
cargo build --release
</code></pre>
<h2>快速开始</h2>
<p>首次运行会显示欢迎对话框，自动检测系统编辑器和 Markdown 预览器：</p>
<pre><code>hxk
</code></pre>
<h3>输入法切换（macOS）</h3>
<p>为了更好的输入体验，在正常模式下自动切换到英文输入法，在对话框模式（如创建/编辑任务）时保持用户的输入法。</p>
<p><strong>推荐安装 im-select 工具：</strong></p>
<pre><code># 使用 Homebrew 安装
brew install im-select

# 或者使用 curl 安装
curl -Ls https://raw.githubusercontent.com/daipeihust/im-select/master/install_mac.sh | sh
</code></pre>
<blockquote>
<p>注意：如果不安装 im-select，程序仍可正常运行，只是不会自动切换输入法。</p>
</blockquote>
<h3>配置管理</h3>
<p>查看当前配置：</p>
<pre><code>hxk config show
</code></pre>
<p>设置编辑器：</p>
<pre><code>hxk config editor nvim
hxk config editor "code --wait"
</code></pre>
<p>设置 Markdown 预览器：</p>
<pre><code>hxk config viewer glow
hxk config viewer "open -a Marked 2"
</code></pre>
<h2>键位绑定</h2>
<h3>基础导航</h3>
<table>
<thead>
<tr>
<th>键位</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>j</code> / <code>↓</code></td>
<td>下一个任务</td>
</tr>
<tr>
<td><code>k</code> / <code>↑</code></td>
<td>上一个任务</td>
</tr>
<tr>
<td><code>h</code> / <code>←</code></td>
<td>左边的列</td>
</tr>
<tr>
<td><code>l</code> / <code>→</code></td>
<td>右边的列</td>
</tr>
<tr>
<td><code>q</code></td>
<td>退出程序</td>
</tr>
<tr>
<td><code>ESC</code></td>
<td>取消/返回</td>
</tr>
<tr>
<td><code>:</code></td>
<td>命令模式</td>
</tr>
<tr>
<td><code>?</code></td>
<td>显示帮助</td>
</tr>
<tr>
<td><code>Space</code></td>
<td>打开命令菜单</td>
</tr>
</tbody>
</table>
<h3>任务操作</h3>
<table>
<thead>
<tr>
<th>键位</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>a</code></td>
<td>创建新任务</td>
</tr>
<tr>
<td><code>e</code></td>
<td>编辑任务标题</td>
</tr>
<tr>
<td><code>E</code></td>
<td>用外部编辑器编辑任务</td>
</tr>
<tr>
<td><code>v</code></td>
<td>预览任务（TUI 内）</td>
</tr>
<tr>
<td><code>V</code></td>
<td>用外部工具预览任务</td>
</tr>
<tr>
<td><code>d</code></td>
<td>删除任务</td>
</tr>
<tr>
<td><code>H</code></td>
<td>任务移到左列</td>
</tr>
<tr>
<td><code>L</code></td>
<td>任务移到右列</td>
</tr>
<tr>
<td><code>J</code></td>
<td>任务在列内下移</td>
</tr>
<tr>
<td><code>K</code></td>
<td>任务在列内上移</td>
</tr>
</tbody>
</table>
<h3>项目管理</h3>
<table>
<thead>
<tr>
<th>键位</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>n</code></td>
<td>新建本地项目 [L]</td>
</tr>
<tr>
<td><code>N</code></td>
<td>新建全局项目 [G]</td>
</tr>
<tr>
<td><code>Space f</code></td>
<td>快速切换项目</td>
</tr>
<tr>
<td><code>Space p o</code></td>
<td>打开项目</td>
</tr>
<tr>
<td><code>Space p n</code></td>
<td>创建新项目</td>
</tr>
<tr>
<td><code>Space p d</code></td>
<td>删除项目</td>
</tr>
<tr>
<td><code>Space p r</code></td>
<td>重命名项目</td>
</tr>
<tr>
<td><code>Space r</code></td>
<td>重新加载当前项目</td>
</tr>
<tr>
<td><code>Space R</code></td>
<td>重新加载所有项目</td>
</tr>
</tbody>
</table>
<h3>窗口管理</h3>
<table>
<thead>
<tr>
<th>键位</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Space w w</code></td>
<td>下一个窗口</td>
</tr>
<tr>
<td><code>Space w v</code></td>
<td>垂直分屏</td>
</tr>
<tr>
<td><code>Space w s</code></td>
<td>水平分屏</td>
</tr>
<tr>
<td><code>Space w q</code></td>
<td>关闭窗口</td>
</tr>
<tr>
<td><code>Space w h</code></td>
<td>聚焦左面板</td>
</tr>
<tr>
<td><code>Space w l</code></td>
<td>聚焦右面板</td>
</tr>
<tr>
<td><code>Space w j</code></td>
<td>聚焦下面板</td>
</tr>
<tr>
<td><code>Space w k</code></td>
<td>聚焦上面板</td>
</tr>
</tbody>
</table>
<h3>命令模式</h3>
<p>按 <code>:</code> 进入命令模式，支持的命令：</p>
<ul>
<li><code>:q</code> / <code>:quit</code> - 退出应用</li>
<li><code>:open</code> / <code>:po</code> - 打开项目</li>
<li><code>:new</code> / <code>:pn</code> - 创建新项目（全局）</li>
<li><code>:new-local</code> / <code>:pnl</code> - 创建新项目（本地）</li>
<li><code>:add</code> / <code>:tn</code> - 创建新任务</li>
<li><code>:edit</code> / <code>:te</code> - 编辑任务</li>
<li><code>:view</code> / <code>:tv</code> - 预览任务</li>
<li><code>:reload</code> / <code>:r</code> / <code>:refresh</code> - 重新加载当前项目</li>
<li><code>:reload-all</code> / <code>:ra</code> / <code>:refresh-all</code> - 重新加载所有项目</li>
<li><code>:vsplit</code> / <code>:sv</code> - 垂直分屏</li>
<li><code>:hsplit</code> / <code>:sh</code> - 水平分屏</li>
<li><code>:help</code> / <code>:h</code> - 显示帮助</li>
</ul>
<h2>数据存储</h2>
<h3>全局项目</h3>
<p>全局项目存储在 <code>~/.kanban/projects/</code> 目录下。</p>
<h3>本地项目</h3>
<p>在任何目录下按 <code>n</code> 创建本地项目，会在当前目录的 <code>.kanban/</code> 下存储：</p>
<pre><code>your-project/
├── .kanban/
│   └── kanban-project/
│       ├── .kanban.toml
│       ├── todo/
│       ├── doing/
│       └── done/
└── ... (你的其他文件)
</code></pre>
<h3>项目结构</h3>
<pre><code>project-name/
├── .kanban.toml          # 项目配置
├── todo/                 # Todo 任务
│   ├── 001.md
│   └── 002.md
├── doing/                # 进行中任务
│   └── 003.md
└── done/                 # 完成的任务
    └── 004.md
</code></pre>
<h3>任务文件格式</h3>
<p>任务以 Markdown 格式存储：</p>
<pre><code># 任务标题

created: 2025-12-10T10:30:00+08:00
priority: high

任务的详细描述内容...

## 子任务

- [ ] 子任务 1
- [x] 子任务 2
</code></pre>
<h3>配置文件</h3>
<p>应用配置存储在 <code>~/.kanban/config.toml</code>：</p>
<pre><code>editor = "nvim"
markdown_viewer = "glow"

# 隐藏的全局项目列表（软删除）
hidden_projects = ["old-project", "archived-project"]
</code></pre>
<h3>工作区状态保存</h3>
<p>应用会自动保存窗口布局和工作状态，下次启动时恢复：</p>
<p><strong>保存内容</strong>：</p>
<ul>
<li>分屏结构（垂直/水平分割）</li>
<li>每个窗格打开的项目</li>
<li>当前选中的列和任务</li>
<li>聚焦的窗格</li>
</ul>
<p><strong>保存位置</strong>：</p>
<ul>
<li>全局工作区：<code>~/.kanban/workspace.toml</code> - 在任何目录启动时使用</li>
<li>本地工作区：<code>.kanban/workspace.toml</code> - 在项目目录下启动时优先使用</li>
</ul>
<p><strong>使用场景</strong>：</p>
<ul>
<li>经常需要同时查看多个项目？设置好分屏布局后，下次启动自动恢复</li>
<li>在不同项目目录工作？每个目录都有自己独立的工作区布局</li>
<li>想要重置布局？使用命令 <code>:reset-layout</code> 恢复默认单窗格</li>
</ul>
<p><strong>示例工作区配置</strong> (<code>workspace.toml</code>)：</p>
<pre><code># 自动生成，通常无需手动编辑
focused_pane = 2
next_pane_id = 4

[[panes]]
id = 0
type = "horizontal_split"
left = 1
right = 2

[[panes]]
id = 1
type = "leaf"
project = "work-project"
selected_column = 1
selected_task_index = 0

[[panes]]
id = 2
type = "leaf"
project = "personal-project"
selected_column = 0
selected_task_index = 2
</code></pre>
<h2>开发</h2>
<pre><code># 运行开发版本
cargo run

# 运行测试
cargo test

# 构建 release 版本
cargo build --release
</code></pre>
<h2>致谢</h2>
<ul>
<li>键位设计灵感来自 <a href="https://helix-editor.com/" rel="noopener noreferrer">Helix Editor</a></li>
<li>UI 框架使用 <a href="https://github.com/ratatui-org/ratatui" rel="noopener noreferrer">ratatui</a></li>
</ul>
<h2>许可证</h2>
<p>MIT OR Apache-2.0</p>
]]></description><pubDate>2025-12-11 10:37:54</pubDate></item><item><title>使用 Rust 宏实现基于 Sea-ORM 的乐观锁样板代码自动化</title><link>https://rustcc.cn/article?id=1e3818da-3c6a-46eb-89ab-3e3144fc362c</link><description><![CDATA[<p>在昨天的文章中，我们讨论了乐观锁（Optimistic Locking）作为高并发场景下保证数据一致性的重要手段。但乐观锁的实现，尤其是基于版本号（Version）或时间戳（Updated At）的 <strong>CAS (Compare-and-Swap)</strong> 模式，往往需要在应用的每个 Repository 中重复编写大量的样板代码。</p>
<p>今天的核心主题是：如何利用 <strong>Rust 过程宏</strong>的强大能力，将这些繁琐的持久化逻辑自动化，让开发者只需声明字段，即可获得健壮的乐观锁支持。</p>
<hr>
<h2>宏架构：分治与协作</h2>
<p>实现一个完整的、自动化的乐观锁流程，需要宏在两个不同的代码层面进行注入和协作：</p>
<ol>
<li><strong>数据变更层</strong> (<code>ActiveModelBehavior</code>)：负责在数据写入数据库前，自动管理版本号 (<code>version</code>) 和时间戳 (<code>updated_at</code>) 的递增/更新。</li>
<li><strong>持久化操作层</strong> (<code>Repository::save</code>)：负责实现核心的原子更新逻辑，即 <strong>CAS 检查</strong>。</li>
</ol>
<h3>Part 1: ActiveModel 的预处理钩子 (<code>before_save</code>)</h3>
<p>这是我们实现乐观锁的第一步：确保在更新操作中，版本号能够正确地 <strong>自增</strong>。</p>
<p>我们通过宏注入或修改 <code>sea-orm::ActiveModelBehavior</code> Trait 的 <code>before_save</code> 钩子。</p>
<p><strong>宏注入逻辑概览：</strong></p>
<pre><code>// 宏片段：insert_active_model_behavior_impl 的核心逻辑
if need_version {
    let version_stmt = quote! {
        if insert {
            // 插入 (insert=true) 时，版本号初始化为 1
            self.version = Set(1);
        } else if self.is_changed() {
            // 更新 (insert=false) 且模型有业务字段变化时，版本号自增
            let current_version = match self.version {
                Set(v) =&gt; *v,
                _ =&gt; 0,
            };
            self.version = Set(current_version + 1);
        }
    };
}
// updated_at 逻辑类似：非插入且 is_changed 时设置为当前时间
</code></pre>
<p><strong>关键成果：</strong>
当我们在 Repository 中执行更新操作时，<code>ActiveModel</code> 已经通过 <code>before_save</code> 确保了两个重要事实：</p>
<ol>
<li>它携带着我们从数据库中读出的 <strong>旧版本号</strong>。</li>
<li>它将尝试写入的 <code>version</code> 值，是 <strong>旧版本号 + 1</strong>。</li>
</ol>
<hr>
<h3>Part 2: Repository 的原子 CAS 更新 (<code>save</code> 方法)</h3>
<p>这是乐观锁实现的核心战场，由 <code>fn create_tenant_save_impl</code> 宏片段生成。其逻辑必须严格遵循 <strong>三步走</strong> 策略，以处理成功、冲突和首次插入三种情况。</p>
<h4>Step 1: 原子 UPDATE (Compare-and-Swap)</h4>
<p>我们使用 <code>sea-orm</code> 的 <code>update_many</code> 配合 <code>filter</code> 条件，来实现原子性检查。</p>
<p>我们从聚合根 (<code>entity</code>) 中取出 <strong>旧版本</strong>（即 <code>current_version</code>），并将其作为 <code>WHERE</code> 子句的一部分。</p>
<pre><code>// 宏片段：create_tenant_save_impl 的核心 CAS 逻辑

// 从聚合获取当前版本（即期望的旧版本）
let current_version = entity_model.#optimistic_lock_field_ident();

// 1) 原子 UPDATE（带 version CAS）
let res = models::Entity::update_many()
    #id_filters // 主键和 TenantId 过滤
    // ⬇️ 核心：只有当数据库中的版本号等于旧版本号时，才允许更新 ⬇️
    .filter(models::Column::#optimistic_lock_col_ident.eq(current_version)) 
    .set(update_model.clone())
    .exec(&amp;conn)
    .await?;

if res.rows_affected &gt; 0 {
    // 成功！说明版本匹配，且更新成功写入
    // ... 事件处理并返回 Ok(())
    return Ok(());
}
</code></pre>
<p>如果 <code>rows_affected &gt; 0</code>，任务圆满完成。如果 <code>rows_affected == 0</code>，则进入下一步判断。</p>
<h4>Step 2 &amp; 3: 冲突检测与首次插入</h4>
<p>如果 CAS 更新失败（<code>rows_affected == 0</code>），我们需要区分是 <strong>版本冲突</strong>（记录存在但版本号不匹配）还是 <strong>首次插入</strong>（记录根本不存在）。</p>
<pre><code>// 2) UPDATE 未命中，检查记录是否存在
if models::Entity::find()
    #id_filters // 仅按主键和 TenantId 查找
    .one(&amp;conn)
    .await?
    .is_some()
{
    // 记录存在，但 Step 1 未命中 -&gt; 乐观锁冲突！
    return Err(#crate_root::domain::RepositoryError::optimistic_lock_error(
        "Optimistic lock conflict: Version mismatch".to_string(),
    ));
}

// 3) 记录不存在，执行首次插入
let insert_model: models::Model = entity_model.clone().try_into()?;
let mut active_model = insert_model.into_active_model();
active_model.insert(&amp;conn).await?;
// ... 事件处理并返回 Ok(())
</code></pre>
<h3>Talk is cheap, show me the code</h3>
<h4>before_save</h4>
<pre><code>fn insert_active_model_behavior_impl(input: &amp;mut ItemMod, model_config: &amp;ModelConfig) {
  let Some((_, items)) = &amp;mut input.content else {
      return;
  };

  let mut has_active_model_behavior = false;
  for item in items.iter_mut() {
      if let syn::Item::Impl(item_impl) = item
          &amp;&amp; let Some((_, path, _)) = &amp;item_impl.trait_
          &amp;&amp; path.segments.last().unwrap().ident == "ActiveModelBehavior"
      {
          has_active_model_behavior = true;
          break;
      }
  }

  if !has_active_model_behavior {
      let active_model_behavior_impl = quote! {
          #[async_trait]
          impl ActiveModelBehavior for ActiveModel {
              async fn before_save&lt;C&gt;(mut self, db: &amp;C, insert: bool) -&gt; Result&lt;Self, DbErr&gt;
              where
                  C: ConnectionTrait,
              {
                  Ok(self)
              }
          }
      };
      items.push(parse_quote!(#active_model_behavior_impl));
  }

  for item in items.iter_mut() {
      if let syn::Item::Impl(item_impl) = item
          &amp;&amp; let Some((_, path, _)) = &amp;item_impl.trait_
          &amp;&amp; path.segments.last().unwrap().ident == "ActiveModelBehavior"
      {
          let mut has_before_save = false;
          for item in item_impl.items.iter_mut() {
              if let syn::ImplItem::Fn(method) = item
                  &amp;&amp; method.sig.ident == "before_save"
              {
                  has_before_save = true;
                  break;
              }
          }

          if !has_before_save {
              let before_save_method = quote! {
                  async fn before_save&lt;C&gt;(mut self, db: &amp;C, insert: bool) -&gt; Result&lt;Self, DbErr&gt;
                  where
                      C: ConnectionTrait,
                  {
                      Ok(self)
                  }
              };
              item_impl.items.push(parse_quote!(#before_save_method));
          }

          let need_created_at = model_config
              .fields
              .iter()
              .any(|f| f.ident.as_ref().unwrap() == "created_at");
          let need_updated_at = model_config
              .fields
              .iter()
              .any(|f| f.ident.as_ref().unwrap() == "updated_at");

          let need_version = model_config
              .fields
              .iter()
              .any(|f| f.ident.as_ref().unwrap() == "version");

          if !(need_created_at || need_updated_at || need_version) {
              return;
          }

          for item in item_impl.items.iter_mut() {
              if let syn::ImplItem::Fn(method) = item
                  &amp;&amp; method.sig.ident == "before_save"
              {
                  let mut stmts = Vec::new();
                  stmts.push(quote! {
                      let now = chrono::Utc::now();
                  });

                  if need_created_at {
                      let created_at_stmt = quote! {
                          if insert {
                              self.created_at = Set(now);
                          }
                      };
                      stmts.push(created_at_stmt);
                  }
                  if need_updated_at {
                      let updated_at_stmt = quote! {
                          if insert {
                              self.updated_at = Set(now);
                          } else if self.is_changed() {
                              self.updated_at = Set(now);
                          }
                      };
                      stmts.push(updated_at_stmt);
                  }

                  if need_version {
                      let version_stmt = quote! {
                          if insert {
                              self.version = Set(1);
                          } else if self.is_changed() {
                              let current_version = match self.version {
                              Set(v) =&gt; *v,
                              _ =&gt; 0,
                          };
                              self.version = Set(current_version + 1);
                          }
                      };
                      stmts.push(version_stmt);
                  }

                  let stmts = parse_quote!({#(#stmts)*});

                  // 插入到方法体的开头
                  method.block.stmts.insert(0, stmts);
              }
          }
      }
  }
}

</code></pre>
<p>宏生成的代码示例</p>
<pre><code> impl ActiveModelBehavior for ActiveModel {
        #[allow(
            elided_named_lifetimes,
            clippy::async_yields_async,
            clippy::diverging_sub_expression,
            clippy::let_unit_value,
            clippy::needless_arbitrary_self_type,
            clippy::no_effect_underscore_binding,
            clippy::shadow_same,
            clippy::type_complexity,
            clippy::type_repetition_in_bounds,
            clippy::used_underscore_binding
        )]
        fn before_save&lt;'life0, 'async_trait, C&gt;(
            self,
            db: &amp;'life0 C,
            insert: bool,
        ) -&gt; ::core::pin::Pin&lt;
            Box&lt;
                dyn ::core::future::Future&lt;Output = Result&lt;Self, DbErr&gt;&gt;
                    + ::core::marker::Send
                    + 'async_trait,
            &gt;,
        &gt;
        where
            C: ConnectionTrait,
            C: 'async_trait,
            'life0: 'async_trait,
            Self: 'async_trait,
        {
            Box::pin(async move {
                if let ::core::option::Option::Some(__ret) =
                    ::core::option::Option::None::&lt;Result&lt;Self, DbErr&gt;&gt;
                {
                    #[allow(unreachable_code)]
                    return __ret;
                }
                let mut __self = self;
                let insert = insert;
                let __ret: Result&lt;Self, DbErr&gt; = {
                    {
                        let now = chrono::Utc::now();
                        if insert {
                            __self.created_at = Set(now);
                        }
                        if insert {
                            __self.updated_at = Set(now);
                        } else if __self.is_changed() {
                            __self.updated_at = Set(now);
                        }
                    }
                    Ok(__self)
                };
                #[allow(unreachable_code)]
                __ret
            })
        }
    }
</code></pre>
<h4>Repository::save</h4>
<pre><code>fn create_tenant_save_impl(
    crate_root: &amp;Path,
    aggregate: &amp;Path,
    args: &amp;RepositoryStructArgs,
    id_filters: &amp;TokenStream,
) -&gt; TokenStream {
    // 若指定了乐观锁字段，准备字段名/Column ident
    let optimistic_lock_field = args.optimistic_lock_field.as_ref().map(|lit| {
        let optimistic_lock_field_name = lit.value();
        let optimstic_lock_field_ident = new_id(&amp;optimistic_lock_field_name); // 用于 ActiveModel/Model 字段访问
        let optimistic_lock_col_ident = new_id(&amp;to_pascal_case(&amp;optimistic_lock_field_name)); // 用于 models::Column::Xxx
        (
            optimistic_lock_field_name,
            optimstic_lock_field_ident,
            optimistic_lock_col_ident,
        )
    });

    // 根据是否指定乐观锁字段，生成 save 的实现
    if let Some((
        optimistic_lock_field_name,
        optimistic_lock_field_ident,
        optimistic_lock_col_ident,
    )) = optimistic_lock_field
    {
        if optimistic_lock_field_name == "version" {
            quote! {
                async fn save(
                    &amp;self,
                    txn: &amp;mut TC,
                    entity: &amp;mut #crate_root::domain::EventSourcedEntity&lt;#aggregate&gt;,
                ) -&gt; Result&lt;(), #crate_root::domain::RepositoryError&gt; {
                    use #crate_root::domain::SeaOrmModelUpdater;
                    use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter};
                    use sea_orm::ActiveValue::Set;

                    let conn = txn.get_connection();
                    let entity_model: &amp;#aggregate = entity;

                    let id = entity_model.id();
                    let tenant_id = entity_model.tenant_id();

                    // 从聚合获取当前版本与期望旧版本
                    let current_version = entity_model.#optimistic_lock_field_ident();

                    // 构造用于原子更新的 ActiveModel（只写回必要列）
                    let mut update_model = models::Model::from(entity_model.clone()).into_active_model();

                    // 1) 原子 UPDATE（带 version CAS）
                    let res = models::Entity::update_many()
                        #id_filters
                        .filter(models::Column::TenantId.eq(*tenant_id))
                        .filter(models::Column::#optimistic_lock_col_ident.eq(current_version))
                        .set(update_model.clone())
                        .exec(&amp;conn)
                        .await?;

                    if res.rows_affected &gt; 0 {
                        entity.move_event_to_context(txn);
                        return Ok(());
                    }

                    // 2) UPDATE 未命中，检查记录是否存在（按主键 + tenant）
                    if models::Entity::find()
                        #id_filters
                        .filter(models::Column::TenantId.eq(*tenant_id))
                        .one(&amp;conn)
                        .await?
                        .is_some()
                    {
                        return Err(#crate_root::domain::RepositoryError::optimistic_lock_error(
                            "Optimistic lock conflict: Version mismatch".to_string(),
                        ));
                    }

                    // 3) 记录不存在，插入数据
                    let insert_model: models::Model = entity_model.clone().try_into()?;
                    let mut active_model = insert_model.into_active_model();
                    active_model.insert(&amp;conn).await?;
                    entity.move_event_to_context(txn);
                    Ok(())
                }
            }
        } else {
            // treat as timestamp update_at
            quote! {
                async fn save(
                    &amp;self,
                    txn: &amp;mut TC,
                    entity: &amp;mut #crate_root::domain::EventSourcedEntity&lt;#aggregate&gt;,
                ) -&gt; Result&lt;(), #crate_root::domain::RepositoryError&gt; {
                    use #crate_root::domain::SeaOrmModelUpdater;
                    use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter};
                    use sea_orm::ActiveValue::Set;
                    use chrono::Utc;

                    let conn = txn.get_connection();
                    let entity_model: &amp;#aggregate = entity;

                    let id = entity_model.id();
                    let tenant_id = entity_model.tenant_id();

                    // 读取实体携带的旧时间戳与准备新的时间戳
                    let current_ts = entity_model.#optimistic_lock_field_ident();

                    // 构造用于原子更新的 ActiveModel
                    let mut update_model = models::Model::from(entity_model.clone()).into_active_model();

                    // 1) 原子 UPDATE（带 updated_at CAS）
                    let res = models::Entity::update_many()
                        #id_filters
                        .filter(models::Column::TenantId.eq(*tenant_id))
                        .filter(models::Column::#optimistic_lock_col_ident.eq(current_ts))
                        .set(update_model.clone())
                        .exec(&amp;conn)
                        .await?;

                    if res.rows_affected &gt; 0 {
                        entity.move_event_to_context(txn);
                        return Ok(());
                    }

                    // 2) UPDATE 未命中，检查记录是否存在
                    if models::Entity::find()
                        #id_filters
                        .filter(models::Column::TenantId.eq(*tenant_id))
                        .one(&amp;conn)
                        .await?
                        .is_some()
                    {
                        return Err(#crate_root::domain::RepositoryError::optimistic_lock_error(
                            "Optimistic lock conflict".to_string(),
                        ));
                    }

                    // 3) 记录不存在，直接插入数据
                    let insert_model: models::Model = entity_model.clone().try_into()?;
                    let mut active_model = insert_model.into_active_model();

                    active_model.insert(&amp;conn).await?;
                    entity.move_event_to_context(txn);
                    Ok(())
                }
            }
        }
    } else {
        // no optimistic lock field -&gt; simple update/insert behavior (原始实现)
        quote! {
            async fn save(
                &amp;self,
                txn: &amp;mut TC,
                entity: &amp;mut #crate_root::domain::EventSourcedEntity&lt;#aggregate&gt;,
            ) -&gt; Result&lt;(), #crate_root::domain::RepositoryError&gt; {
                use #crate_root::domain::SeaOrmModelUpdater;
                use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter};

                let conn = txn.get_connection();

                let entity_model: &amp;#aggregate = entity;

                let id = entity_model.id();
                let tenant_id = entity_model.tenant_id();

                if let Some(mut model) = models::Entity::find()
                    #id_filters
                    .filter(models::Column::TenantId.eq(*tenant_id))
                    .one(&amp;conn)
                    .await?
                {
                    if &amp;model.tenant_id != tenant_id {
                        return Err(#crate_root::domain::RepositoryError::mapping_error(
                            format!(
                                "Tenant ID mismatch: expected {}, found {}, id: {}",
                                tenant_id, model.tenant_id, id
                            ),
                        ));
                    }

                    // 更新逻辑
                    model.update_from_aggregate_root(entity_model).await?;

                    let active_model = model.into_active_model();
                    active_model.update(&amp;conn).await?;
                } else {
                    // 创建新记录
                    let model: models::Model = entity_model.clone().try_into()?;
                    let active_model = model.into_active_model();
                    active_model.insert(&amp;conn).await?;
                }

                entity.move_event_to_context(txn);
                Ok(())
            }
        }
    }
}

</code></pre>
<p>宏生成的代码示例</p>
<pre><code>  async fn save(
        &amp;self,
        txn: &amp;mut TC,
        entity: &amp;mut core_common::domain::EventSourcedEntity&lt;TenantUser&gt;,
    ) -&gt; Result&lt;(), core_common::domain::RepositoryError&gt; {
        use core_common::domain::SeaOrmModelUpdater;
        use sea_orm::{
            ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter,
        };
        use sea_orm::ActiveValue::Set;
        use chrono::Utc;
        let conn = txn.get_connection();
        let entity_model: &amp;TenantUser = entity;
        let id = entity_model.id();
        let current_ts = entity_model.update_at();
        let mut update_model = models::Model::from(entity_model.clone())
            .into_active_model();
        let res = models::Entity::update_many()
            .filter(models::Column::Id.eq((id.tenant_id(), id.user_id())))
            .filter(models::Column::UpdateAt.eq(current_ts))
            .set(update_model.clone())
            .exec(&amp;conn)
            .await?;
        if res.rows_affected &gt; 0 {
            entity.move_event_to_context(txn);
            return Ok(());
        }
        if models::Entity::find()
            .filter(models::Column::Id.eq((id.tenant_id(), id.user_id())))
            .one(&amp;conn)
            .await?
            .is_some()
        {
            return Err(
                core_common::domain::RepositoryError::optimistic_lock_error(
                    "Optimistic lock conflict".to_string(),
                ),
            );
        }
        let insert_model: models::Model = entity_model.clone().try_into()?;
        let mut active_model = insert_model.into_active_model();
        active_model.insert(&amp;conn).await?;
        entity.move_event_to_context(txn);
        Ok(())
    }
</code></pre>
<h3>兼容性处理</h3>
<p>宏的另一个优势是其灵活性。它能根据字段名称自动适配不同的乐观锁策略：</p>
<ul>
<li>如果检测到字段为 <code>"version"</code>，则执行版本号的 CAS 逻辑。</li>
<li>如果检测到其他时间戳字段如 <code>"updated_at"</code>，则执行基于时间戳的 CAS 逻辑。</li>
</ul>
<hr>
<h2>结论</h2>
<p>通过将 <code>before_save</code> 中的版本递增逻辑，与 <code>Repository::save</code> 中的原子 CAS 检查完美结合，我们使用 Rust 过程宏实现了一个 <strong>高内聚、低耦合</strong> 的乐观锁基础设施。</p>
<p>开发者现在可以专注于业务逻辑，而将并发控制的复杂性和样板代码完全交给宏来处理。这不仅极大地提高了开发效率，同时也确保了底层持久化操作的健壮性和一致性。</p>
]]></description><pubDate>2025-11-18 12:33:36</pubDate></item><item><title>避开数据竞态：Rust SeaORM 中的乐观锁与 Upsert 模式实践</title><link>https://rustcc.cn/article?id=7436f49b-1862-4226-90cf-b517cf0d1902</link><description><![CDATA[<p>在构建高并发的后端服务时，确保数据的最终一致性是至关重要的。特别是当业务逻辑需要执行 <strong>"更新或插入 (Upsert)"</strong> 这种复合操作时，传统的 “先查询，后更新” 模式极易陷入并发陷阱。<br>
本文将深入探讨为什么简单的操作会引发竞态条件，并介绍如何在 Rust 的 SeaORM 框架中，使用 <strong>版本号（<code>i32</code>）</strong> 实现一个健壮的 <strong>原子化乐观锁 Upsert</strong> 流程。</p>
<hr>
<h2>一、乐观锁：不是不锁，而是“巧”锁</h2>
<p>数据库的并发控制主要分为悲观锁和乐观锁。</p>
<ul>
<li><strong>悲观锁（Pessimistic Locking）：</strong> 假设冲突一定会发生。在读取数据时就对数据行进行锁定，直到事务完成。</li>
<li><strong>乐观锁（Optimistic Locking）：</strong> 假设冲突很少发生。在整个事务过程中不锁定资源，而是通过检查数据是否被修改来确认。</li>
</ul>
<p>乐观锁的核心思想是：<strong>通过一次原子性的操作来检查并修改数据，而不是依赖两次独立的数据库操作。</strong></p>
<hr>
<h2>二、没有锁的陷阱：丢失更新的竞态条件</h2>
<p>让我们以一个 <code>version: i32</code> 字段为例，来看看缺乏原子性操作会导致什么问题。</p>
<h3>场景：多人同时更新同一条记录</h3>
<ol>
<li><strong>查询（事务 A/B）：</strong> 事务 A 和事务 B 都读取了 ID=1 的记录，其 <code>version</code> 都为 <strong><code>1</code></strong>。</li>
<li><strong>更新（事务 B 提交）：</strong> 事务 B 完成修改，执行 <strong>无版本检查</strong> 的 <code>UPDATE</code> 语句，数据库中的 <code>version</code> 变为 <code>2</code>。</li>
<li><strong>更新（事务 A 提交）：</strong> 事务 A 完成修改，也执行 <strong>无版本检查</strong> 的 <code>UPDATE</code> 语句。</li>
</ol>
<p><strong>结果：</strong> 事务 B 的业务变更被事务 A 的修改覆盖，导致 <strong>丢失更新（Lost Update）</strong> 的竞态条件。</p>
<h3>乐观锁的解决之道：单次原子操作</h3>
<p>要解决这个问题，必须让 <strong>“检查旧版本”</strong> 和 <strong>“设置新值”</strong> 成为一个原子操作，即在 <code>UPDATE</code> 语句中加入版本过滤条件：</p>
<pre><code>UPDATE records
SET title = '新标题', version = version + 1
WHERE id = 1 AND version = 1; -- 关键：只有旧版本为 1 时才允许更新
</code></pre>
<p>在 SeaORM 中，我们使用 <code>update_many()</code> 配合 <code>filter()</code> 来构造这个原子操作，并通过检查 <code>rows_affected</code> 来判断操作是否成功。</p>
<hr>
<h2>三、Upsert 流程的抉择：先 Update 再 Insert 的优势</h2>
<p>实现 Upsert 功能主要有两种策略：<strong>“先 Update 再 Insert”</strong> 和 <strong>“先 Insert 再 Update”</strong>。在涉及<strong>乐观锁</strong>的业务中，<strong>“先 Update 再 Insert”</strong> 模式是更优的选择。</p>
<h3>1. 模式一：先 Update 再 Insert（推荐）</h3>
<p>这种模式总是优先处理最常见的情况：<strong>更新现有记录</strong>。</p>
<p><strong>优势分析：</strong></p>
<ul>
<li><strong>天然支持乐观锁：</strong> 乐观锁检查（<code>WHERE version = ?</code>）直接集成在 <code>UPDATE</code> 语句中，利用了数据库的原子性，保证了在单次操作中完成检查和修改。</li>
<li><strong>高效处理更新：</strong> 在高并发的更新场景中，大部分操作都是更新。这种模式只需执行一次成功的 <code>UPDATE</code> 就能完成任务，避免了不必要的 <code>INSERT</code> 尝试。</li>
</ul>
<h3>2. 模式二：先 Insert 再 Update</h3>
<p><strong>流程：</strong> 尝试 <code>INSERT</code> $\to$ 如果失败（主键冲突），执行 <code>UPDATE</code>。</p>
<p><strong>劣势分析：</strong></p>
<ul>
<li><strong>乐观锁实现复杂：</strong> 如果 <code>INSERT</code> 失败，转到 <code>UPDATE</code> 时，必须确保 <code>UPDATE</code> 操作是带有乐观锁检查的，这增加了流程的复杂性。</li>
<li><strong>高更新场景效率低：</strong> 如果大部分操作是更新，这种模式会强制执行一次注定会失败的 <code>INSERT</code> 操作（抛出主键冲突错误），然后再执行一次 <code>UPDATE</code>，浪费了数据库资源。</li>
</ul>
<h3>总结：选择 “先 Update 再 Insert” 的理由</h3>
<p>在处理带有乐观锁的聚合根持久化时，<strong>“先 Update 再 Insert”</strong> 模式是首选方案。它能够利用 <code>UPDATE</code> 的原子性高效地处理最常见的<strong>更新</strong>操作，并<strong>天然地</strong>将乐观锁检查与数据库写操作绑定。</p>
<hr>
<h2>四、SeaORM 中的 Upsert 流程：UPDATE $\to$ FIND $\to$ INSERT</h2>
<p>基于 <strong>“先 Update 再 Insert”</strong> 的策略，我们构建一个清晰的 <strong>"原子 UPDATE + FIND + INSERT"</strong> 三步流程，以可靠地处理成功更新、并发冲突和成功插入三种情况。</p>
<h3>核心实现代码</h3>
<pre><code>// 假设 entity.version 是更新后的新版本，expected_old_version = entity.version - 1
async fn save&lt;T: TransactionContext&gt;(
    &amp;self,
    callback: &amp;mut EventSourcedEntity&lt;Callback&gt;,
    txn: &amp;mut T,
) -&gt; Result&lt;(), RepositoryError&gt; {
    let conn = txn.get_connection();
    let entity: &amp;Callback = callback;
    let id = entity.channel.0.clone(); 
    let expected_old_version = entity.version - 1; 

    // 准备 ActiveModel，设置新的 version
    let mut active_model_for_update: ActiveModel = entity.clone().into_active_model();
    active_model_for_update.version = Set(entity.version); 

    // ----------------------------------------------------
    // 第一步：尝试原子 UPDATE（带乐观锁）
    // ----------------------------------------------------
    let res = callback_model::Entity::update_many()
        .set(active_model_for_update)
        .filter(callback_model::Column::Channel.eq(id.clone())) 
        .filter(callback_model::Column::Version.eq(expected_old_version)) // 乐观锁检查
        .exec(conn)
        .await?;

    if res.rows_affected &gt; 0 {
        // 更新成功：影响行数 &gt; 0，说明乐观锁条件满足。
        callback.move_event_to_context(txn);
        return Ok(());
    }

    // ----------------------------------------------------
    // 第二步：UPDATE 失败。使用 FIND 检查记录是否存在（判断是否为并发冲突）
    // ----------------------------------------------------
    if callback_model::Entity::find_by_id(id.clone())
        .one(conn)
        .await?
        .is_some()
    {
        // 记录存在。UPDATE 失败且记录存在，必然是版本不匹配，即并发冲突。
        return Err(RepositoryError::optimistic_lock_error(
            "Optimistic lock conflict: Record exists, but old version did not match."
        ));
    }

    // ----------------------------------------------------
    // 第三步：记录不存在，尝试 INSERT
    // ----------------------------------------------------
    let active_model_for_insert: ActiveModel = entity.clone().into_active_model();
    
    active_model_for_insert.insert(conn).await
        .map_err(|e| {
             // 如果 INSERT 失败，则视为并发冲突（在 FIND 之后被其他事务插入）。
             match e {
                 DbErr::RecordNotInserted | DbErr::Custom(_) =&gt; RepositoryError::optimistic_lock_error(
                    "Concurrency conflict: Record inserted after non-existence check."
                 ),
                 _ =&gt; e.into(),
            }
        })?;

    callback.move_event_to_context(txn);
    Ok(())
}
</code></pre>
<hr>
<h2>结论：告别竞态，拥抱原子性</h2>
<p>通过本文的分析和实践，我们可以得出以下关键结论：</p>
<ol>
<li><strong>乐观锁是高并发的基石：</strong> 放弃“先查后改”的传统模式，将<strong>版本检查</strong>与<strong>数据修改</strong>集成到一次原子性的 <code>UPDATE</code> 操作中，是避免丢失更新等竞态条件的根本方法。</li>
<li><strong>选择正确的 Upsert 策略：</strong> <strong>“先 Update 再 Insert”</strong> 模式凭借其对乐观锁的天然支持和对更新操作的高效处理，成为处理聚合根持久化的首选。</li>
<li><strong>利用数据库的原子性：</strong> 无论是通过检查 <code>rows_affected</code>，还是依赖主键约束错误来区分更新失败的原因，都是在充分利用数据库底层机制来确保数据一致性。</li>
</ol>
<p>在您的 Rust DDD/CQRS 架构中，将这种原子化逻辑封装进仓储（Repository）层的 <code>save()</code> 方法中，是确保数据完整性和系统高可用性的关键。</p>
]]></description><pubDate>2025-11-18 12:33:11</pubDate></item><item><title>with_err_location：让 Rust 错误处理更智能的过程宏</title><link>https://rustcc.cn/article?id=2650d510-e3ee-4f14-9284-5927ea273e91</link><description><![CDATA[<p>在 Rust 错误处理中，我们经常需要记录错误发生的位置信息以便调试。虽然 <code>snafu</code> 库提供了强大的错误处理能力，但手动为每个错误变体添加位置字段和工厂方法仍然繁琐且容易出错。本文介绍一个自定义的过程宏 <code>#[with_err_location]</code>，它可以自动化这些重复工作，让错误处理更加优雅和高效。</p>
<h2>问题背景</h2>
<p>使用 <code>snafu</code> 进行错误处理时，我们通常需要：</p>
<ol>
<li>为每个错误变体手动添加 <code>location</code> 字段</li>
<li>添加相应的属性（<code>#[snafu(implicit)]</code>、<code>#[serde(skip)]</code>）</li>
<li>为复杂的 source 字段添加 <code>#[snafu(source(false))]</code></li>
<li>手动实现工厂方法来创建错误实例</li>
</ol>
<p>这导致了大量的样板代码：</p>
<pre><code>#[derive(Debug, Serialize, Snafu)]
#[serde(tag = "type")]
pub enum ApiError {
    #[serde(rename = "validate_error")]
    ValidateError {
        message: String,
        #[serde(skip)]
        #[snafu(implicit)]
        location: snafu::Location,
    },
    
    #[serde(rename = "internal_error")]
    InternalError {
        message: String,
        #[serde(skip)]
        #[snafu(source(false))]
        source: Option&lt;Box&lt;dyn std::error::Error + Send + Sync&gt;&gt;,
        #[serde(skip)]
        #[snafu(implicit)]
        location: snafu::Location,
    },
}

impl ApiError {
    #[track_caller]
    pub fn validate_error(message: String) -&gt; Self {
        ApiError::ValidateError {
            message,
            location: GenerateImplicitData::generate(),
        }
    }
    
    #[track_caller]
    pub fn internal_error(message: String) -&gt; Self {
        ApiError::InternalError {
            message,
            source: None,
            location: GenerateImplicitData::generate(),
        }
    }
    
    #[track_caller]
    pub fn internal_error_with_source(message: String, source: Option&lt;Box&lt;dyn std::error::Error + Send + Sync&gt;&gt;) -&gt; Self {
        ApiError::InternalError {
            message,
            source,
            location: GenerateImplicitData::generate(),
        }
    }
}
</code></pre>
<h2>解决方案：<code>#[with_err_location]</code> 宏</h2>
<p><code>#[with_err_location]</code> 宏可以自动化所有这些工作，让您只需要定义核心的错误结构：</p>
<pre><code>#[with_err_location]
#[derive(Debug, Serialize, Snafu)]
#[serde(tag = "type")]
pub enum ApiError {
    #[serde(rename = "validate_error")]
    ValidateError {
        message: String,
    },
    
    #[serde(rename = "internal_error")]
    InternalError {
        message: String,
        source: Option&lt;Box&lt;dyn std::error::Error + Send + Sync&gt;&gt;,
    },
}
</code></pre>
<h2>核心特性</h2>
<h3>1. 自动添加 Location 字段</h3>
<p>宏会为每个枚举变体自动添加 <code>location: snafu::Location</code> 字段，并配置必要的属性：</p>
<ul>
<li><code>#[snafu(implicit)]</code>：让 snafu 自动填充位置信息</li>
<li><code>#[serde(skip)]</code>：在序列化时跳过该字段（默认行为）</li>
</ul>
<h3>2. 智能 Source 字段处理</h3>
<p>宏能识别复杂的 source 字段类型，并自动添加 <code>#[snafu(source(false))]</code> 属性：</p>
<pre><code>// 自动识别并处理
source: Option&lt;Box&lt;dyn std::error::Error + Send + Sync&gt;&gt;
</code></pre>
<h3>3. 自动生成工厂方法</h3>
<p>宏为每个变体生成相应的工厂方法：</p>
<h4>普通变体</h4>
<pre><code>// 生成：
pub fn validate_error(message: String) -&gt; Self { ... }
</code></pre>
<h4>复杂 Source 字段变体</h4>
<p>对于包含 <code>Option&lt;Box&lt;dyn Error + Send + Sync&gt;&gt;</code> 类型的 source 字段，宏会生成两个方法：</p>
<pre><code>// 基础方法（source = None）
pub fn internal_error(message: String) -&gt; Self { ... }

// 带 source 的方法
pub fn internal_error_with_source(message: String, source: Option&lt;Box&lt;dyn std::error::Error + Send + Sync&gt;&gt;) -&gt; Self { ... }
</code></pre>
<h3>4. 灵活的配置选项</h3>
<h4>全局配置</h4>
<pre><code>#[with_err_location(serde = true)]  // 不添加 #[serde(skip)]
#[derive(Debug, Snafu)]
pub enum ApiError { ... }
</code></pre>
<h4>变体级别配置</h4>
<pre><code>#[with_err_location]
#[derive(Debug, Snafu)]
pub enum ApiError {
    #[location(serde = true)]  // 此变体不添加 #[serde(skip)]
    SpecialError {
        message: String,
    },
}
</code></pre>
<h2>实现细节</h2>
<h3>宏的工作流程</h3>
<ol>
<li><strong>解析输入</strong>：解析枚举定义和宏参数</li>
<li><strong>字段分析</strong>：检查每个变体的字段类型和现有属性</li>
<li><strong>添加 Location 字段</strong>：为没有 location 字段的变体添加</li>
<li><strong>属性处理</strong>：添加必要的 snafu 和 serde 属性</li>
<li><strong>工厂方法生成</strong>：基于字段类型生成相应的工厂方法</li>
</ol>
<h3>关键函数</h3>
<h4>字段类型检测</h4>
<pre><code>fn should_add_source_false(field: &amp;syn::Field) -&gt; bool {
    let type_str = field.ty.to_token_stream().to_string();
    let is_option_box_dyn_error = type_str.starts_with("Option &lt; Box &lt; dyn");
    let is_source_field = field.ident.as_ref().map(|name| name == "source").unwrap_or(false);
    is_source_field &amp;&amp; is_option_box_dyn_error
}
</code></pre>
<h4>工厂方法生成</h4>
<pre><code>fn generate_factory_methods(input_enum: &amp;ItemEnum) -&gt; darling::Result&lt;TokenStream&gt; {
    // 检测复杂 source 字段
    let has_complex_source = fields_named.named.iter().any(should_add_source_false);
    
    if has_complex_source {
        // 生成两个方法：基础方法和带 source 的方法
    } else {
        // 生成单个方法
    }
}
</code></pre>
<h2>使用示例</h2>
<h3>基本使用</h3>
<pre><code>#[with_err_location]
#[derive(Debug, Snafu)]
pub enum MyError {
    NetworkError { url: String },
    ValidationError { field: String, message: String },
}

// 使用生成的工厂方法
let error = MyError::network_error("https://api.example.com".to_string());
</code></pre>
<h3>复杂 Source 字段</h3>
<pre><code>#[with_err_location]
#[derive(Debug, Snafu)]
pub enum ComplexError {
    DatabaseError {
        query: String,
        source: Option&lt;Box&lt;dyn std::error::Error + Send + Sync&gt;&gt;,
    },
}

// 两种使用方式
let error1 = ComplexError::database_error("SELECT * FROM users".to_string());
let error2 = ComplexError::database_error_with_source(
    "SELECT * FROM users".to_string(),
    Some(Box::new(io_error))
);
</code></pre>
<h3>配置选项</h3>
<pre><code>#[with_err_location(serde = true)]  // 全局配置
#[derive(Debug, Snafu)]
pub enum ApiError {
    #[location(serde = false)]  // 变体级别覆盖
    InternalError { message: String },
    
    PublicError { message: String },  // 使用全局配置
}
</code></pre>
<h2>完整代码</h2>
<pre><code>#[proc_macro_attribute]
pub fn with_err_location(
    args: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -&gt; proc_macro::TokenStream {
    let args = args.into();
    with_err_location::with_err_location_impl(args, input.into())
        .unwrap_or_else(darling::Error::write_errors)
        .into()
} 
</code></pre>
<pre><code>use darling::{Error, FromMeta, ast::NestedMeta};
use proc_macro2::TokenStream;
use quote::{ToTokens, quote};
use syn::{Attribute, Field, Fields, ItemEnum, Meta, punctuated::Punctuated, token::Comma};

#[derive(Debug, FromMeta, Default)]
struct WithErrLocationArgs {
    pub serde: bool,
}

pub fn with_err_location_impl(
    args: TokenStream,
    input: TokenStream,
) -&gt; darling::Result&lt;TokenStream&gt; {
    let mut input_enum: ItemEnum = match syn::parse2(input) {
        Ok(v) =&gt; v,
        Err(e) =&gt; return Err(Error::from(e)),
    };

    // 解析全局参数
    let global_args = if args.is_empty() {
        WithErrLocationArgs::default()
    } else {
        let attr_args = match NestedMeta::parse_meta_list(args) {
            Ok(v) =&gt; v,
            Err(e) =&gt; return Err(Error::from(e)),
        };
        WithErrLocationArgs::from_list(&amp;attr_args).unwrap_or_default()
    };

    // 遍历枚举的所有变体
    for variant in &amp;mut input_enum.variants {
        // 查找并解析 #[location(...)] 属性
        let (location_config, remaining_attrs) =
            parse_and_remove_location_attrs(&amp;variant.attrs, &amp;global_args)?;

        // 移除 location 属性，保留其他属性
        variant.attrs = remaining_attrs;

        match &amp;mut variant.fields {
            Fields::Named(fields_named) =&gt; {
                // 检查是否已经有 location 字段
                let location_field_index = fields_named.named.iter().position(|field| {
                    field
                        .ident
                        .as_ref()
                        .map(|ident| ident == "location")
                        .unwrap_or(false)
                });
                match location_field_index {
                    Some(index) =&gt; {
                        // 如果已经有 location 字段，确保它至少有 #[snafu(implicit)]
                        let existing_field = &amp;mut fields_named.named[index];
                        ensure_location_field_has_snafu_implicit(existing_field, &amp;location_config);
                    }
                    None =&gt; {
                        // 如果没有 location 字段，则添加一个新的（总是带有 #[snafu(implicit)]）
                        let location_field = create_location_field(&amp;location_config);
                        fields_named.named.push(location_field);
                        fields_named.named.push_punct(Comma::default());
                    }
                }
                // 如果有source 且类型是Option&lt;Box&lt;dyn std::error::Error + Send + Sync&gt;&gt;
                // 需要为其加上#[snafu(source(false))]
                for field in &amp;mut fields_named.named {
                    if should_add_source_false(field) {
                        ensure_source_false_attribute(field);
                    }
                }
            }
            _ =&gt; {
                return Err(Error::unsupported_format(
                    "Only named fields variants are supported",
                ));
            }
        }
    }

    // 生成工厂方法
    let factory_methods = generate_factory_methods(&amp;input_enum)?;

    Ok(quote! {
        #input_enum
        #factory_methods
    })
}

/// 解析并移除 location 属性，返回配置和剩余属性
fn parse_and_remove_location_attrs(
    variant_attrs: &amp;[Attribute],
    global_args: &amp;WithErrLocationArgs,
) -&gt; darling::Result&lt;(LocationConfig, Vec&lt;Attribute&gt;)&gt; {
    let mut config = LocationConfig {
        serde: global_args.serde,
    };

    let mut remaining_attrs = Vec::new();

    for attr in variant_attrs {
        if attr.path().is_ident("location") {
            // 解析 location 属性的参数
            match &amp;attr.meta {
                Meta::List(meta_list) =&gt; {
                    let nested = meta_list.parse_args_with(
                        Punctuated::&lt;NestedMeta, syn::Token![,]&gt;::parse_terminated,
                    )?;
                    let location_args =
                        WithErrLocationArgs::from_list(&amp;nested.into_iter().collect::&lt;Vec&lt;_&gt;&gt;())?;

                    config.serde = location_args.serde;
                }
                _ =&gt; {
                    // 如果没有参数，使用默认配置
                }
            }
        } else {
            // 保留非 location 属性
            remaining_attrs.push(attr.clone());
        }
    }

    Ok((config, remaining_attrs))
}

#[derive(Debug)]
struct LocationConfig {
    serde: bool,
}

/// 确保现有的 location 字段至少有 #[snafu(implicit)] 属性
fn ensure_location_field_has_snafu_implicit(field: &amp;mut Field, config: &amp;LocationConfig) {
    // 根据配置添加或确保有 #[serde(skip)]
    if !config.serde {
        let has_serde_skip = field.attrs.iter().any(|attr| {
            if attr.path().is_ident("serde")
                &amp;&amp; let Meta::List(meta_list) = &amp;attr.meta
            {
                return meta_list.tokens.to_string().contains("skip");
            }
            false
        });

        if !has_serde_skip {
            let serde_skip_attr: Attribute = syn::parse_quote! {
                #[serde(skip)]
            };
            field.attrs.push(serde_skip_attr);
        }
    }
    let has_snafu_implicit = field.attrs.iter().any(|attr| {
        if attr.path().is_ident("snafu")
            &amp;&amp; let Meta::List(meta_list) = &amp;attr.meta
        {
            return meta_list.tokens.to_string().contains("implicit");
        }
        false
    });

    // 如果没有 #[snafu(implicit)]，则添加它
    if !has_snafu_implicit {
        let snafu_implicit_attr: Attribute = syn::parse_quote! {
            #[snafu(implicit)]
        };
        field.attrs.push(snafu_implicit_attr);
    }
}

/// 检查字段是否需要自动添加 #[snafu(source(false))]
fn should_add_source_false(field: &amp;syn::Field) -&gt; bool {
    let type_str = field.ty.to_token_stream().to_string();

    // 检查是否是 Option&lt;Box&lt;dyn std::error::Error + Send + Sync&gt;&gt; 类型
    let is_option_box_dyn_error = type_str.starts_with("Option &lt; Box &lt; dyn");

    // 检查字段名是否为 "source"
    let is_source_field = field
        .ident
        .as_ref()
        .map(|name| name == "source")
        .unwrap_or(false);

    is_source_field &amp;&amp; is_option_box_dyn_error
}

/// 确保复杂 source 字段有 #[snafu(source(false))] 属性
fn ensure_source_false_attribute(field: &amp;mut Field) {
    // 检查是否已经有 #[snafu(source(false))] 属性
    let has_source_false = field.attrs.iter().any(|attr| {
        if attr.path().is_ident("snafu")
            &amp;&amp; let Meta::List(meta_list) = &amp;attr.meta
        {
            let tokens_str = meta_list.tokens.to_string();
            return tokens_str.contains("source")
                &amp;&amp; (tokens_str.contains("false") || tokens_str.contains("( false )"));
        }
        false
    });

    // 如果没有，则添加 #[snafu(source(false))]
    if !has_source_false {
        let source_false_attr: Attribute = syn::parse_quote! {
            #[snafu(source(false))]
        };
        field.attrs.push(source_false_attr);
    }
}

/// 根据配置创建 location 字段
fn create_location_field(config: &amp;LocationConfig) -&gt; Field {
    if !config.serde {
        syn::parse_quote! {
            #[serde(skip)]
            #[snafu(implicit)]
            location: snafu::Location
        }
    } else {
        syn::parse_quote! {
            #[snafu(implicit)]
            location: snafu::Location
        }
    }
}

/// 为枚举生成工厂方法
fn generate_factory_methods(input_enum: &amp;ItemEnum) -&gt; darling::Result&lt;TokenStream&gt; {
    let enum_name = &amp;input_enum.ident;
    let mut methods = Vec::new();

    for variant in &amp;input_enum.variants {
        let variant_name = &amp;variant.ident;

        // 将变体名转换为 snake_case
        let method_name = convert_to_snake_case(&amp;variant_name.to_string());
        let method_ident = syn::Ident::new(&amp;method_name, variant_name.span());

        match &amp;variant.fields {
            Fields::Named(fields_named) =&gt; {
                // 检查是否有复杂的 source 字段
                let has_complex_source = fields_named.named.iter().any(should_add_source_false);

                if has_complex_source {
                    // 生成两个方法：基础方法（source = None）和带 source 的方法

                    // 1. 基础方法：source 为 None
                    let (base_params, base_assignments) =
                        analyze_fields_for_source_method(fields_named, true);
                    let base_method = quote! {
                        #[track_caller]
                        pub fn #method_ident(#(#base_params),*) -&gt; Self {
                            #enum_name::#variant_name {
                                #(#base_assignments,)*
                            }
                        }
                    };
                    methods.push(base_method);

                    // 2. 带 source 的方法
                    let source_method_name = format!("{}_with_source", method_name);
                    let source_method_ident =
                        syn::Ident::new(&amp;source_method_name, variant_name.span());
                    let (source_params, source_assignments) =
                        analyze_fields_for_source_method(fields_named, false);

                    let source_method = quote! {
                        #[track_caller]
                        pub fn #source_method_ident(#(#source_params),*) -&gt; Self
                        {
                            #enum_name::#variant_name {
                                #(#source_assignments,)*
                            }
                        }
                    };
                    methods.push(source_method);
                } else {
                    // 分析字段，确定需要的参数
                    let (params, field_assignments) = analyze_fields(fields_named);

                    // 生成基础方法
                    let method = quote! {
                        #[track_caller]
                        pub fn #method_ident(#(#params),*) -&gt; Self {
                            #enum_name::#variant_name {
                                #(#field_assignments,)*
                            }
                        }
                    };

                    methods.push(method);
                }
            }
            _ =&gt; continue,
        }
    }

    Ok(quote! {
        impl #enum_name {
            #(#methods)*
        }
    })
}

/// 将 PascalCase 转换为 snake_case
fn convert_to_snake_case(s: &amp;str) -&gt; String {
    let mut result = String::new();
    for (i, ch) in s.chars().enumerate() {
        if ch.is_uppercase() &amp;&amp; i &gt; 0 {
            result.push('_');
        }
        result.push(ch.to_lowercase().next().unwrap());
    }
    result
}

/// 分析字段，生成参数和字段赋值
fn analyze_fields(fields: &amp;syn::FieldsNamed) -&gt; (Vec&lt;TokenStream&gt;, Vec&lt;TokenStream&gt;) {
    let mut params = Vec::new();
    let mut assignments = Vec::new();

    for field in &amp;fields.named {
        let field_name = field.ident.as_ref().unwrap();
        let field_type = &amp;field.ty;

        if field_name == "location" {
            assignments.push(quote! { #field_name: snafu::GenerateImplicitData::generate() });
            continue;
        }

        // 普通字段作为参数
        params.push(quote! { #field_name: #field_type });
        assignments.push(quote! { #field_name });
    }

    (params, assignments)
}

/// 分析字段，为带 source 的方法生成参数和字段赋值
fn analyze_fields_for_source_method(
    fields: &amp;syn::FieldsNamed,
    is_base: bool,
) -&gt; (Vec&lt;TokenStream&gt;, Vec&lt;TokenStream&gt;) {
    let mut params = Vec::new();
    let mut assignments = Vec::new();

    for field in &amp;fields.named {
        let field_name = field.ident.as_ref().unwrap();
        let field_type = &amp;field.ty;

        if field_name == "location" {
            assignments.push(quote! { #field_name: snafu::GenerateImplicitData::generate() });
            continue;
        }

        if is_base &amp;&amp; should_add_source_false(field) {
            // 复杂 source 字段设为 None，不作为参数
            assignments.push(quote! { #field_name: None });
        } else {
            // 普通字段作为参数
            params.push(quote! { #field_name: #field_type });
            assignments.push(quote! { #field_name });
        }
    }

    (params, assignments)
}

</code></pre>
<h2>优势总结</h2>
<ol>
<li><strong>减少样板代码</strong>：自动生成重复的字段和方法定义</li>
<li><strong>类型安全</strong>：在编译时确保正确的类型处理</li>
<li><strong>灵活配置</strong>：支持全局和变体级别的配置选项</li>
<li><strong>智能处理</strong>：自动识别复杂类型并生成相应的方法</li>
<li><strong>向后兼容</strong>：可以与现有的 snafu 代码无缝集成</li>
</ol>
<h2>结论</h2>
<p><code>#[with_err_location]</code> 宏通过自动化错误处理中的重复工作，显著提升了开发效率和代码质量。它不仅减少了样板代码，还通过智能的类型检测和方法生成，提供了更加优雅和类型安全的错误处理解决方案。</p>
<p>无论是简单的错误类型还是复杂的带源错误的场景，这个宏都能提供恰到好处的自动化支持，让开发者能够专注于业务逻辑而不是重复的错误处理代码。</p>
]]></description><pubDate>2025-11-18 12:31:08</pubDate></item></channel></rss>