<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>【Rust日报】2026-06-19 Rust PNG crate 再提速：已进入 GNOME 与 Chromium 默认链路</title><link>https://rustcc.cn/article?id=2cbbf67a-17da-45e5-82d4-23639177b837</link><description><![CDATA[<h2>Rust PNG crate 再提速：已进入 GNOME 与 Chromium 默认链路</h2>
<p>Rust 生态里的 <code>png</code> crate（<code>image-png</code>）过去一年不只是继续提速，还真正进入了超大规模生产环境：作者在新博文里确认，它已经自 <strong>Chromium M139</strong> 起成为 Chromium 各平台默认 PNG 实现，同时也随着 <strong>glycin / image-rs</strong> 的推广，进入了越来越多 <strong>GNOME</strong> 应用的默认图像加载路径。</p>
<p>这次更新最值得关注的点有几个：</p>
<ul>
<li>在解码性能上，<code>image-png</code> 依旧保持很强优势。文中基于 2848 张图像语料给出的数据里，Ryzen 9 7950X 上可达 <strong>410.8 MP/s average / 339.9 MP/s geomean</strong>，Apple M4 上可达 <strong>387.7 MP/s average / 310.5 MP/s geomean</strong>，继续领先 libpng、stb_image、spng 等传统 C 实现</li>
<li>Chromium 采用它并不只是因为“Rust 更安全”，还要求它在 <strong>功能、兼容性、正确性、性能</strong> 上都不能掉链子。为满足浏览器场景，项目补齐了 <strong>部分下载时的中间解码状态显示</strong>，并加入了 <strong>mDCV / cLLI</strong> 等新 chunk 支持</li>
<li>GNOME 方向的价值点则更偏向 <strong>内存安全</strong> 与 <strong>APNG 支持</strong>。由于上游 libpng 长期没有内建 APNG，很多 Linux 桌面应用此前并不能默认支持动画 PNG；切到 image-rs / glycin 路线后，这个短板被真正补上</li>
<li>性能提升背后并不神秘，主要来自 <strong>原地 unfilter</strong>、更合理的内部 buffer 尺寸、对 interlacing 的专项优化，以及 <code>simd_adler32</code> / <code>crc32fast</code> 等生态组件对 AVX-512、NEON 的进一步利用</li>
</ul>
<p>这条新闻真正有传播力的地方在于：它再次证明了 <strong>内存安全实现并不必然牺牲底层性能</strong>，而且 Rust 图像基础设施已经不只是“实验室成绩好看”，而是被桌面与浏览器主线真正吃进去了。</p>
<p>文章链接：https://blog.image-rs.org/2026/06/18/png-adoption.html
项目链接：https://github.com/image-rs/image-png/</p>
<p>原文链接：https://blog.image-rs.org/2026/06/18/png-adoption.html</p>
<h2>ClickHouse 谈在 150 万行 C++ 数据库里引入 Rust：不是重写，而是可审计增量接入</h2>
<p>Rust in Production Podcast 新一期找来了 <strong>ClickHouse</strong> 创始人 <strong>Alexey Milovidov</strong> 和长期维护 <code>sqlx</code> 的 <strong>Austin Bonander</strong>，聊的是一个很多团队都关心的问题：当你的主系统已经是 <strong>150 万行、每天跑数千万测试的 C++ 代码库</strong> 时，Rust 究竟该怎么进场？</p>
<p>这期内容最有价值的地方，不在于“Rust 很棒”的空泛表态，而是把大型存量系统引入 Rust 的现实阻力摊开讲了：</p>
<ul>
<li>ClickHouse 并没有把 Rust 当作“一把梭的重写按钮”，而是优先考虑 <strong>把 Rust 组件以可审计、可复现、能满足合规要求</strong> 的方式接入现有 C++ 服务</li>
<li>讨论里反复提到 <strong>Cargo 与 CMake 的边界</strong>。对多语言 monorepo 来说，语言本身往往不是最难的，真正麻烦的是 <strong>构建系统、依赖供应链、FIPS 合规、可重现构建</strong> 这些工程现实</li>
<li>节目里还专门提到 <strong>Corrosion</strong> 这类 CMake ↔ Rust 集成方案，以及 <code>ring</code>、<code>delta-kernel-rs</code>、<code>h3o</code> 等案例，说明 ClickHouse 看重的不是“会不会写 Rust”，而是 Rust 组件能否在已有基础设施里稳定落地</li>
<li>Austin 本身同时维护 <code>sqlx</code>、参与 ClickHouse Rust 工具链，这让节目兼具数据库一线实践与 Rust 工程生态视角，信息密度相当高</li>
</ul>
<p>这条内容的意义在于，它给很多大型基础设施团队提供了一个更现实的范式：<strong>Rust 的进入方式未必要从重写开始，更可能是从局部、受控、可复现的关键路径切入。</strong></p>
<p>节目页面：https://corrode.dev/podcast/s06e06-clickhouse/
ClickHouse 仓库：https://github.com/ClickHouse/ClickHouse
相关 Rust 客户端：https://github.com/ClickHouse/clickhouse-rs</p>
<p>原文链接：https://corrode.dev/podcast/s06e06-clickhouse/</p>
<h2>soa-rs 1.0.0 发布：用 Rust 宏把 Struct of Arrays 做成可落地容器</h2>
<p><code>soa-rs</code> 发布了 <strong>1.0.0</strong>，核心目标很明确：把 Rust 里常见、但经常要手搓样板代码的 <strong>Struct of Arrays（SoA）</strong> 数据布局，做成一套更容易在真实项目里落地的容器与派生宏方案。</p>
<p>这次 1.0.0 虽然不是那种“大而全”的重写版本，但几个更新方向都很实用：</p>
<ul>
<li>新增 <strong><code>SoaClone</code> derive macro</strong>，让 SoA 类型在需要复制时更自然，不必再围着内部字段自己补样板逻辑</li>
<li>改进了 <code>extend</code> 路径里的 <strong>size hint</strong> 使用方式，意味着批量追加数据时可以更稳地减少不必要的分配与搬运</li>
<li>修正了 <code>soa![Foo; n]</code> 在 <strong><code>n &gt; 2</code></strong> 时的行为，让这个宏在更一般化的批量构造场景下按预期工作</li>
<li>从 release note 也能看出，项目已经不只是作者一人玩具，开始有外部贡献者参与 1.0.0 的打磨</li>
</ul>
<p>SoA 这类话题表面上不如“新框架发布”显眼，但它对 <strong>ECS、数值计算、批处理、cache-friendly 数据访问</strong> 都很关键。<code>soa-rs</code> 做到 1.0，意味着 Rust 生态里这块“高性能布局工具”又多了一个更成熟的选择。</p>
<p>发布说明：https://github.com/tim-harding/soa-rs/releases/tag/1.0.0
项目仓库：https://github.com/tim-harding/soa-rs</p>
<p>原文链接：https://github.com/tim-harding/soa-rs/releases/tag/1.0.0</p>
<h2>meon：扁平化、零拷贝、声明式的 Rust 文本解析引擎</h2>
<p><code>meon</code> 是一个很有意思的新项目，作者把它定义成 <strong>flat, fast, declarative parsing engine</strong>：它不走传统“先建 AST，再遍历节点”的路，而是把解析结果直接组织成 <strong>Struct of Arrays 风格的扁平表结构</strong>，让不同元素类型各自存放在独立连续数组里。</p>
<p>项目的设计亮点很鲜明：</p>
<ul>
<li>对 Markdown 或其他文本格式来说，解析结果不是一棵 heterogeneous tree，而是像 <code>headings</code>、<code>links</code>、<code>bolds</code>、<code>bullet_items</code> 这样按类型拆开的连续 <code>Vec</code>，访问某一类元素时更接近顺序扫描，<strong>cache 友好得多</strong></li>
<li>所有元素本质上都是指向原始字节切片的 <strong><code>Span { start, end }</code></strong>，也就是 <strong>零拷贝偏移引用</strong>；真正需要转成字符串时再解引用</li>
<li>项目提供 <code>define_parser!</code> 宏，在编译期生成 parser、content struct 与 <code>find_*</code> 迭代器，强调 <strong>没有运行时解释器、没有 vtable、没有额外分发开销</strong></li>
<li>文档里还给出了 <code>meon-md</code>、benchmarks、fuzzing 等配套内容，说明这不是只停在概念层面的 parser toy，而是在认真往可测、可 benchmark、可扩展的方向走</li>
</ul>
<p>从工程角度看，这个项目最值得关注的不是“又一个 Markdown 解析器”，而是它在尝试回答一个更底层的问题：<strong>如果把 Rust 的数据布局思维和编译期生成能力真正用到文本解析里，能不能绕开传统 AST 模型的性能包袱？</strong></p>
<p>项目仓库：https://github.com/vgnapuga/meon
crates.io：https://crates.io/crates/meon
架构说明：https://github.com/vgnapuga/meon/blob/main/ARCHITECTURE.md</p>
<p>原文链接：https://github.com/vgnapuga/meon</p>
<hr>
<p>From Rust中文社区 Mike</p>
<p>社区学习交流平台订阅：</p>
<ul>
<li><a href="https://rustcc.cn/" rel="noopener noreferrer">Rustcc论坛: 支持rss</a></li>
<li><a href="https://rustcc.cn/article?id=ed7c9379-d681-47cb-9532-0db97d883f62" rel="noopener noreferrer">微信公众号：Rust语言中文社区</a></li>
</ul>
]]></description><pubDate>2026-06-19 01:06:22</pubDate></item><item><title>【Rust日报】2026-06-18 OpenAI 加入 Rust Foundation 白金会员：总计 60 万美元投入 Rust 维护与生态建设</title><link>https://rustcc.cn/article?id=55176907-8ff7-43d4-9d22-e0c696291055</link><description><![CDATA[<h2>OpenAI 加入 Rust Foundation 白金会员：总计 60 万美元投入 Rust 维护与生态建设</h2>
<p>Rust Foundation 宣布 <strong>OpenAI</strong> 成为新的 <strong>Platinum Member</strong>，并将通过基金会总计投入 <strong>60 万美元</strong>，用于支持 Rust 项目维护者、技术优先事项以及更广泛的生态建设。这不是普通的企业站台新闻，而是一次直接落到资金层面的长期支持承诺。</p>
<p>这笔投入覆盖的方向也很清楚：</p>
<ul>
<li>资金会通过 Rust Foundation 与 Rust Project 既有机制分发，包括 <strong>Project Goals</strong>、<strong>Rust Innovation Lab</strong> 和更直接的维护者支持计划</li>
<li>Rust Foundation 强调，<strong>Rust Project 的治理与技术决策仍然保持独立</strong>，企业资金不会改变项目本身的决策结构</li>
<li>OpenAI 方面给出的理由也很直白：Rust 能帮助他们在 <strong>性能、安全性、可靠性</strong> 之间不必做痛苦取舍</li>
<li>OpenAI 的 Predrag Gruevski 还将代表公司进入 Rust Foundation 董事会，而他本身也是 Rust 社区长期活跃成员、<code>cargo-semver-checks</code> 的维护者之一</li>
</ul>
<p>从社区视角看，这条消息真正重要的不是“又一家大公司来了”，而是 <strong>Rust 维护工作正在得到更明确、更结构化的资金支持</strong>，而且支持方愿意把钱投向维护者与基础设施这些最难被看见、却最关键的环节。</p>
<p>文章链接：https://rustfoundation.org/media/rust-foundation-welcomes-openai-as-platinum-member-announces-donation-to-rust-project/
补充说明：https://rustfoundation.org/media/on-openais-support-for-rust/</p>
<p>原文链接：https://rustfoundation.org/media/rust-foundation-welcomes-openai-as-platinum-member-announces-donation-to-rust-project/</p>
<h2>你的 Rust 服务未必真的泄漏：问题可能出在分配器而不是业务代码</h2>
<p>这篇文章讲的是一个非常实用、也非常容易误判的问题：<strong>服务在高并发突发流量后内存冲高不回落，未必是 Rust 代码泄漏，可能只是 allocator 把内存留在 RSS 里了。</strong></p>
<p>作者的排查过程很有参考价值：</p>
<ul>
<li>业务模型是事件驱动 + Tokio 并发任务 + Semaphore 限流，负载呈现 <strong>每小时 3–4 次突发、每次约 10 万事件</strong> 的形态</li>
<li>用 <code>dhat</code> 看堆分配时，程序结束后几乎所有堆内存都被释放了，说明 <strong>逻辑层面并没有明显泄漏</strong></li>
<li>真正的问题出在 <strong>glibc ptmalloc</strong> 的 arena / sub-heap / 缓存回收机制：即使对象已经释放，RSS 也可能因为堆顶 trimming、碎片与缓存块未合并而长时间维持高位</li>
<li>切到 <strong>jemalloc</strong> 后，借助 size-class slab 与后台 purging，内存在流量峰值后更容易回到基线；而作者测试 <strong>MiMalloc</strong> 时，在该类稀疏突发工作负载下并没有得到同样理想的效果</li>
</ul>
<p>文章写得好的地方在于，它没有停留在“换 jemalloc 就行”，而是把 <strong>glibc、jemalloc、MiMalloc 在 Tokio work-stealing + sparse workload 下的行为差异</strong> 解释得相当清楚。对做 Rust 服务、容器部署和性能排查的人来说，这基本属于一篇很值得收藏的实战笔记。</p>
<p>文章链接：https://pranitha.dev/posts/rust-and-memory-allocators/</p>
<p>原文链接：https://pranitha.dev/posts/rust-and-memory-allocators/</p>
<h2>ProcessKit：在 async Rust 里把“孤儿进程”问题做成内核级整树回收</h2>
<p><strong>ProcessKit</strong> 是一个面向 <strong>Tokio</strong> 的 Rust 子进程管理库，核心卖点非常明确：不仅能启动子进程，还要在程序 panic、超时、future 被丢弃时，<strong>把整棵进程树一起收干净</strong>，解决长期困扰自动化工具、测试框架和后台服务的 orphan-process 问题。</p>
<p>它的设计亮点主要在这里：</p>
<ul>
<li>在 Windows 上用 <strong>Job Object</strong>，Linux 上优先用 <strong>cgroup v2</strong>，macOS / BSD 则落到 <strong>POSIX process group</strong>，保证 kill-on-drop 针对的是整棵树而不只是直接子进程</li>
<li>除了回收能力，还打包了 <strong>streaming I/O、shell-free pipelines、supervision、timeouts / retries / cancellation</strong> 等一整套流程控制能力</li>
<li>文档里明确把它和 <code>std::process</code>、<code>tokio::process</code>、<code>duct</code>、<code>command-group</code> 等方案做了对比，突出它的差异点就是 <strong>whole-tree containment</strong></li>
<li>项目已经发布到 crates.io，MSRV 为 <strong>Rust 1.88</strong>，并且把错误语义、测试替身、record/replay 等工程能力也一起补齐了</li>
</ul>
<p>这类项目不一定像新框架那样“显眼”，但对做 CI、编译封装器、集成测试、CLI 编排、开发环境守护进程的人来说，<strong>“掉了 future 但孙进程还活着”</strong> 这种坑非常真实。ProcessKit 把这件事往前推进了一大步。</p>
<p>项目主页：https://zelanton.github.io/processkit/
crates.io：https://crates.io/crates/processkit
文档：https://docs.rs/processkit
代码仓库：https://github.com/ZelAnton/ProcessKit-rs</p>
<p>原文链接：https://zelanton.github.io/processkit/</p>
<h2>Fearless Concurrency on the GPU：把 Rust 所有权模型延伸到 GPU Kernel</h2>
<p>这篇新论文提出了 <strong>cuTile Rust</strong>：一个面向 tile-based GPU kernel 的 Rust 系统，目标是把大家熟悉的 <strong>所有权与别名约束</strong>，真正带进 GPU 内核编写这件事里，而不是一写 CUDA 就重新回到“你自己保证安全”的世界。</p>
<p>论文里最值得关注的点有几个：</p>
<ul>
<li>它把可变输出拆成互不重叠的片段，让 kernel launch 也能维持主机侧的所有权约束</li>
<li>开发者仍然可以在需要时 <strong>局部退出安全抽象</strong>，保留做底层优化的空间</li>
<li>系统不仅管 kernel 代码，还提供了从 <strong>同步启动、异步流水线到 CUDA Graph replay</strong> 的组合式 host 执行模型</li>
<li>性能上并没有为了安全抽象明显掉队：论文给出的数据里，B200 上 element-wise 可到 <strong>7 TB/s</strong>，GEMM 达到 <strong>2 PFlop/s</strong>，约为 cuBLAS 的 <strong>96%</strong></li>
</ul>
<p>更有意思的是，作者还给出了一个基于 cuTile Rust 的推理引擎 <strong>Grout</strong>，用它跑通了 Qwen3 的端到端推理路径。也就是说，这不是只停留在 toy example 的类型系统论文，而是 <strong>在高性能 GPU 场景里认真尝试“安全并发 + Rust + 实际推理工作负载”</strong> 的一套方案。</p>
<p>论文链接：https://arxiv.org/abs/2606.15991</p>
<p>原文链接：https://arxiv.org/abs/2606.15991</p>
]]></description><pubDate>2026-06-18 02:36:17</pubDate></item><item><title>【Rust日报】2026-06-17 zlib-rs 进入 Firefox 151：gzip 压缩/解压正式切到 Rust</title><link>https://rustcc.cn/article?id=7d97fb56-a981-42be-b52b-5bf781d9b6c0</link><description><![CDATA[<h2>zlib-rs 进入 Firefox 151：gzip 压缩/解压正式切到 Rust</h2>
<p>Firefox 从 <strong>151.0.0</strong> 开始，已经把 gzip 压缩/解压实现切换到 <strong>zlib-rs</strong>。这意味着一个核心浏览器组件正式把传统 zlib 路径替换为 Rust 实现，同时获得了更好的安全性与相当可观的性能收益。</p>
<p>这次集成并不只是“换个库”那么简单：</p>
<ul>
<li>Trifecta 团队从 2024 年夏天开始与 Mozilla 合作，前后花了近两年才真正进到生产环境</li>
<li>zlib-rs 虽然提供 drop-in replacement 体验，但在不同压缩级别上采用了更接近 zlib-ng 的算法，因此输出字节与压缩长度会与 stock zlib 略有不同，Firefox 测试也因此需要调整</li>
<li>集成过程中还撞上了 Intel 13/14 代 Raptor Lake CPU 的已知硬件问题：LLVM 生成的某条字节写入指令会偶发触发错误，Firefox 最终通过一段可审计的小型 <code>unsafe</code> 绕过了这个坑</li>
<li>根据文中基准，zlib-rs 在 Linux x86_64 上的一次性解压提升非常夸张，部分场景可达 20x 以上</li>
</ul>
<p>这条新闻的意义不只是“某个 crate 更快了”，而是 <strong>Rust 基础设施正在真实进入超大规模终端软件的底层路径</strong>。</p>
<p>文章链接：https://trifectatech.org/blog/zlib-rs-in-firefox/</p>
<p>原文链接：https://old.reddit.com/r/rust/comments/1u7cgen/zlibrs_in_firefox_trifecta_tech_foundation/</p>
<h2>just 1.53.0：十周年版本带来列表类型与并行依赖映射</h2>
<p>命令运行器 <strong>just</strong> 迎来了一个很适合纪念节点的版本：作者回顾自己十年前提交的第一条 commit，同时发布了 <strong>just 1.53.0</strong>，核心新特性是 <strong>lists（列表）</strong>。</p>
<p>这次更新的关键点很清楚：</p>
<ul>
<li>just 过去基本把所有值都当作字符串处理；现在改成了“所有值都是字符串列表”，原本的字符串可视作长度为 1 的列表</li>
<li>这一变化让不少过去难以优雅表达的能力终于落地，比如变参的正确引用、把变参批量映射成依赖、以及并行展开任务</li>
<li>作者给出的例子里，<code>[parallel] build *args: *(compile *args)</code> 可以把参数列表映射成并行构建依赖</li>
<li>作者还提到 just 从早期开始就保持了比较完整的单元测试与集成测试，目前累计已有 <strong>2076</strong> 个测试</li>
</ul>
<p>从传播角度看，这不是那种“新做了一个小工具”的帖子，而是 <strong>Rust 生态里成熟生产力工具的一次类型系统升级</strong>。</p>
<p>发布说明：https://github.com/casey/just/releases/tag/1.53.0</p>
<p>原文链接：https://old.reddit.com/r/rust/comments/1u7qtfr/happy_ten_years_of_just_and_lists/</p>
<h2>xxutf-rs：把 Unicode 规范化与大小写折叠推到 GB/s 级别</h2>
<p>作者发布了 <strong>xxutf-rs</strong>，这是其底层 C 库 <strong>xxUTF</strong> 的 Rust 绑定，目标非常直接：把 <strong>Unicode normalization</strong> 与 <strong>case folding</strong> 这些通常不太“性感”的文本处理基础能力，做出接近 <strong>GB/s 级别</strong> 的吞吐。</p>
<p>项目当前状态与亮点包括：</p>
<ul>
<li>crate 仍处在 alpha 阶段，但作者表示测试覆盖已经足以支撑对外试用</li>
<li>主要提供 NFC / NFD 等规范化路径，以及大小写折叠能力</li>
<li>API 设计明确不想简单复刻 ICU4X 或 <code>unicode-normalization</code>，而是探索更偏性能导向的接口形态</li>
<li>作者还专门写了一篇博客解释 Unicode 规范化本身是什么、为什么值得关心</li>
</ul>
<p>这类项目很容易被忽视，但只要你做搜索、索引、排序、标识符匹配、跨语言文本处理，<strong>Unicode 基础设施的性能与正确性都会直接影响上层产品体验</strong>。</p>
<p>项目链接：https://github.com/dzfrias/xxutf-rs
博客说明：https://dzfrias.dev/blog/xxutf/#unicode-normalization</p>
<p>原文链接：https://old.reddit.com/r/rust/comments/1u7r8m7/unicode_normalization_at_gbs/</p>
<h2>Fearless Embedded Rust：用 Pico W 驱动乐高小车的 no_std 异步实战</h2>
<p>这篇文章展示了一个很有“Rust 教学味”的项目：用 <strong>Raspberry Pico W</strong>、<strong>Embassy</strong>、<strong>no_std async Rust</strong> 和乐高动力组件，做一辆可以通过 Wi‑Fi/TCP 从电脑远程控制的无线小车。</p>
<p>它有意思的地方不只是“能跑起来”，而是作者把很多嵌入式 Rust 的典型约束都压进了一个相对直观的项目里：</p>
<ul>
<li>运行环境是 <code>no_std</code>，没有分配器，很多缓冲区都要静态规划</li>
<li>使用 Embassy 处理 async 执行、网络、USB 日志等能力，让并发模型保持清晰</li>
<li>不是用常见 hobby 项目那种高层 Python 路线，而是直接做更贴近底层的 Rust 实现</li>
<li>小车通过 Wi‑Fi 接收控制命令，仍然保持全无线，且尽量复用乐高现成供电与电机体系</li>
</ul>
<p>如果说很多 embedded Rust 内容还停留在点灯、串口、温湿度传感器，这篇算是把 <strong>网络、驱动、供电、并发、调试链路</strong> 都揉进了一个更完整的实战案例里。</p>
<p>文章链接：https://dystroy.org/blog/picomobile/</p>
<p>原文链接：https://old.reddit.com/r/rust/comments/1u7jiir/fearless_embedded_rust_driving_a_lego_car_with_a/</p>
<hr>
<p>From Rust中文社区 Mike</p>
<p>社区学习交流平台订阅：</p>
<ul>
<li><a href="https://rustcc.cn/" rel="noopener noreferrer">Rustcc论坛: 支持rss</a></li>
<li><a href="https://rustcc.cn/article?id=ed7c9379-d681-47cb-9532-0db97d883f62" rel="noopener noreferrer">微信公众号：Rust语言中文社区</a></li>
</ul>
]]></description><pubDate>2026-06-17 01:08:03</pubDate></item><item><title>受够了 Postman 的臃肿，我用 Rust 和 GPUI 写了一个超快的高性能 API 测试工具</title><link>https://rustcc.cn/article?id=8109534f-50da-4b4f-9f99-8e30afe50361</link><description><![CDATA[<p>大家好！</p>
<p>作为一个长期与 API 打交道的开发者，我发现市面上主流的 API 测试工具（如 Postman、Insomnia 等）大多基于 Electron 或 Web
技术构建。随着时间推移，它们变得越来越臃肿，启动慢、占用内存大，有时甚至让人觉得它们比我要调试的服务还要卡顿。</p>
<p>出于对“极致原生体验”的追求，我决定自己动手，使用 Rust 和 Zed 编辑器开源的底层渲染框架 GPUI，开发了这款高性能的桌面 API 调试工具 —— HiPoster。</p>
<p>✨ 核心亮点：</p>
<ul>
<li>🚀 极其轻快： 纯 Rust 编写，得益于 GPUI 的 GPU 硬件加速，启动秒开，操作丝滑，彻底告别 Electron 的卡顿感。</li>
<li>💻 跨平台原生体验： 支持 macOS（原生 Universal 芯片级支持）、Windows 和 Linux。</li>
<li>🎨 颜值在线： 内置多种精美开发者主题（GitHub Light, Solarized, Monokai, Catppuccin 等），代码高亮赏心悦目。</li>
<li>🛠 完整协议支持： 轻松处理各种复杂的 Headers、Query Params，支持 Raw JSON、FormData、UrlEncoded 等多种 Body 类型，内置 Bearer/Basic 鉴权。</li>
<li>📦 开箱即用： 历史记录自动保存，支持数据本地化持久存储。</li>
</ul>
<p>📖 附带了一本开发指南：
在开发过程中，我踩了不少 GPUI 框架的坑。为了帮助想用 GPUI 开发桌面应用的同学，我把项目的架构、脏标记渲染优化、异步网络处理等实战经验写成了一本开源的《GPUI 实战进阶指南》（使用 mdBook 生成），代码仓库里可以直接看。</p>
<p>🔗 项目地址： https://github.com/wandercn/hiposter (https://github.com/wandercn/hiposter)
(如果觉得有意思，欢迎给个 Star ⭐️ 支持一下！)</p>
<p>大家在日常调试 API 时有什么特别的痛点吗？欢迎在评论区交流，我很乐意在后续版本中加入大家需要的功能！</p>
]]></description><pubDate>2026-06-16 08:47:57</pubDate></item><item><title>EasyDB v2.11.0：把「会写 SQL」变成你处理一切数据的超能力</title><link>https://rustcc.cn/article?id=fae4435e-e3a1-4e09-bcd6-5f8bfbdfced6</link><description><![CDATA[<h1>EasyDB v2.11.0：把「会写 SQL」变成你处理一切数据的超能力</h1>
<blockquote>
<p>不用导入数据库，不用写脚本，不用装一堆工具。一份 CSV、一个 Excel、一张 MySQL 表，用一条标准 SQL 就能查、能联、能导出。打开即用，全程离线。</p>
</blockquote>
<p>先放结论：如果你经常要从各种文件和数据库里捞数据、对数、做清洗，EasyDB 大概率能帮你省下大量「为了查几行数据而折腾环境」的时间。这篇文章会先讲它能帮你做什么，再讲 v2.11.0 这次更新带来了什么。</p>
<hr>
<h2>一、它到底能帮你解决什么问题？</h2>
<p>我们每天都在和数据打交道，但很多场景其实卡在「工具太重」上：</p>
<ul>
<li>想查一份几百 MB 的 CSV 里满足条件的行 → Excel 打不开，命令行 <code>grep</code> 又写不出复杂条件</li>
<li>想把一个 Excel 报表和数据库里的表对一下账 → 得先把 Excel 导入数据库，建表、定字段类型、写导入脚本……</li>
<li>想把清洗后的数据生成 INSERT 语句灌进另一个库 → 手写 SQL 或者临时写个 Python 脚本</li>
<li>想用窗口函数、子查询分析本地文件 → 文件不是数据库，根本没法直接跑 SQL</li>
</ul>
<p><strong>EasyDB 的思路很简单：把文件直接当成数据库表，让你用最熟悉的 SQL 去操作一切数据源。</strong> 你已经会的 SQL 技能，在这里能用到 CSV、Excel、JSON、Parquet 上，甚至和 MySQL、PostgreSQL 混在一条语句里查。</p>
<pre><code>-- Excel 报表 × PostgreSQL 库存表，一次 JOIN 对完账
SELECT t1."product_name", t2."inventory_count"
FROM read_excel('/data/products.xlsx') AS t1
INNER JOIN
read_postgres('inventory', host =&gt; 'localhost', username =&gt; 'postgres', db =&gt; 'mydb') AS t2
ON t1."product_id" = t2."id"
WHERE t2."inventory_count" &lt; 100;
</code></pre>
<p>没有建表，没有导入，没有 ETL。把文件路径写进 SQL，回车，结果就出来了。</p>
<hr>
<h2>二、它适合谁？几个真实场景</h2>
<p><strong>数据分析师 / 运营</strong>：拿到一份几十万行的 Excel/CSV，只想筛出符合条件的几行、做个分组统计。拖进 EasyDB，写一句 SQL，<code>⌘Enter</code> 就出结果——比在 Excel 里拉筛选、写公式快得多，文件再大也不卡。</p>
<pre><code>-- 按部门算平均薪资（窗口函数）
SELECT "name", "department", "salary",
       AVG("salary") OVER (PARTITION BY "department") AS dept_avg
FROM read_csv('/data/employees.csv');
</code></pre>
<p><strong>DBA / 后端工程师</strong>：要把一批文件数据导入数据库，或者把一张表的数据迁到另一个库。用 EasyDB 查出来，直接导出成 INSERT / UPDATE 语句（可选 MySQL / PostgreSQL 方言），还能重命名字段、移除多余列、指定每列的目标类型，<strong>生成的 SQL 拿来就能执行</strong>。</p>
<p><strong>需要跨源核对数据的人</strong>：本地文件和线上数据库的数据不一致？不用来回导出导入，一条 JOIN 直接在同一条 SQL 里比对。</p>
<p><strong>任何临时要查数据、又不想搭环境的人</strong>：日志文件、导出的报表、第三方给的数据包……拖进来就能查。完全离线，数据不出本机，敏感数据也放心。</p>
<pre><code>-- 在日志文件里用正则筛出错误行（整行作为一列 column_1）
SELECT *
FROM read_text('/data/app.log', has_header =&gt; false)
WHERE REGEXP_LIKE("column_1", '^ERROR:\s+\d{3}');
</code></pre>
<hr>
<h2>三、为什么是 EasyDB，而不是别的？</h2>
<ul>
<li><strong>真·开箱即用</strong>：原生桌面应用（macOS / Windows），下载安装就能用，不需要 CLI、不需要写代码、不需要联网加载资源</li>
<li><strong>够快、够省</strong>：基于 Rust + Apache DataFusion，几百 MB 到数 GB 的文件也能稳定处理，硬件要求不高</li>
<li><strong>SQL 足够完整</strong>：多表 JOIN、子查询、窗口函数、正则匹配……DataFusion 提供的是完整 SQL，不是阉割版</li>
<li><strong>拖拽即生成 SQL</strong>：文件拖进编辑器自动生成查询语句，配合语法高亮、智能补全（函数名 + 列名）、格式化，写起来很顺</li>
<li><strong>数据安全</strong>：纯本地运行，全程离线，数据不上传任何服务器</li>
</ul>
<p>经常有人问：「这不就是 DuckDB 吗？」两者定位不同，各有长短，这里客观对比一下：</p>
<table>
<thead>
<tr>
<th>维度</th>
<th>EasyDB</th>
<th>DuckDB</th>
</tr>
</thead>
<tbody>
<tr>
<td>使用方式</td>
<td>原生桌面 GUI，拖文件即查</td>
<td>CLI / 编程 API，可嵌入程序</td>
</tr>
<tr>
<td>上手门槛</td>
<td>会写 SQL 就行</td>
<td>需了解其语法与 API</td>
</tr>
<tr>
<td>查询性能</td>
<td>够用，常规分析流畅</td>
<td>更强，向量化引擎在大规模 OLAP 上领先</td>
</tr>
<tr>
<td>生态扩展</td>
<td>内置数据源，开箱即用</td>
<td>扩展丰富、社区活跃、可编程性强</td>
</tr>
<tr>
<td>SQL 导出</td>
<td>内置 INSERT/UPDATE，可选方言</td>
<td>需自行编程实现</td>
</tr>
<tr>
<td>集成能力</td>
<td>独立桌面应用，不便嵌入其他程序</td>
<td>可作为库嵌入 Python/Java 等，便于自动化</td>
</tr>
</tbody>
</table>
<p><strong>DuckDB 的优势</strong>：自研向量化引擎性能更强，尤其在大规模分析查询上；生态成熟、扩展多，能作为库嵌入程序，适合做数据管道和自动化。<strong>它的代价</strong>是更偏开发者——没有原生 GUI，需要命令行或写代码。</p>
<p><strong>EasyDB 的优势</strong>：原生桌面应用、开箱即用、拖拽生成 SQL、内置 SQL 导出，会写 SQL 就能上手，适合临时查数、对账、做清洗。<strong>它的不足</strong>也很明显——查询性能和生态扩展性不及 DuckDB，也不适合嵌入到其他程序里做自动化。</p>
<p>一句话：要把分析能力嵌进程序、追求极致性能，选 DuckDB；想拖个文件写句 SQL 马上拿结果，选 EasyDB。</p>
<hr>
<h2>四、v2.11.0 这次更新了什么</h2>
<p>距离上次在社区分享 v2.8.0，EasyDB 连续迭代了 v2.9、v2.10、v2.11，这里一次性讲清楚。</p>
<h3><code>read_json()</code> 回归，标准 JSON 数组终于支持</h3>
<p>早期切换查询引擎时 <code>read_json()</code> 曾被临时移除，只能逐行读 NDJSON。但很多人手上的 JSON 其实是<strong>标准数组格式</strong>（<code>[{...}, {...}]</code>）。这次它正式回归，而且更聪明：</p>
<ul>
<li><strong>自动识别格式</strong>——按文件内容嗅探，<code>[</code> 开头当标准 JSON 数组，<code>{</code> 开头当 NDJSON，嗅探不出再回退扩展名</li>
<li><code>.json</code> 与 <code>.ndjson</code> 都能直接用，编辑器补全和拖拽模板统一改用 <code>read_json()</code></li>
<li>修复了带 UTF-8 BOM 的 JSON 数组被误判为 NDJSON 的问题</li>
</ul>
<pre><code>SELECT * FROM read_json('/data/records.json') WHERE "status" = 'active';
</code></pre>
<h3>导出 SQL 一键复制到剪贴板</h3>
<p>SQL 导出对话框新增**「复制 SQL 语句」<strong>，生成的 INSERT / UPDATE 不必再落地成文件，直接复制贴进数据库客户端就能跑。导出 / 复制全流程都加了</strong>进行中 / 成功 / 失败**状态提示；复制超过 10,000 行会自动截断并给出警告。</p>
<h3>保存常用查询，告别重复粘贴（v2.10）</h3>
<ul>
<li>常用 SQL 可<strong>命名保存</strong>，在左侧栏随时加载，支持搜索筛选与删除</li>
<li>查询历史增强：关键词搜索、显示条数配置（50 / 100 / 200 / 500 / 全部）、按时间清理（7 / 30 / 90 天前或全部清空）</li>
<li>执行后自动切到结果页签，底部显示本次查询耗时</li>
</ul>
<h3>表头直接显示列类型（v2.9）</h3>
<p>查询结果表头会展示每列的 Arrow 类型，一眼看清数据结构；SQL 导出的列类型配置也直接复用查询结果的类型信息，少一次后端请求。</p>
<h3>性能与稳定性</h3>
<ul>
<li>SQL 生成改用 <strong>RecordBatch 批处理</strong>，显著降低导出内存占用</li>
<li><strong>Apache DataFusion 升级至 53.1.0</strong></li>
<li>优化 Excel 日期时间解析，修复序列号被误当日期字符串、空格分隔日期时间等问题</li>
<li>修复注册远程文件路径（HTTP / S3 等 URL）时误报「文件不存在」的问题</li>
</ul>
<hr>
<h2>数据源一览</h2>
<table>
<thead>
<tr>
<th>格式</th>
<th>函数</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>CSV</td>
<td><code>read_csv()</code></td>
<td>自定义分隔符、表头、Schema 推断</td>
</tr>
<tr>
<td>TSV</td>
<td><code>read_tsv()</code></td>
<td>Tab 分隔文件</td>
</tr>
<tr>
<td>Text</td>
<td><code>read_text()</code></td>
<td>通用文本文件，自定义分隔符</td>
</tr>
<tr>
<td>Excel</td>
<td><code>read_excel()</code> / <code>read_xlsx()</code></td>
<td><code>.xlsx</code> 支持，可选工作表</td>
</tr>
<tr>
<td>JSON</td>
<td><code>read_json()</code></td>
<td>标准 JSON 数组与 NDJSON，自动检测</td>
</tr>
<tr>
<td>NdJson</td>
<td><code>read_ndjson()</code></td>
<td>每行一个 JSON 对象</td>
</tr>
<tr>
<td>Parquet</td>
<td><code>read_parquet()</code></td>
<td>列式存储格式</td>
</tr>
<tr>
<td>MySQL</td>
<td><code>read_mysql()</code></td>
<td>直连 MySQL 数据库表</td>
</tr>
<tr>
<td>PostgreSQL</td>
<td><code>read_postgres()</code></td>
<td>直连 PostgreSQL 数据库表</td>
</tr>
</tbody>
</table>
<hr>
<h2>技术栈</h2>
<table>
<thead>
<tr>
<th>层级</th>
<th>技术</th>
</tr>
</thead>
<tbody>
<tr>
<td>前端</td>
<td>React 18 + TypeScript + Vite</td>
</tr>
<tr>
<td>后端</td>
<td>Rust + Tauri v2</td>
</tr>
<tr>
<td>查询引擎</td>
<td>Apache DataFusion 53.1.0</td>
</tr>
<tr>
<td>UI 框架</td>
<td>HeroUI + Tailwind CSS</td>
</tr>
<tr>
<td>SQL 编辑器</td>
<td>Ace Editor (react-ace)</td>
</tr>
<tr>
<td>SQL 解析</td>
<td>sqlparser-rs (Rust) + node-sql-parser (JS)</td>
</tr>
<tr>
<td>历史存储</td>
<td>SQLite (rusqlite)</td>
</tr>
</tbody>
</table>
<hr>
<h2>快速开始</h2>
<ol>
<li>访问 <a href="https://github.com/shencangsheng/easydb_app/releases" rel="noopener noreferrer">GitHub Releases</a> 下载安装包</li>
<li><strong>macOS</strong>：下载 <code>.dmg</code>，拖拽到应用程序文件夹</li>
<li><strong>Windows</strong>：下载 <code>.exe</code>，运行安装程序</li>
<li>拖拽任意文件到编辑器自动生成 SQL，按 <code>⌘Enter</code> 执行</li>
</ol>
<blockquote>
<p>macOS 若提示「应用已损坏」，在终端执行：
<code>xattr -r -d com.apple.quarantine /Applications/EasyDB.app</code></p>
</blockquote>
<hr>
<p><strong>GitHub</strong>：<a href="https://github.com/shencangsheng/easydb_app" rel="noopener noreferrer">https://github.com/shencangsheng/easydb_app</a></p>
<p>EasyDB 是我利用业余时间维护的开源项目，如果它能帮你少折腾一点、多省一点时间，欢迎点个 Star，也欢迎到 <a href="https://github.com/shencangsheng/easydb_app/issues" rel="noopener noreferrer">Issues</a> 提想法和反馈——很多功能就是这么一点点被大家提出来、加进去的。</p>
]]></description><pubDate>2026-06-16 07:13:13</pubDate></item><item><title>【Rust日报】2026-06-16 QDRV 1.0：开源浮点 HDR 视频格式与工具链，纯 Rust 实现</title><link>https://rustcc.cn/article?id=44a8bb8e-2d6d-467c-8343-84b63b69b03c</link><description><![CDATA[<h2>QDRV 1.0：开源浮点 HDR 视频格式与工具链，纯 Rust 实现</h2>
<p>作者发布了 <strong>QDRV 1.0</strong>（Quantum Dynamic Range Video）首个公开版本，目标是为 HDR 工作流提供一套更开放、可审计的浮点视频格式与工具链，用来替代过早量化到整数域、并依赖专有元数据生态的传统 HDR 管线。</p>
<p>项目核心特点：</p>
<ul>
<li>纯 Rust workspace，共 9 个 crate；代码树里只有 2 处 <code>unsafe</code>，都位于 ZFP FFI 边界</li>
<li>采用双层模型：<code>qdrv64</code> 负责 Float64 线性光母版，<code>qdrv32</code> 负责 Float32 PQ/HLG 交付</li>
<li>交付层基于标准 AV1，可与 ffmpeg、dav1d、MP4Box、Shaka Packager 等现有工具互通</li>
<li>每帧元数据以内嵌 ITU-T T.35 OBU 形式携带，跨 MP4 / CMAF / IVF / OBU 封装仍可保留</li>
<li>还支持 HMAC 与 FIPS 204 ML-DSA 后量子签名、每帧 Merkle inclusion proof，以及实验性的浏览器端解码路径</li>
</ul>
<p>项目主页：https://github.com/qdrv/QDRV</p>
<p>原文链接：https://old.reddit.com/r/rust/comments/1u6yh7p/qdrv_10_an_open_floatingpoint_hdr_video_format/</p>
<h2>reliakit 1.0：零依赖、no_std 友好的 Rust 可靠性工具箱</h2>
<p>作者发布 <strong>reliakit 1.0</strong>，这是一个由多个小型 crate 组成的 Rust 可靠性工具箱，主打“在边界处验证数据，把可信约束带进程序内部”，并尽量避免引入运行时耦合与额外依赖树。</p>
<p>包含的能力很完整：</p>
<ul>
<li>验证型基础类型：<code>Port</code>、<code>Email</code>、<code>HttpUrl</code>、<code>BoundedStr</code>、<code>Percent</code> 等</li>
<li>Secret redaction：<code>Secret&lt;T&gt;</code> / <code>SecretString</code> 在日志和调试输出中自动脱敏</li>
<li>有界集合、严格 JSON / CSV、规范化二进制编码</li>
<li>运行时无关的 resilience 组件：retry/backoff、circuit breaker、rate limiter、bulkhead、timeout</li>
<li>整个项目坚持零第三方依赖、<code>#![forbid(unsafe_code)]</code>、核心 crate 面向 <code>no_std</code></li>
</ul>
<p>仓库地址：https://github.com/satyakwok/reliakit</p>
<p>原文链接：https://old.reddit.com/r/rust/comments/1u6p61k/reliakit_10_small_zerodependency_no_std/</p>
<h2>Typst 0.15：变量字体、MathML 与多文件导出一起到来</h2>
<p>Typst 发布 <strong>0.15</strong>，这是迄今为止体量最大的一次更新之一。新版本一口气把变量字体、MathML 支持、bundle/multi-file 导出、多 bibliography、多个 PDF 标准并行支持等能力推进到了更可用的状态。</p>
<p>这次更新的亮点包括：</p>
<ul>
<li>变量字体支持：可直接按 weight / stretch / optical size 等轴控制字体变体</li>
<li>HTML 导出补上 MathML，公式终于不再只能退化成图片或 SVG</li>
<li>新的 bundle export 能一次导出多个 HTML / PDF / SVG / PNG 文件，适合文档站点与多页面内容</li>
<li>原生支持多个 bibliography，适合按章节或按主题拆分参考文献</li>
<li>官方文档已迁移到 Typst 本身，并首次提供完整迁移指南与可打印版本</li>
</ul>
<p>文章链接：https://typst.app/blog/2026/typst-0.15</p>
<p>原文链接：https://old.reddit.com/r/rust/comments/1u6n46u/typst_typst_015_contains_multitudes_typst_blog/</p>
<h2>termem：按目录索引多种 coding agent 会话的 Rust CLI</h2>
<p>作者做了一个名为 <strong>termem</strong> 的 Rust CLI，想解决多种 coding agent 会话彼此割裂、跨工具接续困难的问题。它会把 Claude Code、Codex、Gemini、opencode、shell 等会话按目录索引进 SQLite，重点不是“再调一个模型”，而是给终端工作流加上一层共享记忆与恢复能力。</p>
<p>项目目前主打这些能力：</p>
<ul>
<li>按目录与子目录组织会话，方便在当前项目上下文里找回历史工作</li>
<li>可按消息内容、标题、路径做搜索，而不只是按 session 名称翻</li>
<li>支持恢复准确的历史会话，并回到正确工具和目录继续工作</li>
<li>零网络请求、零模型调用，只负责索引、检索与恢复</li>
</ul>
<p>项目主页：https://termem.com/</p>
<p>原文链接：https://old.reddit.com/r/rust/comments/1u6sp8i/termem_a_rust_cli_that_indexes_codingagent/</p>
<hr>
<p>From Rust中文社区 Mike</p>
<p>社区学习交流平台订阅：</p>
<ul>
<li><a href="https://rustcc.cn/" rel="noopener noreferrer">Rustcc论坛: 支持rss</a></li>
<li><a href="https://rustcc.cn/article?id=ed7c9379-d681-47cb-9532-0db97d883f62" rel="noopener noreferrer">微信公众号：Rust语言中文社区</a></li>
</ul>
]]></description><pubDate>2026-06-16 01:06:55</pubDate></item><item><title>FlowDB 介绍：一个纯 Rust 的嵌入式 LSM 引擎与IndexedDB  JSON 文档数据库</title><link>https://rustcc.cn/article?id=fa5ced63-de04-49e4-b7c0-297f00740cbc</link><description><![CDATA[<h1>FlowDB 介绍：一个纯 Rust 的嵌入式 LSM 引擎与 JSON 文档数据库</h1>
<h2>产品能力：一个引擎，两种用法</h2>
<p>FlowDB 提供两层 API：</p>
<h3>底层：LSM-Tree Key-Value 引擎</h3>
<pre><code>use flowdb::{Engine, Config};

let engine = Engine::open(Config::new("/tmp/mydb"))?;
engine.put(b"key", b"value")?;
let val = engine.get(b"key")?;         // Option&lt;Vec&lt;u8&gt;&gt;
engine.delete(b"key")?;
</code></pre>
<p>支持前缀扫描、范围扫描、批量写入、迭代器。这是一个完整的 LSM 引擎，但功能定位很克制——不像 RocksDB 那样有数百个配置项。</p>
<h3>上层：JsonDB 文档层</h3>
<p>JsonDB 是构建在 LSM 引擎之上的 JSON 文档数据库，兼容 <strong>IndexedDB API 范式</strong>（object store + index）：</p>
<pre><code>use flowdb::{Engine, Config, JsonDB, Transaction, QueryBuilder};
use serde_json::json;

let engine = Engine::open(Config::new("/tmp/myjsondb"))?;
let jsondb = JsonDB::open(engine)?;

// 创建 store 和索引
let store = jsondb.create_object_store("users", "id")?;
jsondb.create_index("users", "email", true)?;  // unique index

// 写入文档
jsondb.put("users", json!({"id": "u1", "name": "Alice", "email": "alice@x.com", "age": 30}))?;

// 按主键查询
let doc = jsondb.get::&lt;Value&gt;("users", "u1")?;

// 按索引查询
let doc = jsondb.get_by_index::&lt;Value&gt;("users", "email", "alice@x.com")?;

// 范围扫描 + 排序
let results = jsondb.range_by_index::&lt;Value&gt;(
    "users", "age", 20..=40, SortDir::Asc, None
)?;

// 声明式查询 (自动选索引)
let results = QueryBuilder::new("users")
    .where_eq("email", "bob@x.com")
    .collect(&amp;jsondb)?;
</code></pre>
<p><strong>核心特性：</strong></p>
<ul>
<li>多索引支持（唯一/非唯一，单字段/复合）</li>
<li>嵌套字段查询（<code>"address.city"</code> 点号路径）</li>
<li>事务支持（OCC + MVCC，批量原子提交，自动回滚）</li>
<li>自动递增主键</li>
<li>范围扫描、分页、排序</li>
</ul>
<h3>与 RocksDB 的对比</h3>
<p>M 系列芯片, 100K records, 128B value:</p>
<table>
<thead>
<tr>
<th>操作</th>
<th>FlowDB</th>
<th>RocksDB</th>
<th>倍数</th>
</tr>
</thead>
<tbody>
<tr>
<td>顺序写</td>
<td>4.5M ops/s</td>
<td>3.1M ops/s</td>
<td><strong>1.42x</strong></td>
</tr>
<tr>
<td>8线程并发写</td>
<td>9.4M ops/s</td>
<td>4.7M ops/s</td>
<td><strong>2.02x</strong></td>
</tr>
<tr>
<td>点查询</td>
<td>6.0M ops/s</td>
<td>549K ops/s</td>
<td><strong>10.95x</strong></td>
</tr>
<tr>
<td>前缀扫描 ~200条</td>
<td>72K ops/s</td>
<td>11K ops/s</td>
<td><strong>6.39x</strong></td>
</tr>
<tr>
<td>全表扫描 200K条</td>
<td>65 ops/s</td>
<td>40 ops/s</td>
<td><strong>1.63x</strong></td>
</tr>
</tbody>
</table>
<h3>JsonDB 文档层性能</h3>
<table>
<thead>
<tr>
<th>操作</th>
<th>吞吐</th>
</tr>
</thead>
<tbody>
<tr>
<td>单文档写入</td>
<td>~121 docs/s</td>
</tr>
<tr>
<td>批量写入 (100/批)</td>
<td>~7,057 docs/s</td>
</tr>
<tr>
<td>主键点查</td>
<td>~244,741 ops/s</td>
</tr>
<tr>
<td>索引等值查找</td>
<td>~9,402 queries/s</td>
</tr>
</tbody>
</table>
<p>（写入瓶颈在 fsync，可用 SyncMode::IntervalMs 或批量提升）</p>
<h3>为什么点查比 RocksDB 快 11 倍？</h3>
<ol>
<li><strong>Vec memtable</strong> — 读路径上活跃数据无 BTreeMap 遍历开销</li>
<li><strong>Key 级 bloom</strong> — 命中率远高于 (key, ts) 级</li>
<li><strong>无 CGo/CXX 跨语言开销</strong> — 纯 Rust 调用，无需 FFI</li>
<li><strong>配置极简</strong> — 不做 RocksDB 那种上百项调优，默认就快</li>
<li><strong>BlockMetaIndex 二维索引</strong> — 按 key 和按时间双索引加速</li>
</ol>
<p><strong>GitHub:</strong> https://github.com/restsend/flowdb
<strong>crates.io:</strong> <code>flowdb</code></p>
<p>欢迎 issue / PR / 讨论。</p>
]]></description><pubDate>2026-06-15 06:02:29</pubDate></item><item><title>[分享] 使用 Rust + TeaQL 打造的极速、极简应用：World Cup 2026 交互式 CLI 🏆</title><link>https://rustcc.cn/article?id=af61efb6-39e1-4c8c-925c-f717625b3073</link><description><![CDATA[<p>大家好！今天想和大家分享一个最近开源的有趣项目——World Cup 2026 - Rust Edition。</p>
<p>这是一个高性能、交互式的命令行（CLI）应用程序，用来查看 2026 年 FIFA 世界杯的分组和积分榜情况。</p>
<h4>🌟 项目背景与亮点</h4>
<p>这个项目最初是用 Java 实现的，现在我们使用 Rust + TeaQL 数据引擎 + SQLite 进行了完全的重写。得益于 Rust
的特性，这个版本实现了零冷启动开销和极速的执行效率。</p>
<p>最酷的是，它被编译成了一个完全静态链接的独立可执行文件（基于 musl），并打包进了一个  scratch  Docker 镜像中，整个 Docker 镜像大小不到
7MB！完全不需要 JVM 或任何其他外部运行时依赖。
核心功能包括：
• 交互式 CLI Shell： 带有  wc2026&gt;  提示符的完整 REPL 体验。
• 漂亮的终端视图： 支持查看 A-L 分组，拥有完美的文本对齐、Emoji 国旗支持和动态的颜色高亮。
• TeaQL 无缝集成： 使用 TeaQL 生成的 Rust 宏和 ORM 直接映射到内嵌的 SQLite 数据库。</p>
<h4>🛠️ 深入了解 TeaQL 核心 API 实践</h4>
<p>在这个项目中，我们深度使用了 TeaQL 框架提供的三大核心
API，不仅让数据操作更加类型安全，也让业务代码极其易读。以下是我们在项目中的一些真实使用场景，希望能给大家在 Rust 中做数据持久化一些参考：</p>
<ol>
<li>Q API (Query API - 查询 API)
Q  API 提供了强类型、可链式调用的数据库查询方法，非常适合复杂的多表级联和排序。</li>
</ol>
<pre><code>// 场景：关系联表查询与复杂多重排序
let standings = Q::group_standings()
    .select_tournament_team_with(Q::tournament_teams().select_self()) // 级联加载队伍数据
    .with_match_group_matching(Q::match_groups().with_id_is(g.id()))
    .order_by_points_desc()
    .order_by_goal_difference_desc()
    .order_by_goals_for_desc()
    .purpose("cli").execute_for_list(ctx).await?;
</code></pre>
<ol start="2">
<li>E API (Expression Facade - 表达式门面)
E  API 提供了一种安全、强类型的方式，用来从已加载的实体中提取值或导航关联关系，避免手写魔术字符串。</li>
</ol>
<pre><code>// 场景：强类型安全地提取实体数据
let name = E::tournament(entity)
    .get_tournament_name()
    .eval();
</code></pre>
<ol start="3">
<li>Entity API (实体 API)
Entity API 负责状态变更（增、删、改），并且它内置了非常健壮的审计日志（Audit Logging）功能。</li>
</ol>
<pre><code>// 场景：数据初始化与带审计追踪的插入
let mut t = Q::tournaments().new_entity(ctx);
t.update_tournament_name("FIFA World Cup 2026".to_string());
t.update_total_teams(48);

// 保存实体的同时，生成详细的 trace log 用于后续追踪
t.audit_as("Seed tournament").save(ctx).await?;
</code></pre>
<h4>🚀 如何体验？</h4>
<p>你可以直接使用 Docker 秒速体验（由于镜像极小，拉取瞬间完成）：</p>
<pre><code>docker run -it --rm teaql/worldcup2026:latest group A
</code></pre>
<p>或者克隆代码并在本地运行：</p>
<pre><code>cd rust-workspace
cargo run
</code></pre>
<p>项目的代码结构非常清晰，分为领域模型  models/ 、生成的实体库  generate-lib/  和主应用  rust-workspace/ 。非常适合作为学习 Rust 命令行开发 以及 TeaQL
框架使用 的参考样例。</p>
<p>非常欢迎大家去 Repo 看看，跑一下代码。如果觉得有意思欢迎点个 Star ⭐️，也期待在评论区听到你们的建议和探讨！</p>
<p>🔗 项目仓库地址：https://github.com/teaql/teaql-rust-app-examples/tree/main/001-world-cpu-2006-cli</p>
]]></description><pubDate>2026-06-15 03:53:13</pubDate></item><item><title>【Rust日报】2026-06-15 Zinnia：纯 Rust 打造的模块化 64 位类 Unix 内核</title><link>https://rustcc.cn/article?id=0c2cf0ad-9c97-4a5c-9bfb-291746418d28</link><description><![CDATA[<h2>Zinnia：用 Rust 编写的模块化 64 位类 Unix 内核，可运行 Wayland/X11 桌面</h2>
<p>作者从 2024 年开始出于学习目的打造 <strong>Zinnia</strong>，一款用（几乎）100% Rust 编写、努力避免 unsafe 代码的模块化 64 位类 Unix 内核。目前已能在大量真实 x86_64 机器上启动。</p>
<p>主要特性：</p>
<ul>
<li>实现大量 POSIX 系统调用，并支持 Linux/BSD 常见扩展（epoll、timerfd），可运行 Wayland/X11 现代桌面</li>
<li>驱动以模块形式实现——编译为 Rust ELF dylib，在启动时从 initrd 动态加载链接，类似 Linux 模块机制</li>
<li>基于 Limine 引导器，支持从任意 UEFI 系统启动</li>
<li>规划中：aarch64、riscv64 支持</li>
</ul>
<p>项目主页：https://zinnia-os.org</p>
<p>原文链接：https://www.reddit.com/r/rust/comments/1u61pkj/zinnia_a_modular_64bit_unixlike_kernel_written_in/</p>
<h2>Diplomat：面向 Rust 库的多语言单向 FFI 工具</h2>
<p>Manish Goregaokar 撰文介绍 <strong>Diplomat</strong>，一个在 ICU4X（Unicode 库 Rust 实现）项目中发展出的多语言单向 FFI 工具。设计初衷：当 Rust 库需要同时暴露接口给 C++、JS、Dart、JVM 等多种语言时，手动维护 FFI 绑定极不可行。</p>
<p>核心理念是"单向"FFI：Rust 作为权威端，代码生成器将 Rust 接口机械映射为各目标语言的绑定，而非双向互相暴露实现细节。经过数年在 ICU4X 上的生产验证，现已正式面向更广泛 Rust 库推广。</p>
<p>GitHub：https://github.com/rust-diplomat/diplomat</p>
<p>原文链接：https://www.reddit.com/r/rust/comments/1u5u5j5/diplomat_multilanguage_ffi_for_rust_libraries/</p>
<h2>deconvolution：28 种图像去卷积/复原算法的 Rust 库</h2>
<p>作者发布了 <strong>deconvolution</strong> crate，集成了 28 种图像去卷积/复原方法，覆盖从实用模糊消除到科研级成像算法。</p>
<p>支持的方法包括：</p>
<ul>
<li>逆滤波、Wiener、Richardson-Lucy、约束优化、近端算法、Krylov、MLE 复原</li>
<li>盲 Richardson-Lucy、盲最大似然、参数化 PSF 估计</li>
<li>高斯、运动、散焦、显微镜模型等多种卷积核</li>
</ul>
<p>底层使用 <code>image::DynamicImage</code> 与 ndarray，crates.io 已发布，仍在活跃开发中。</p>
<p>GitHub：https://github.com/pbkx/deconvolution</p>
<p>原文链接：https://www.reddit.com/r/rust/comments/1u5wwtj/deconvolution_a_comprehensive_image_deconvolution/</p>
<hr>
<p>From Rust中文社区 Mike</p>
<p>社区学习交流平台订阅：</p>
<ul>
<li><a href="https://rustcc.cn/" rel="noopener noreferrer">Rustcc论坛: 支持rss</a></li>
<li><a href="https://rustcc.cn/article?id=ed7c9379-d681-47cb-9532-0db97d883f62" rel="noopener noreferrer">微信公众号：Rust语言中文社区</a></li>
</ul>
]]></description><pubDate>2026-06-15 01:04:30</pubDate></item><item><title>用 Rust 打造的 AI 应用管理后台，高性能、高扩展、全开源。</title><link>https://rustcc.cn/article?id=be9a7439-ecf0-422d-9f59-4f2481ae8924</link><description><![CDATA[<h2>一句话介绍</h2>
<p><strong>祺洛 AI</strong>是一个基于 Rust + Vue 3 的 AI 聊天管理平台，提供多供应商 AI 接入、用户管理、套餐计费、微信生态管理的一站式解决方案。</p>
<hr>
<h2>解决了什么问题？</h2>
<table>
<thead>
<tr>
<th>痛点</th>
<th>祺洛的解法</th>
</tr>
</thead>
<tbody>
<tr>
<td>AI 供应商切换困难</td>
<td>统一接入层，一行配置切换 OpenAI / 通义 / DeepSeek 等</td>
</tr>
<tr>
<td>无法控制用户用量</td>
<td>完善的 Access Key + 用量统计 + 限额控制</td>
</tr>
<tr>
<td>没有付费体系</td>
<td>VIP 套餐管理 + 支付对接（商业版自动支付）</td>
</tr>
<tr>
<td>缺少微信生态</td>
<td>公众号管理、菜单、回复、用户管理完整覆盖</td>
</tr>
<tr>
<td>部署运维复杂</td>
<td>Rust 单二进制部署，资源占用极低</td>
</tr>
</tbody>
</table>
<hr>
<h2>核心优势</h2>
<h3>⚡ 性能 — Rust 原生</h3>
<ul>
<li>单二进制部署，无运行时依赖</li>
<li>内存占用约 15MB（空载），并发</li>
<li>启动时间 &lt; 1 秒</li>
</ul>
<h3>🔌 扩展 — 多供应商架构</h3>
<p>提供统一的 AI 供应商接口，目前已兼容：</p>
<ul>
<li>✅ OpenAI / Azure OpenAI</li>
<li>✅ Anthropic Claude</li>
<li>✅ 通义千问（Qwen）</li>
<li>✅ DeepSeek</li>
<li>✅ 百度文心一言</li>
<li>✅ 任何 OpenAI 兼容接口</li>
</ul>
<h3>🎯 完整 — 开箱即用</h3>
<ul>
<li><strong>AI 管理</strong>：供应商、Key、用量、聊天记录、归档</li>
<li><strong>用户管理</strong>：普通用户（Access Key 接入）和管理员（后台接入）</li>
<li><strong>计费体系</strong>：VIP 套餐、用量计费、支付二维码</li>
<li><strong>系统管理</strong>：RBAC 权限、菜单、字典、定时任务、代码生成器</li>
<li><strong>微信管理</strong>：公众号、菜单、自动回复、消息、用户</li>
</ul>
<h3>🛡️ 可靠 — 企业级特性</h3>
<ul>
<li>JWT 认证 + 白名单 Token 管理</li>
<li>完整的操作审计日志</li>
<li>数据库读写分离与连接池</li>
<li>Redis 缓存加速</li>
<li>CORS 安全配置</li>
</ul>
<hr>
<h2>适用场景</h2>
<table>
<thead>
<tr>
<th>场景</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>🏢 企业内部 AI 助手</td>
<td>员工使用内部 AI 聊天，管理员控制模型和用量</td>
</tr>
<tr>
<td>🛒 AI 产品商业化</td>
<td>搭建面向客户的 AI 聊天产品（如 AI 写作、客服）</td>
</tr>
<tr>
<td>🧪 AI 应用开发测试</td>
<td>多模型快速对比测试，统一管理 API Key</td>
</tr>
<tr>
<td>🏫 教育机构</td>
<td>为学生提供 AI 能力，限制每日使用量</td>
</tr>
<tr>
<td>🔗 集成到现有系统</td>
<td>通过 Access Key 将 AI 能力嵌入你的应用</td>
</tr>
</tbody>
</table>
<hr>
<h2>技术栈</h2>
<table>
<thead>
<tr>
<th>层</th>
<th>技术选型</th>
<th>选择理由</th>
</tr>
</thead>
<tbody>
<tr>
<td>后端</td>
<td>Rust + Axum</td>
<td>高性能、内存安全、单二进制部署</td>
</tr>
<tr>
<td>数据库</td>
<td>MySQL / SQLite / PostgreSQL</td>
<td>SeaORM 支持多种数据库</td>
</tr>
<tr>
<td>缓存</td>
<td>Redis / 内存缓存</td>
<td>灵活切换</td>
</tr>
<tr>
<td>前端</td>
<td>Vue 3 + TypeScript</td>
<td>现代响应式开发体验</td>
</tr>
<tr>
<td>UI</td>
<td>Element Plus + UnoCSS</td>
<td>组件丰富，按需加载</td>
</tr>
<tr>
<td>AI 协议</td>
<td>OpenAI 兼容接口</td>
<td>生态最广，供应商最多</td>
</tr>
</tbody>
</table>
<hr>
<h2>社区</h2>
<ul>
<li>⭐ GitHub: <a href="https://github.com/chelunfu/qiluo_ai" rel="noopener noreferrer">github.com/chelunfu/qiluo_ai</a></li>
<li>💬 问题反馈：GitHub Issues</li>
<li>📖 完整文档：<a href="https://www.qiluo.vip" rel="noopener noreferrer">https://www.qiluo.vip</a></li>
</ul>
<hr>
]]></description><pubDate>2026-06-14 17:55:11</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>