<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-07-09 Bun 重写转向 Rust：不是追新，而是拿编译期约束去解决运行时与手动内存混用的稳定性债</title><link>https://rustcc.cn/article?id=99f40661-a7ef-4761-a027-650ca999ca15</link><description><![CDATA[<h2>Bun 重写转向 Rust：不是追新，而是拿编译期约束去解决运行时与手动内存混用的稳定性债</h2>
<p>Bun 这篇文章真正值得 Rust 社区关注的，不只是“一个知名 Zig 项目改写成 Rust”这种标题效应，而是它把<strong>为什么要重写</strong>讲得非常具体。Bun 现在每月 CLI 下载量已经超过 2200 万次，产品面又横跨运行时、包管理、测试、Node API、HTTP/WebSocket 等多个层面；在这种规模下，团队过去一直在和 use-after-free、double-free、漏释放、异常路径清理不完整这类问题拉扯。文章里列出的 bug 清单很长，但核心结论很简单：他们不想再靠一次次补洞来维持稳定，而是想用语言层面的约束把这类问题系统性压下去。</p>
<p>这也是 Bun 选择 Rust 而不是继续靠风格约束、homegrown smart pointer 或单纯加强 review 的原因。文中反复强调，对 Bun 这种同时要处理垃圾回收对象和手动管理内存的项目来说，<strong>“什么时候释放、谁负责释放、是否只释放一次”</strong> 不能只靠经验和 code review 兜底；在 safe Rust 里，这类问题很多直接会变成编译错误。换句话说，这不是一次为了“换个更流行语言”而做的重写，而是一次把稳定性反馈前移到编译期的工程决策。</p>
<p>更有传播性的地方在于，它还顺手展示了另一层现实：Bun 团队用大规模工作流和测试体系推动这次迁移，但最后真正支撑决策的，仍然是 Rust 在资源生命周期建模上的确定性。对 Rust 社区来说，<strong>这次重写更像是一张来自超大体量运行时项目的“稳定性投票”</strong>，说明当系统复杂度和用户规模都上来后，Rust 的价值依然会越来越硬。</p>
<p>原文链接：https://bun.com/blog/bun-in-rust
参考链接：https://www.reddit.com/r/rust/comments/1ur8ca1/rewriting_bun_in_rust/</p>
<h2>broadsheet 开源：把算法讲解视频做成“时间纯函数”的 Rust 动画引擎</h2>
<p>今天 r/rust 上最容易让人眼前一亮的项目之一，是 <strong>broadsheet</strong> 这个专门面向算法 / 数据结构讲解视频的 Rust 动画引擎。它的切口并不是“我要做另一个通用动画工具”或者“Rust 版 After Effects”，而是非常克制地盯住技术讲解场景：节点、数组、代码块、箭头、镜头移动、路径描边、typewriter 文本这些解释器材，能不能被一套可编程、可复现的 DSL 高效组织起来。</p>
<p>这个项目最漂亮的设计点，是作者把整条时间线定义成 <strong>scene_at_time = f(base_scene, timeline, t)</strong>。也就是说，每一帧都是绝对时间 <code>t</code> 的纯函数，播放器不需要从前一帧一路模拟到后一帧，暂停、拖动、逐帧检查、离线渲染都直接落到同一套求值路径上。这种“无状态时间线”思路很 Rust：把场景、轨道、求值和导出拆成清晰的数据变换边界，再让类型系统帮你把这些边界固定住。</p>
<p>它的价值不只在美观，而在<strong>可复现的技术内容生产</strong>。作者已经给出 <code>seq!</code>、<code>par!</code>、<code>stagger</code> 这类组合原语，内建 circles / cells / code blocks / arrows / camera-aware page rendering，还直接打通 ffmpeg 导出、GIF、PNG、marker JSON 等路径。对做技术讲解、课程内容、系统设计演示的人来说，这不只是一个炫酷 demo，更像是一个正在形成产品形态的内容基础设施雏形。</p>
<p>原文链接：https://khushal.net/blog/broadsheet-rust-animation-engine/
项目地址：https://github.com/SK1PPR/broadsheet</p>
<h2>RootAsRole 4.0 发布：Rust 重写三年后，这个 sudo/doas 替代品继续把权限边界做细</h2>
<p><strong>RootAsRole 4.0</strong> 这次更新的看点，不只是版本号跳到了大版本，而是它继续沿着“把权限委派边界做得更细”这条路往前走。项目本身是一个基于 RBAC 的 Linux 权限委派工具，可以把它理解成 <code>sudo</code> / <code>su</code> 的替代路线；作者在 Reddit 里回顾时提到，项目三年前完成 Rust 重写后，后续又把策略格式从 XML 转到了 JSON，并持续优化策略评估与执行模型。</p>
<p>4.0 里比较关键的两个新增点，一个是<strong>工作目录约束</strong>：某些命令只能在指定工作目录下执行；另一个是<strong>目录式策略配置</strong>：允许把策略拆成目录下的多个文件，方便做按用户审计和更清晰的组织方式。更重要的是，作者没有把这版描述成“加了几个 feature”，而是顺着执行模型继续往下改：新的 <code>rar-exec</code> crate 把程序执行方式从更接近 doas 的模型，转向更像 sudo 那样带中介进程的执行 / 转发 / 过滤思路，强调的是更强的控制面和安全性。</p>
<p>这类项目在 Rust 生态里的意义很直接：它不是做一层表面 CLI 包装，而是在碰真正敏感的系统权限边界。与此同时，4.0 还顺手完成了 Rust 2024 edition 升级、依赖整理、更严格的 Clippy 策略和 AI 使用透明度文档。把这些放在一起看，<strong>RootAsRole 4.0 更像是一个安全工具项目在工程成熟度、策略表达力和治理透明度上的同步升级</strong>。</p>
<p>原文链接：https://github.com/LeChatP/RootAsRole/releases/tag/v4.0.0
参考链接：https://www.reddit.com/r/rust/comments/1ur3fab/three_years_after_rewriting_rootasrole_in_rust/</p>
<h2>Linux 内核里的 Rust 增长图谱出炉：349 个 .rs 文件、9.3 万 SLOC，驱动与抽象层一起变厚</h2>
<p>今天另一个很适合进日报的点，是有人把 <strong>Rust 在 Linux 内核主线里的增长情况</strong> 做成了一个按版本展开的量化观察站。这个站点不是泛泛而谈“Rust 正在进入内核”，而是直接按版本去数 <code>.rs</code> 文件、SLOC、注释行数、目录类别和驱动子系统，让你能看到 6.12 到 7.1 之间 Rust 代码到底是怎么长起来的。对经常关注 “Rust for Linux 到底走到哪了” 的人来说，这比单条新闻更有持续参考价值。</p>
<p>截至最新快照 7.1.3，站点给出的数字是 <strong>349 个 Rust 源文件、93,245 行 SLOC、4.6 MiB 代码体积、13 个用途类别、6 个已出货 Rust 驱动子系统</strong>。更有意思的是，它把增长拆成了两条叙事：一条是内核自己的 Rust 抽象层、生产驱动和配套基础设施在稳定扩张；另一条则是 6.19 那次比较剧烈的抬升，主要来自内核把 <code>syn</code>、<code>quote</code>、<code>proc-macro2</code> 等过程宏依赖整批 vendoring 进树里。也就是说，Rust 在内核里的存在感，已经不是“有几个试验性驱动”那么简单，而是在工具链、宏系统、抽象层和驱动层同时变厚。</p>
<p>如果再看子系统分布，会发现 GPU/DRM、Android binder 等方向已经积累了相当可观的 Rust 代码量。这条信息最有价值的地方在于，它让“Rust for Linux”从理念讨论变成了可测量、可追踪的工程现实。<strong>当 Rust 在主线内核里可以被按版本做增长分析时，说明它已经进入了一个真正值得长期跟踪的阶段。</strong></p>
<p>原文链接：https://rusted-kernel.com/
参考链接：https://www.reddit.com/r/rust/comments/1ura8xu/rust_proliferation_in_the_linux_kernel/</p>
]]></description><pubDate>2026-07-09 01:09:46</pubDate></item><item><title>【Rust日报】2026-07-08 rama 0.3 发布：四年打磨的 Rust 网络服务底座，开始转向 2-8 周发布列车</title><link>https://rustcc.cn/article?id=2d9bf1de-7176-4e9c-8390-3718481d9733</link><description><![CDATA[<h2>rama 0.3 发布：四年打磨的 Rust 网络服务底座，开始转向 2-8 周发布列车</h2>
<p>如果说过去不少人对 <strong>Rama</strong> 的印象还停留在“一个很有野心的 Rust 网络框架 / 代理框架”，那么 0.3 这篇长文更像是在宣布：它现在终于开始从长期打磨阶段，转入一个 <strong>可以被更稳定采用</strong> 的阶段。作者回顾了 Rama 过去四年的演化，从早期建立在 tower-rs / hyperium 之上的尝试，到后来不断重写、重构，逐步形成如今这套面向 <strong>clients / servers / gateways / proxies</strong> 的统一网络服务基础层。</p>
<p>0.3 的一个关键信号，是项目方明确承认 <strong>从 0.2 升到 0.3 仍有真实迁移成本</strong>，但同时承诺这是最后一次出现这么大的版本跨度；从这一版开始，目标是进入 <strong>2 到 8 周的 release train</strong>，并尽量保持更可预期的 SemVer 节奏。对于考虑在生产环境里押注某个基础框架的人来说，这比“又加了几个 feature”更重要，因为它关系到项目的维护节奏是否足够成熟。</p>
<p>另一层含金量在于，Rama 不再只是理念型项目。文中提到它已经被用于多类真实业务，包括数据提取、安全、AI 代理网关、云基础设施、代理网络和各类 Web 服务平台；Plabayo 也已经把全职注意力放在 Rama 上。把这些信息合在一起看，<strong>Rama 0.3 更像是一个 Rust 网络服务底座开始进入生产级扩张期的信号</strong>，而不是一次普通的小版本更新。</p>
<p>原文链接：https://plabayo.tech/blog/rama-0-3
发布页：https://github.com/plabayo/rama/releases/tag/rama-0.3.0</p>
<h2>nexir-mvcc-core 开源：把事务型存储系统里的 MVCC 核心单独抽出来了</h2>
<p>这条开源项目的切口很明确：<strong>nexir-mvcc-core</strong> 并不想再做一个“一整套数据库”，而是把事务型存储系统里最核心、也最容易反复重造的一层——<strong>MVCC 并发控制与版本管理</strong>——单独拆出来做成可复用组件。作者强调，这个项目刻意把存储引擎、网络、共识和查询执行都排除在外，只聚焦事务核心本身，这种边界划分其实挺有吸引力，因为它更容易被现有系统吸收。</p>
<p>从能力列表看，它已经具备一套像样的事务骨架：<strong>按时间戳排序的 MVCC 读写、基于 intent 的 prewrite / commit / abort 事务流程、多 key 原子批量事务、CAS 风格 guarded writes、增量式 MVCC GC，以及可对接不同后端的抽象层</strong>。如果你在做 KV、存储引擎、分布式数据库或者事务中间层，这些几乎都是绕不过去的基础能力。</p>
<p>更重要的是，作者想卖的不是“又一个数据库项目”，而是一个 <strong>deterministic、可嵌入、可测试的 MVCC core</strong>。这种定位很务实：不少团队真正缺的不是 SQL 层或者查询规划器，而是一块能放心拿来打底的事务内核。对 Rust 数据基础设施生态来说，这类“拆层开源”的项目通常比一套大而全的产品更容易产生复用价值。</p>
<p>原文链接：https://old.reddit.com/r/rust/comments/1uq204t/a_standalone_mvcc_engine_for_transactional/
项目地址：https://github.com/nexirdb/nexir-mvcc-core</p>
<h2>Rust OSDev 月报：Asterinas 0.18、Zinnia、uefi-rs 0.38 与 no_std I/O 一起推进</h2>
<p>这期 <strong>This Month in Rust OSDev</strong> 依然很密，价值在于它不是只讲一个项目，而是把整个 Rust 操作系统开发生态最近一个月的推进面摊开给你看。新闻区最显眼的几个点包括 <strong>Asterinas 0.18.0</strong>、Redox 最新进展、几乎全 Rust 编写且能在真实 x86_64 硬件上跑 Weston / XFCE 的 <strong>Zinnia</strong>，以及面向 Rust 的从零 OS 开发教材 <strong>LearnixOS</strong>。如果你关心 Rust 在“真正的底层系统软件”里推进到了哪一步，这种总览比单看某一条 release 更能看出势能。</p>
<p>工具链层面的内容也很有意思。比如 <code>std::io::Error</code> 被继续往 <code>core</code> 方向推进，后续 <code>Read</code> / <code>Write</code> 等能力也有望进一步下沉；<code>box_as_ptr</code> 在稳定化、<code>int_format_into</code> 让整数格式化更适合固定缓冲区、<code>-Zstaticlib-hide-internal-symbols</code> 能在非 LTO 构建里缩掉 5% 到 12% 的体积、Xtensa 的 <code>asm!</code> 也终于进了树。这些改动看似零散，但合起来都直接服务于 <strong>no_std / 嵌入式 / 内核级</strong> Rust 开发体验。</p>
<p>项目区同样很扎实：<code>uefi-rs</code> 合入 IoMmu、PciRootBridgeIo、<code>time</code> / <code>jiff</code> 集成并发布 <code>uefi-0.38.0</code>；<code>multiboot2</code> 持续做安全性与现代化整理；<code>bootloader</code> 跟进最近 nightly ABI 变化；<code>ovmf-prebuilt</code> 和 <code>uart_16550</code> 也都有稳步推进。它传递出的信号很清楚：<strong>Rust OSDev 不是只有几个明星项目单点发光，而是整条工具链、库和实验系统都在同步变厚</strong>。</p>
<p>原文链接：https://rust-osdev.com/this-month/2026-06/
项目主页：https://github.com/rust-osdev/homepage/</p>
]]></description><pubDate>2026-07-08 01:07:52</pubDate></item><item><title>一个共享内存锁引发的思考</title><link>https://rustcc.cn/article?id=aaf92b79-9bdd-4f06-bfcf-69c1a723ef2e</link><description><![CDATA[<h1>共享内存技术方案：Rust与Python进程间视频帧通信</h1>
<h2>项目背景</h2>
<p>本项目是一个视频增强项目，需要将视频帧通过管道传输给Python算法处理，处理完成后再传回进行编码和封装以生成新视频。</p>
<p>该项目主要涉及媒体处理，包含FFmpeg、Libx26x、Zlib和字体库，完全使用Rust编写。但这只是整个系统的一部分——图像处理算法才是核心。我需要利用该算法增强所有视频帧，之后才能进行编码和封装生成新的MP4/MOV文件。</p>
<p>理想方案是将所有内容（包括FFI）整合到Rust程序中，如同使用动态链接库（so/dylibs）一样。但实际情况是，算法模型由大量Python代码组成，这些代码之间存在相互依赖关系，涉及磁盘文件搜索和动态导入。这种拓扑结构使得用C++/Rust重写变得不可能。</p>
<h2>第一次尝试：管道方案</h2>
<h3>实现方式</h3>
<p>在第一个版本中，我们采用管道方式实现：使用stdin将所有YUV帧流式传输给Python程序，然后通过stdout管道接收处理结果。</p>
<p>管道方案并不简单，因为Python代码依赖许多第三方库，这些库可能会隐式地打印调试信息。我们需要设计类似MP4 Box或H.26x Annex-B格式的头部/分隔符，用于解析真正的YUV内容。</p>
<p><strong>Rust端写入stdin：</strong></p>
<pre><code>let mut idx = self.frames % infsdrs_len;
let mut data = vec![];
data.extend_from_slice(STDIO_SEP.as_bytes());
data.extend_from_slice(&amp;(self.frames as u32).to_be_bytes());
self.prosess_avframe(self.dec_frame, &amp;mut data)?;
</code></pre>
<p><strong>Rust端读取stdout：</strong></p>
<pre><code>let std_finder = memchr::memmem::Finder::new(&amp;*STDIO_SEP);
let sep_len = STDIO_SEP.len();
loop {
  if let Ok(n_rst) = tokio_read_stdio!(&amp;mut stdout, &amp;mut buf, 200) {
      match n_rst {
          Ok(n) =&gt; {
              if n &gt; 0 {
                  cache.extend_from_slice(&amp;buf[0..n]);
                  if let Some(m) = std_finder.find(&amp;cache) {
                      if cache.len() &gt;= total_frame_size + m {
                      ...
</code></pre>
<p>对于不规则尺寸的视频，还需要额外的数据拷贝工作。</p>
<h3>方案缺陷</h3>
<p>管道方案虽然可用，但存在以下问题：</p>
<ol>
<li><strong>Python依赖问题</strong>：程序需要Python环境，导致部署不便捷、可移植性差，整个系统显得"混乱"</li>
<li><strong>内存开销大</strong>：管道缓冲区内存占用通常很大，且缺乏对异常情况的保证（如数据截断或损坏）</li>
<li><strong>异常处理复杂</strong>：需要设计非常健壮的模式来处理所有异常情况</li>
</ol>
<h2>第二次尝试：共享内存方案</h2>
<h3>问题解决思路</h3>
<p><strong>1. Python部署问题</strong></p>
<p>尝试了多种方案，从最初的miniforge到uv工具。最终发现<a href="https://github.com/pyinstaller/pyinstaller" rel="noopener noreferrer">pyinstaller</a>可以将算法模型打包为可执行文件。经过一些调整（将Python代码中的相对路径改为绝对路径），我们成功生成了可用的可执行文件。虽然不完美，但大大简化了部署复杂度。</p>
<p><strong>2. 管道内存问题</strong></p>
<p>为了避免大内存占用，考虑过使用信号量（semaphore），但面临两个问题：</p>
<ul>
<li>必须跨平台，因此不能仅依赖POSIX</li>
<li>算法可能需要多个worker（如3个），需要创建至少3个信号量，增加了管理复杂度</li>
</ul>
<p>最终采用<a href="https://github.com/elast0ny/raw_sync-rs" rel="noopener noreferrer">raw_sync</a> crate，创建超快速、低内存占用的共享内存互斥锁/读写锁标志。</p>
<p><strong>3. 异常处理简化</strong></p>
<p>如果解决上述两个问题，异常处理将简化为仅处理原子标志、共享内存文件处理等。相比之前的纯Python + 权重搜索 + 管道设计，使用超时和标志设计来报告异常算法worker要简单得多。</p>
<h3>共享内存架构</h3>
<p>这次采用了<a href="https://github.com/elast0ny/shared_memory-rs" rel="noopener noreferrer">shared_memory</a>和<a href="https://github.com/elast0ny/raw_sync-rs" rel="noopener noreferrer">raw_sync</a>。raw_sync提供了互斥锁包装：POSIX平台基于pthread实现，Windows平台使用winapi::um::synchapi。</p>
<p>使用shared_memory创建文件时，该crate会将文件内容映射到内存中：</p>
<pre><code>                                   共享 mmap() 文件
     ┌────────────────────────────────────────────────────────────────────────────┐
     │                                                                            │
     │  +----------------+----------------------------------------------------+   │
     │  | 0 ~ 7          | AtomicU8 标志 (8 字节 / 缓存行对齐)                |   │
     │  +----------------+----------------------------------------------------+   │
     │  | 8 ~ xxx        | pthread_mutex_t (PTHREAD_PROCESS_SHARED)           |   │
     │  +----------------+----------------------------------------------------+   │
     │  | 输入缓冲区     |                                                    |   │
     │  | (Rust → Python)|   原始视频帧 / 元数据                             |   │
     │  |                |                                                    |   │
     │  +----------------+----------------------------------------------------+   │
     │  | 输出缓冲区     |                                                    |   │
     │  | (Python→ Rust) |   检测结果 / AI推理结果 / 处理后帧                |   │
     │  |                |                                                    |   │
     │  +----------------+----------------------------------------------------+   │
     │                                                                            │
     └────────────────────────────────────────────────────────────────────────────┘
              ▲                                                    ▲
              │                                                    │
              │                                                    │
      写入输入帧                                         写入AI处理结果
              │                                                    │
              │                                                    │
              │                                                    │
  ┌──────────────────────────┐                        ┌──────────────────────────┐
  │      Rust 进程           │                        │   Pyo3/Python 进程       │
  │──────────────────────────│                        │──────────────────────────│
  │ 硬件解码                 │                        │ AI / 计算机视觉算法      │
  │ 硬件视频处理             │                        │ NumPy / Torch / OpenCV   │
  │ WebRTC / 编码器          │                        │ CPU/GPU 推理             │
  └──────────────────────────┘                        └──────────────────────────┘
</code></pre>
<h3>同步机制对比</h3>
<p>在实现过程中发现，原子标志和pthread互斥锁功能有所重叠：</p>
<p><strong>1. AtomicU8标志（自旋锁）</strong></p>
<p>这是一个基于共享内存的标志，为了在Python端使用，我们构建了一个Rust的Python wheel项目并安装，提供Python SDK来操作该标志。双端都可使用该标志，这是一种<strong>进程安全</strong>的模式。</p>
<p>有趣的是，如果使用该标志作为互斥锁，每次循环约需10ns，相当于快速响应的<strong>自旋锁</strong>：</p>
<pre><code>while flag.load(Ordering::Relaxed) != CODE_CONSUMED {
    std::hint::spin_loop();
    if write_start.elapsed().as_secs() &gt; timeout {
        bail!("Host timeout waiting for consumer flag CODE_CONSUMED")
    }
}
</code></pre>
<p><strong>2. pthread互斥锁（系统调用）</strong></p>
<p>使用互斥锁同步缓冲区会引入系统调用。在POSIX上是<code>pthread_mutex</code>或<code>futex</code>。由于涉及内核操作，用户态/内核态上下文切换会带来性能开销：</p>
<pre><code>let lock = self.lock
  .try_lock(raw_sync::Timeout::Val(Duration::from_secs(timeout)))?;
</code></pre>
<h3>性能对比结果</h3>
<p>实测表明，原子标志的性能并不优于互斥锁，但有一项重要权衡：</p>
<table>
<thead>
<tr>
<th>特性</th>
<th>AtomicU8标志（自旋锁）</th>
<th>pthread互斥锁</th>
</tr>
</thead>
<tbody>
<tr>
<td>性能</td>
<td>每次循环约10ns</td>
<td>涉及系统调用，有上下文切换开销</td>
</tr>
<tr>
<td>CPU占用</td>
<td><strong>持续占用CPU核心</strong></td>
<td>不占用CPU（等待时挂起）</td>
</tr>
<tr>
<td>适用场景</td>
<td>CPU资源充足</td>
<td>CPU资源有限</td>
</tr>
</tbody>
</table>
<h2>方案选择建议</h2>
<p>根据不同的资源条件，选择合适方案：</p>
<ol>
<li><strong>CPU核心充足，内存充足</strong>：使用AtomicU8共享内存可获得最佳性能</li>
<li><strong>CPU核心有限，内存充足</strong>：可考虑管道（stdin/stdout）方案</li>
<li><strong>需要稳定可靠的方案，内存有限</strong>：考虑使用futex方式的互斥锁</li>
</ol>
]]></description><pubDate>2026-07-07 08:30:25</pubDate></item><item><title>【免费用3月】BGE-M3 全能多粒度嵌入模型：三合一检索、百种语言、超长上下文，量化版上线算纽GPUNexus</title><link>https://rustcc.cn/article?id=7ee95445-1614-429d-9cdb-9e7627d73113</link><description><![CDATA[<p>【免费用3月】BGE-M3 全能多粒度嵌入模型：三合一检索、百种语言、超长上下文，量化版上线算纽GPUNexus</p>
<h1><strong>RAG 检索的新标杆</strong></h1>
<p>在构建 RAG（检索增强生成）系统、多语言知识库和长文档向量化应用时，选择合适的嵌入模型至关重要。传统的嵌入模型往往需要在密集检索、稀疏检索和多向量检索之间做出取舍，而跨语言场景下的性能表现更是参差不齐。今天，为大家介绍一款全能型嵌入模型——BGE-M3，以及其量化版本 bge-m3-q8_0 在算纽GPUNexus平台上线后，2026年第三季度免费使用。</p>
<h2><strong>BGE-M3 核心能力解析</strong></h2>
<h3>1. 三合一检索：一站式解决方案</h3>
<p>BGE-M3 实现了密集检索、多向量检索和稀疏检索的统一支持：</p>
<ul>
<li>密集检索（Dense Retrieval）：基于向量相似度的传统检索方式，适合语义匹配</li>
<li>多向量检索（Multi-Vector Retrieval）：为文档生成多个向量表示，提升细粒度匹配能力</li>
<li>稀疏检索（Sparse Retrieval）：基于词频的检索方式，保留关键词匹配优势</li>
</ul>
<p>这种三合一架构让开发者无需在不同检索模式间切换，一套模型满足多种需求。</p>
<h3>2. 百种语言覆盖：真正的多语言支持</h3>
<p>BGE-M3 支持 100+ 种语言，在跨语言检索任务中表现卓越：</p>
<ul>
<li>训练数据涵盖主流语言和小语种</li>
<li>统一的向量空间表示，支持跨语言语义对齐</li>
<li>在多语言知识库检索中效果显著提升</li>
</ul>
<h3>3. 超长上下文处理：8192 词元输入</h3>
<p>模型支持最大 8192 词元的输入长度，这意味着：</p>
<ul>
<li>短句查询和万字长文档可以统一编码</li>
<li>无需分段处理，保持文档语义完整性</li>
<li>适合技术文档、论文、长篇文章的向量化</li>
</ul>
<h3>4. 权威榜单表现</h3>
<p>在多项国际基准测试中，BGE-M3 均取得领先成绩：</p>
<ul>
<li>MIRACL：多语言检索基准测试</li>
<li>MKQA：多语言问答数据集</li>
<li>MLDR：多语言文档检索</li>
<li>NarritiveQA：长文档问答基准</li>
</ul>
<h2><strong>bge-m3-q8_0 量化版：性能与效率的平衡</strong></h2>
<p>算纽GPUNexus平台最新上线的 bge-m3-q8_0 是 BGE-M3 的 8-bit 量化版本，具有以下优势：</p>
<h3>量化技术优势</h3>
<ul>
<li>显存占用降低：相比原版减少约 4 倍显存使用</li>
<li>推理速度提升：量化优化带来更快的向量生成速度</li>
<li>精度损失极小：Q8 量化在大多数任务中精度损失可忽略</li>
</ul>
<h3>部署灵活性</h3>
<ul>
<li>本地部署：适合对数据隐私要求高的场景</li>
<li>云端调用：通过 API 快速集成到现有系统</li>
<li>混合部署：根据业务需求灵活选择部署方式</li>
</ul>
<h2><strong>应用场景与最佳实践</strong></h2>
<h3>1. RAG 系统优化</h3>
<ul>
<li>混合检索策略：结合密集检索的语义理解和稀疏检索的关键词匹配</li>
<li>多粒度表示：利用多向量检索提升复杂查询的召回率</li>
<li>长文档处理：直接处理完整文档，避免分段带来的信息损失</li>
</ul>
<h3>2. 多语言知识库</h3>
<ul>
<li>统一向量空间：不同语言文档在同一空间中进行检索</li>
<li>跨语言问答：用中文查询检索英文文档，或反之</li>
<li>小语种支持：覆盖更多语言用户群体</li>
</ul>
<h3>3. 文档聚类与分类</h3>
<ul>
<li>主题发现：基于语义相似度的文档聚类</li>
<li>自动分类：利用向量表示进行多标签分类</li>
<li>去重检测：识别语义相似的重复内容</li>
</ul>
<h2><strong>限时福利</strong></h2>
<p>算纽GPUNexus平台为 bge-m3-q8_0 提供特别优惠：</p>
<ul>
<li>免费试用：2026年第三季度免费使用，零成本体验</li>
<li>访问 算纽GPUNexus大模型超市(https://gpunexus.com/zh/models)，注册账号即可开始免费试用 bge-m3-q8_0，开启高效检索的新篇章！</li>
</ul>
]]></description><pubDate>2026-07-07 05:50:31</pubDate></item><item><title>【Rust日报】2026-07-07 WATaBoy：把 Game Boy 指令 JIT 到 Wasm，跑得比原生解释器还快</title><link>https://rustcc.cn/article?id=e83c05ae-290e-41ae-bc0a-dc9eb50094fe</link><description><![CDATA[<h2>WATaBoy：把 Game Boy 指令 JIT 到 Wasm，跑得比原生解释器还快</h2>
<p>这条最抓眼球的地方，在于作者没有停在“Rust 写了个 Game Boy 模拟器”这一层，而是把 <strong>SM83 指令动态重编译到 Wasm</strong>，再借浏览器引擎的 JIT 把 Wasm 继续编译成原生机器码。作者给出的结论很直接：这套 <strong>JIT-to-Wasm</strong> 路线不仅能跨平台，甚至在浏览器里也能 <strong>稳定跑赢原生解释器</strong>。如果你以前把“浏览器里的 Wasm”更多看成部署便利，这个项目会把它往“性能工具链”重新拉一遍。</p>
<p>更有意思的是，它瞄准的还是一个通常不太会被认为特别适合 JIT 的对象：<strong>Game Boy 模拟器</strong>。文章里提到，作者没有直接生成原生代码，而是利用 WebAssembly 作为中间层，借 JavaScriptCore / WebKit 的能力，在 iOS 这类对传统 JIT 限制严格的平台上绕出一条现实可行的路。换句话说，它展示的不是“Wasm 也能跑 emulator”这么简单，而是 <strong>把 Wasm 当作可移植 JIT 落点</strong> 来用。</p>
<p>从工程角度看，这篇文章的含金量还在于它没有只晒结果，而是认真展开了 <strong>Rust 内生成 Wasm 字节码、通过 embedder 编译/实例化、再接回间接调用表</strong> 的实现路径。对关心运行时代码生成、解释器优化、浏览器宿主限制的人来说，这已经不只是个玩具项目，而是一份很有参考价值的实现笔记。</p>
<p>原文链接：https://humphri.es/blog/WATaBoy/
项目地址：https://github.com/EnergeticBark/WATaBoy</p>
<h2>Kani：Rust 模型检查器从“找 bug”走向“给正确性证明兜底”</h2>
<p>如果说很多人对 <strong>Kani</strong> 的印象还停留在“Rust 形式化验证工具”，这篇新论文把它的定位讲得更完整了。作者强调，Rust 的所有权系统能很好地挡住安全代码里的内存错误，但像 <strong>unsafe 操作是否可靠、函数是否满足功能正确性、程序是否不会 panic</strong> 这类性质，并不会因为编译通过就自动成立。Kani 要补的正是这段空白：它把 Rust 的 <strong>MIR</strong> 转进 <strong>CBMC</strong> 的位精确验证引擎里，对证明 harness 做边界模型检查。</p>
<p>这篇论文最值得注意的，不只是“又能验证更多东西”，而是它开始明确往 <strong>可扩展规格语言</strong> 上走。论文里提到，Kani 提供了函数契约、循环契约、量词和函数桩替换等能力，让验证从有限展开继续向更强的正确性表达推进。作者还给出一个很有传播力的数据点：在工业案例里，这些契约帮助他们把验证目标从 panic-freedom 升级到功能正确性，并发现了 <strong>6 个此前未知的 bug</strong>。</p>
<p>另一个信号是规模。论文直接写到，Kani 已经用于生产 CI 场景，在 Rust 标准库验证工作里，<strong>每次代码变更要检查超过 16,000 个 harness</strong>。这意味着它讨论的已经不是“实验室里能跑起来的验证器”，而是一个开始真正嵌入大型 Rust 工程流程的工具链。对 Rust 社区来说，这种工具成熟度的提升，比单纯多一篇论文更重要。</p>
<p>原文链接：https://arxiv.org/abs/2607.01504
项目地址：https://github.com/model-checking/kani</p>
<h2>rust-analyzer Changelog #335：新诊断、<code>cargo.configPath</code> 与一串崩溃修复一起落地</h2>
<p>今天这版 <strong>rust-analyzer #335</strong> 不算那种“单一超级功能”的发布，但对日常开发体验很实用。新特性里比较醒目的有三个：新增 <code>union-pat-has-rest</code> 诊断、<code>yield-outside-of-coroutine</code> 诊断，以及新的 <strong><code>cargo.configPath</code> 设置项</strong>。前两个继续补齐语义分析和诊断覆盖，后一个则更偏向工程配置层面的可控性，对有复杂 Cargo 布局或自定义环境的人会比较有用。</p>
<p>性能方面，这版虽然没打出特别夸张的 headline，但也做了几项很典型的底层打磨，比如 <strong>减少 parser joint token 的分配尺寸</strong>、上提 attribute qualifier segment 计算，以及删掉没再使用的 SCIP 字段。这类改动单看不炸眼，但 rust-analyzer 的响应体验本来就是靠很多这种小手术一点点堆出来的。</p>
<p>修复列表则相当长，重点集中在一批真实会撞到的崩溃和 lowering / macro / completion 边角问题上。像 const resolution、associated types、macro call 内 completions、递归 enum nested pattern 之类都被点名修过。对每天离不开 rust-analyzer 的人来说，这种发布最直接的意义往往不是“多了一个新按钮”，而是 <strong>少掉一串让人分神的 IDE 小故障</strong>。</p>
<p>原文链接：https://rust-analyzer.github.io/thisweek/2026/07/06/changelog-335.html
发布页：https://github.com/rust-analyzer/rust-analyzer/releases/2026-07-06</p>
<h2>Girls Just Wanna Have Fast MPMC Queues：把 bounded waiting 的多生产者多消费者队列讲透了</h2>
<p>这篇文章切口很硬核：作者尝试自己实现一个 <strong>MPMC 队列</strong>，目标不是再讲一遍“锁免费数据结构有多难”，而是围绕 <strong>bounded waiting</strong> 去构造一套更可解释的设计。文中用双 ticket counter、数据 ring buffer 和状态 ring buffer 组合出一套队列协议，让生产者 / 消费者分别领取“号牌”，再按状态位交替获得槽位访问权。这种讲法很适合 Rust 社区，因为它把原子操作、缓存行填充、内存布局和并发语义几件事揉在了一起。</p>
<p>作者也很诚实地在文首修正了一个关键点：<strong>这并不是真正 wait-free 的实现</strong>。原因是线程挂起仍然可能拖住其他线程，所以更准确的说法应该是“有界等待特性很强，但不满足严格的 wait-free 定义”。这一点反而让文章更值得看——它不是拿术语包装项目，而是把设计边界和理论定义摆出来讲清楚。</p>
<p>对 Rust 生态来说，这类内容的价值在于，它不是只给出一个 crate，而是把背后的思考路径也摊开：为什么要用 <code>AtomicUsize</code>、为什么缓冲区大小强制成 2 的幂、为什么状态位和 reservation number 这样编码。即使最后不直接采用作者的实现，读完也很容易把一些并发设计直觉带回自己的系统里。</p>
<p>原文链接：https://nahla.dev/blog/waitfree_queue/</p>
]]></description><pubDate>2026-07-07 01:08:26</pubDate></item><item><title>专为全栈工程师打造的跨平台 SDK 版本管理器</title><link>https://rustcc.cn/article?id=93490f2c-a5af-4742-8051-dc7cfd16bceb</link><description><![CDATA[<blockquote>
<p>自荐一个我自己造的轮子：sdkm —— 用 Rust 写的跨平台 SDK 版本管理器</p>
</blockquote>
<p>大家好，不知道你们是不是也这样：每次装个 JDK、Node、Python，流程都是熟的——下载、解压、找个目录放好，然后去系统里手动改 <code>JAVA_HOME</code>，<code>CLASSPATH</code>、<code>PATH</code>、<code>NODE_HOME</code>，改完重开终端验证一下，生怕改错把别的x项目搞挂起不来。一个版本就还好，<strong>多版本共存的时候就头大</strong>：老项目要 Java 8、新项目要 Java 21，今天切这个明天切那个，每次都得去环境变量里把路径换来换去，烦都要烦死了。</p>
<p>几乎每次换环境都的自己来一遍，前段时间配新机器配到一半，突然就想：都 2026 年了，为什么这事还得我手动干？我之前也试过 jvms,sdkman这样类似的工具，要么是有诡异的报错，要么是windows不友好压根用不了。</p>
<p>于是用 Rust 自己写了一个：<strong>sdkm</strong>，专门解决「装 + 切 + 配环境变量」这一整套。</p>
<h2>它怎么把这事变简单的</h2>
<p>我就不罗列所有功能了，挑几个我自己用着最爽的讲：</p>
<p><strong>装完就配好，不用碰环境变量</strong>
<code>sdkm install java 21</code> 一条命令：下载、解压、放目录、建符号链接、写 PATH、设环境变量全自动搞定。Windows 体验也很好，<strong>已经开着的程序也能感知到新版本</strong>，不用重开终端。</p>
<p><strong>切版本就是一条命令，而且全局生效</strong>
<code>sdkm switch java 17</code> 之后，符号链接、系统 PATH、<code>JAVA_HOME</code> 同步切过去，全局持久，不像用别的方案，切完只在当前 shell生效，开个新窗口又回去了，反复拉扯。现在一条命令，哪儿都生效。</p>
<p><strong>一个二进制，丢哪跑哪</strong>
你简直不敢相信 就一个 <code>3MB</code> 的可执行文件，没有错就3M!</p>
<p>没有运行时依赖。sdkm 解压后启动即它自己所在目录，完全绿色纯净，没有其他偷偷吃磁盘的操作——配置、已装的 SDK 全在旁边。我把笔记本里的 <code>sdkm.exe</code> 拷到台式机，连配置带已下的 JDK 一起带过去了，直接能跑。下好的 JDK 扔进 <code>store/</code> 目录就托管，<strong>不用重新下一遍</strong>，这对我这种给新机器配环境的人太省事了。</p>
<p><strong>出错也不怕把环境弄坏</strong>
这点是我比较在意的。每步操作都打印清楚在干嘛、为什么。切换过程中任何一步挂了，自动回滚回切换前的样子；配置文件原子写入 + 快照回滚，不会写一半留个烂摊子。</p>
<p><strong>还能模糊匹配 + TUI</strong>
<code>sdkm install java 21</code> 它自己找最新的 21.x，不用查具体小版本号；打错了还会提示相近版本。<code>sdkm list node -r</code> 进一个交互界面，上下键翻远程版本，按 <code>i</code> 装、按 <code>s</code> 切，比背命令舒服。TUI操作就是非常丝滑，谁体验谁知道！</p>
<p><strong>最近还加了点 AI 的活儿</strong>
2026年了，ai时代，cli天生就是给agent调用的。这么一想我就给sdkm 准备了一份给 agent 看的 skill 文档。Claude Code 之类的助手读一遍，就能直接替你装/切 SDK——退出码语义清晰，失败自动思考总结原因。我现在经常直接跟 Claude 说「装个 node 20」就完事，连命令都懒得敲，懒人福音！</p>
<h2>技术栈选择</h2>
<p>Rust 1.92，clap 解析参数，tokio 做异步下载，reqwest 走 HTTP，toml + serde 管配置，crossterm 着色和TUI、indicatif 画进度条。</p>
<p>支持 Java / Node / Python / Maven，另外任何「能从 URL 下载解压」的东西都能一行命令注册成自定义 SDK, 任何本地sdk都能交给sdkm管理，它来负责切换！</p>
<hr>
<p>🔗 项目在这：https://github.com/borenchan/sdkmate （顺手点个 ⭐️ 就更好了，给人继续磨的动力）</p>
<p>最后想问大家一句：你们平时装 SDK、切版本、配环境变量，都踩过什么坑吗。如果有对项目贡献，建议,问题反馈也都欢迎大家来提issues。</p>
]]></description><pubDate>2026-07-06 01:09:04</pubDate></item><item><title>【Rust日报】2026-07-06 Arti 2.5.0 发布：Counter Galois Onion 转正、默认开启拥塞控制，并修复两项 DoS 问题</title><link>https://rustcc.cn/article?id=9e679c1d-8859-4b0e-a4e2-fac542c3708b</link><description><![CDATA[<h2>Arti 2.5.0 发布：Counter Galois Onion 转正、默认开启拥塞控制，并修复两项 DoS 问题</h2>
<p>Tor 项目的 Rust 实现 <strong>Arti 2.5.0</strong> 这次更新，终于把几个之前更多停留在“逐步完善中”的能力推到了更实用的阶段。最醒目的变化，是 <strong>Counter Galois Onion</strong> 现在被标记为稳定特性，并进入完整功能构建；与此同时，<strong>Congestion Control</strong> 也开始在默认构建里直接启用，不需要额外配置就能吃到速度收益。对一直关注 Rust 在匿名网络、隐私基础设施里落地的人来说，这意味着 Arti 不只是“用 Rust 重写 Tor”，而是在持续把协议实现往可部署、可默认启用的新阶段推进。</p>
<p>这次版本的另一个关键信号，是它把“性能推进”和“安全修复”放在了同一次正式发布里处理。官方公告提到，版本内同时修复了 <strong>TROVE-2026-024</strong> 和 <strong>TROVE-2026-027</strong> 两个中等严重度的 DoS 问题；也就是说，这不是一版单纯讲新功能的 release，而是把协议演进、默认性能策略和安全补丁一起打包交付。对基础设施项目来说，这种发布节奏本身就说明工程成熟度在往上走。</p>
<p>开发者侧也有一个需要注意的门槛变化：Arti 2.5.0 把 <strong>MSRV 提升到 Rust 1.91</strong>。如果团队里还有比较老的工具链或者锁得比较紧的构建环境，这次升级不只是 <code>cargo update</code> 那么简单，可能还会牵动 CI 镜像和内部依赖基线。但反过来看，这也说明 Arti 正在更积极地利用较新的 Rust 生态能力，而不是长期停在保守兼容线上。</p>
<p>更长一点看，这个版本依然在继续补齐 <strong>relay</strong> 和 <strong>directory authority</strong> 方向的能力。也就是说，Arti 的故事仍然不是“做一个能跑客户端的替代品”就结束，而是继续朝 Tor 更完整的角色集合靠近。对 Rust 社区而言，这类项目的价值很直接：它证明 Rust 在高安全要求、强协议约束、持续演进的网络基础设施里，已经不仅仅适合写外围工具，而是在承担核心实现。</p>
<p>原文：https://blog.torproject.org/arti_2_5_0_released/
补充报道：https://alternativeto.net/news/2026/7/arti-2-5-brings-stable-counter-galois-onion-default-congestion-control-and-security-fixes/</p>
<p>原文链接：https://blog.torproject.org/arti_2_5_0_released/</p>
<h2>wip：把 Rust 开发期“先跑起来再补正确性”系统化成 warning-first 工作流</h2>
<p>这篇 <strong>Work In Progress Rust</strong> 文章很有共鸣点：Rust 的“正确性优先”固然是优势，但在复杂逻辑刚起草的时候，开发者经常会被 <code>Result</code>、边界条件、所有权细节频繁打断。作者的核心观点不是反对这些约束，而是强调一种更现实的工作流：<strong>先把主要逻辑推进到可测试状态，再把未完成部分明确地挂成 warning，最后在 CI 阶段强制清理</strong>。这个思路并不新，但作者把它系统化得很完整。</p>
<p>文章先梳理了很多 Rust 开发者都熟悉但不够优雅的临时技巧：比如先 <code>unwrap()</code>、先 <code>clone()</code>、用 <code>todo!()</code> 占位，或者拿文档注释伪装出“记得回来修”的编译提醒。作者承认这些手法在真实工程里确实有用，因为它们能让人暂时留在 happy path，不至于在实现主线逻辑时被每一个错误分支拖住。但问题也很明显：这些临时措施并不总会稳定地产生 warning，而且很容易在重构或合并前被忘掉。</p>
<p>因此文章最后落在作者做的 <strong>wip</strong> crate 上：它把 “WIP 阶段允许先欠债，但必须留下可见提醒” 这件事包装成了一套更明确的工具。比如用会稳定发出 warning 的 <code>wip!</code> 代替普通 <code>todo!</code>，用 <code>unwrap_wip</code>、<code>clone_wip</code> 这类扩展显式区分“开发期临时写法”和“准备进入最终代码的正式写法”。这类库不一定适合所有团队，但它提出的问题很真实：<strong>Rust 并不只需要最终正确性工具，也需要服务于开发中间态的工程化工具</strong>。</p>
<p>对 Rust 生态来说，这种文章和小 crate 的价值，在于它不是又一个“性能/安全/框架”话题，而是在认真讨论 <strong>如何更顺手地写 Rust</strong>。当语言逐渐进入更大团队、更复杂产品时，开发期体验本身也会越来越值得被单独产品化。</p>
<p>原文：https://blog.dureuill.net/articles/wip/
项目地址：https://codeberg.org/dureuill/wip/</p>
<p>原文链接：https://blog.dureuill.net/articles/wip/</p>
<h2>hiper 0.5.0：不用 proc-macro、零中间分配的 Rust HTML 模板 DSL</h2>
<p>如果你一直把 Rust 模板生态大致分成 <code>askama</code> 这类“更接近传统模板文件”和 <code>maud</code> 这类“宏里直接写标记”，那 <strong>hiper 0.5.0</strong> 这次会挺值得看。作者给它的定位非常清楚：做一个 <strong>语法尽量接近 maud</strong>，但又坚持 <strong>不用 procedural macro、尽量零中间分配、甚至零依赖</strong> 的渲染 DSL。换句话说，它不只是再做一个“也能写 HTML 的宏”，而是在模板 ergonomics 和底层约束之间走了一条很明确的路线。</p>
<p>README 里最值得注意的，是它把这些卖点落到了具体设计上：模板可以像 Rust 代码一样通过函数组合；宏语法支持 tag、id、class、条件 class、可选属性和值表达式；渲染时尽量避免中间字符串分配。作者还专门把它和 <strong>maud、horrorshow、vy、askama</strong> 摆在一起比较，等于主动回答“为什么已经有这么多模板方案了，你还要再做一个”。其中最核心的差异点就是：<strong>hiper 追求的是声明宏方案下尽可能接近现代模板 DSL 的写法</strong>。</p>
<p>这条项目的意义不一定在于它马上取代谁，而在于它继续把 Rust Web/UI 模板这条线往前拧了一圈：原来除了 <code>proc-macro + DSL</code>，还可以认真探索 <strong><code>decl_macro + 零分配</code></strong> 的路径。对一部分在意编译开销、依赖控制和渲染性能的开发者来说，这种取舍会很有吸引力。</p>
<p>如果后续它继续完善基准、生态集成和大型模板场景下的可维护性，hiper 很可能会在“偏极简、偏性能、偏 Rust 原生语法”的模板工具里占住自己的位置。</p>
<p>原文：https://github.com/lsunsi/hiper
crate 地址：https://crates.io/crates/hiper
文档地址：https://docs.rs/hiper/latest/hiper/</p>
<p>原文链接：https://github.com/lsunsi/hiper</p>
<h2>efimux：Ratatui 跑进 UEFI，做出一个真正可用的 EFI 多路启动器</h2>
<p><strong>efimux</strong> 这条很容易让人眼前一亮，因为它把不少人印象里“更像 Linux 用户态 TUI 工具”的 <strong>Ratatui</strong>，直接带进了 <strong>UEFI</strong> 场景。项目的定位非常直白：它是一个 <strong>EFI application multiplexer</strong>，也就是在启动阶段用于引导其他 EFI 应用的多路入口。比起单纯展示“Rust 也能写 UEFI 程序”，这更接近一个能被实际拿来玩的 end-user 工具。</p>
<p>从工程意味上看，这个项目好玩的地方在于，它不是在普通终端里跑，而是在非常受限、很少有人会首先想到做“界面体验”的启动环境里，把 TUI 这层用户交互做了出来。作者 README 里明确提到它基于 <code>uefi-rs</code> 教程路线，同时提供了相对完整的制作方式：分区、格式化 FAT、把生成的 <code>efimux.efi</code> 放到 <code>efi/boot/bootx64.efi</code>。这说明它不是概念截图，而是一个确实能被写进 U 盘、实际启动测试的项目。</p>
<p>如果只从传播性来看，efimux 的亮点也很足：<strong>“Ratatui in UEFI”</strong> 本身就很抓人。Rust 这些年在嵌入式、系统启动链路、no_std 等方向一直有稳定受众，但很多项目仍然偏底层能力展示；efimux 更接近“把底层能力做成一个一眼能看懂用途的作品”。这类项目往往特别适合给外部开发者建立直觉：Rust 不只是能写内核、驱动、loader，也能在这些地方写出像样的交互界面。</p>
<p>如果后面它继续扩展对更多 EFI 应用的管理体验、启动项组织或者更成熟的设备发现逻辑，这条线完全可能从“有趣作品”继续长成一个更像样的启动工具。</p>
<p>原文：https://github.com/sermuns/efimux
Ratatui：https://ratatui.rs/</p>
<p>原文链接：https://github.com/sermuns/efimux</p>
]]></description><pubDate>2026-07-06 01:07:20</pubDate></item><item><title>KeyCompute 新功能发布：缓存架构升级、数据库读写分离与多项体验优化</title><link>https://rustcc.cn/article?id=221320f5-84e6-4e69-a914-7d69e0fbeebc</link><description><![CDATA[<h3>概述</h3>
<p>本次更新聚焦于 KeyCompute 基础设施的稳定性、可扩展性与用户体验提升。我们移除了内存缓存层，全面拥抱 Redis 作为唯一的缓存方案；实现了主从数据库读写分离与自动路由；构建了 Redis 统一缓存服务层，并集成了分布式限流、分布式锁与任务队列优化。同时，修复了多个前端与后台的 i18n 相关问题，优化了日志输出格式。</p>
<h3>主要变化</h3>
<h4>缓存架构：移除内存缓存，仅保留 Redis</h4>
<p>移除了原有的内存缓存实现，缓存层统一收归至 Redis。<strong>不再提供降级方案</strong>——当 Redis 不可用时，系统将回退至不使用缓存的状态，直接访问数据库。这一调整简化了缓存层的维护复杂度，避免了内存缓存与 Redis 缓存之间的数据不一致问题，也让缓存策略更加清晰可预期。</p>
<h4>数据库：主从读写分离 + 自动路由</h4>
<p>实现了基于主从架构的数据库读写分离，应用层支持<strong>自动 DB 路由</strong>：</p>
<ul>
<li><strong>写操作（INSERT/UPDATE/DELETE）</strong> 自动路由到主库</li>
<li><strong>读操作（SELECT）</strong> 自动路由到从库</li>
</ul>
<p>业务代码无需感知底层数据库拓扑，框架层根据 SQL 语句类型自动完成路由决策，对上层服务完全透明。这一设计有效分摊了数据库读压力，提升了整体吞吐量。</p>
<h4>Redis 统一缓存服务层</h4>
<p>构建了统一的 Redis 缓存服务层，承载以下能力：</p>
<ul>
<li><strong>分布式限流</strong>：基于 Redis + Lua 脚本实现原子性的 ZSET 滑动窗口限流，支撑多节点下的流量管控</li>
<li><strong>分布式锁</strong>：基于 Redis 实现跨节点的互斥锁，保障关键资源的并发安全</li>
<li><strong>任务队列优化</strong>：基于 Redis 的任务队列机制重构，提升了任务投递与消费的可靠性和性能</li>
</ul>
<h4>前端与后台体验修复</h4>
<ul>
<li><strong>注册与需求提交登录框</strong>：修复了前端注册、需求提交场景下登录框不支持 i18n 国际化的问题</li>
<li><strong>后台支付与订单界面</strong>：修复了后台支付、订单管理界面不支持 i18n 的问题</li>
<li><strong>语言切换与主题切换</strong>：修复了语言切换与主题切换互相干扰的 bug，两者现在独立运作</li>
</ul>
<h4>后台复制框优化</h4>
<p>修复了后台复制框在 HTTP 环境路径下不可用的问题，现在无论部署在 HTTP 还是 HTTPS 环境下，复制功能均可正常工作。</p>
<h4>日志格式优化</h4>
<p>日志输出统一升级为 <strong>FULL JSON 格式</strong>，每条日志包含完整的结构化字段（时间戳、级别、目标、消息、上下文字段等），便于接入 ELK/Loki 等日志采集系统进行统一检索与分析。</p>
<h3>为什么这些更新重要</h3>
<p><strong>缓存层的简化</strong>降低了系统的维护复杂度与故障排查成本；<strong>读写分离</strong>为 KeyCompute 支撑更大规模的 AI 请求流量奠定了基础——随着平台接入的模型与用户不断增加，数据库读压力将成为首要瓶颈；<strong>Redis 统一服务层</strong>为分布式限流、分布式锁等能力提供了标准化的基础设施，避免了各模块各自为政的实现碎片化。</p>
<p>i18n 问题的集中修复则显著提升了国际化场景下的用户体验，尤其对于海外用户和 multi-tenancy 部署场景至关重要。</p>
<h3>致谢</h3>
<p>功能贡献者：aiqubits 等。</p>
]]></description><pubDate>2026-07-05 09:13:06</pubDate></item><item><title>【Rust日报】2026-07-05 Dimforge Q2 2026 技术报告：Nexus 跨平台 GPU 多物理引擎开始用 rust-gpu 跑起来</title><link>https://rustcc.cn/article?id=0e6b266d-6eb4-49e6-96d3-fd98c7251971</link><description><![CDATA[<h2>Dimforge Q2 2026 技术报告：Nexus 跨平台 GPU 多物理引擎开始用 rust-gpu 跑起来</h2>
<p>Dimforge 这份 Q2 技术报告里，最值得 Rust 社区关注的新东西，就是 <strong>Nexus</strong> 终于作为新旗舰项目正式亮相。它不是一个只跑原型 demo 的实验仓库，而是一个面向 <strong>跨平台、GPU、多物理场景</strong> 的新引擎：同一套体系要同时覆盖原生桌面、浏览器、机器人、机器学习训练等方向。对已经熟悉 Rapier / Kiss3d 的开发者来说，这基本可以看作 Dimforge 在“Rust 物理 + 图形 + WebGPU”这条线上的一次明显升级。</p>
<p>这套方案最有传播性的点，在于它把 shader 这一层也尽量往 Rust 侧收拢了。报告明确写到：Nexus 的 shader 是 <strong>100% Rust 编写</strong>，底层用了 <code>rust-gpu</code>、<code>khal</code> 和 <code>vortx</code>，而不是继续把核心逻辑留在 WGSL。Dimforge 给出的理由很直接：他们试过 WGSL、WESL、Slang 这些方案，但无论是 IDE 体验、跨 crate 复用，还是 CPU/GPU 结构对齐、调试便利性，最后都没有 Rust 方案顺手。更关键的是，这一版 Nexus 相比他们之前的 WGSL 路线，报告里明确给出一个很抓眼球的结果：<strong>整体大约快 2 倍</strong>，而且浏览器和原生之间的性能落差也更小。</p>
<p>能力层面也不只是“把 Rapier 搬上 GPU”这么简单。Nexus 现在已经支持 <strong>multibody joints、多 collider、动态插入刚体</strong>，还内建了 <strong>batch simulation</strong>，可以并行跑同一场景的成千上万个变体，明显就是在对准强化学习、训练数据生成和机器人仿真这类需求。配套 viewer 则建立在 Kiss3d 上，并且尽量避免 GPU→CPU 同步，把模拟和渲染都留在 GPU 上完成。报告里还特别提到，他们已经能用这套东西训练简单双足机器人完成站立和行走。</p>
<p>除了 Nexus，本季度 Dimforge 还把 Rapier 和 Kiss3d 一起往前推了一截：Rapier 0.34 增加了新的 <code>PhysicsWorld</code> 封装，开始更系统地补多体系统和 MJCF 支持；Kiss3d 则继续增强渲染效果，甚至上了硬件加速 path tracer。把这些拼起来看，这已经不是单点项目更新，而是在构 Rust 自己的跨平台科学计算 / 机器人 / 图形物理基础设施栈。</p>
<p>原文：https://dimforge.com/blog/2026/07/01/dimforge-Q2-technical-report
项目仓库：https://github.com/dimforge/nexus
在线演示：https://nexus.dimforge.com/demos</p>
<p>原文链接：https://dimforge.com/blog/2026/07/01/dimforge-Q2-technical-report</p>
<h2>TrueFix：Rust 版 FIX 协议引擎对齐 QuickFIX/J，卖方 / Acceptor 终于不是陪跑</h2>
<p>今天这条更偏金融基础设施，但技术含量不低。作者发布的 <strong>TrueFix</strong>，目标非常明确：做一个 <strong>生产可用</strong> 的 Rust FIX 协议引擎，而且不是只做买方 / initiator 方向的轻量封装，而是把 <strong>卖方 / acceptor</strong> 也当成一等公民认真做。作者在帖子里点名了现有 Rust 方案的空缺：要么更偏 initiator，要么还停留在 pre-1.0、离“真上生产”还有点距离，所以他干脆自己补了一套。</p>
<p>TrueFix 最能打的一点，是它不只是“号称对齐 QuickFIX/J”，而是把这个口号具体化成了 <strong>验收测试门槛</strong>。项目把 QuickFIX 的 acceptance test 路线移植了过来，并把它作为 release gate。仓库里给出的状态更猛：现在已经覆盖到 <strong>373 / 373 个场景</strong>，横跨 9 个 FIX 版本，并把这套 conformance 当成硬约束，而不是 README 里的 marketing 文案。对于 FIX 这种协议栈来说，这种“先把一致性打穿”的思路，本身就很容易建立信任。</p>
<p>架构上，TrueFix 也不是只把 parser 和 session state machine 拼起来就算了。它支持 <strong>FIX 4.0–5.0SP2 + FIXT 1.1</strong>，底层用统一的 dictionary 管线同时做 <strong>运行时校验</strong> 和 <strong>构建期 typed message codegen</strong>；运行层跑在 tokio 上，TLS 走 rustls，存储支持 Postgres / MySQL / SQLite，配置则尽量对齐传统 <code>.cfg</code> 启动模式，目标是把一个完整 engine 的启动复杂度尽量压缩到“配置驱动 + callback 填业务”这个层级。对一直想看 Rust 进入交易系统底座的人来说，这条很有分量。</p>
<p>更有意思的是，作者没有把“稳定”理解成保守，而是往工程硬度上继续加码：关键路径禁 <code>panic/unwrap</code>、workspace 级禁用 <code>unsafe</code>、支持 multi-session / dynamic sessions、TLS / mTLS、metrics export、failover、代理、背压等能力。它未必会立刻替代 QuickFIX/J，但至少说明 Rust 在金融协议引擎这种传统上很重 Java/C++ 的区域里，已经开始有人认真补齐“可以上线”的那一块了。</p>
<p>项目仓库：https://github.com/zhangjiayin/truefix
Reddit 讨论：https://old.reddit.com/r/rust/comments/1un9ztx/truefix_a_productiongrade_fix_protocol_engine_in/</p>
<p>原文链接：https://github.com/zhangjiayin/truefix</p>
<h2>memsafe v1.0.0：把 Secret 锁进受保护内存页，防 swap、防 core dump、闲置时直接不可读</h2>
<p>安全类 crate 每隔一段时间都会冒出来，但 <strong>memsafe</strong> 这次做法比较硬核。它想解决的不是“drop 时帮你 zeroize 一下”这么基础的问题，而是进一步把 secret 真正放进 <strong>受保护的内存页</strong>：避免被 swap 到磁盘、避免进 Linux core dump、闲置时直接把页权限切到不可访问，甚至在 Unix 上连你自己的进程都不能在未解封状态下随手读到它。这个方向一下就把它和常见的 <code>zeroize</code> / <code>secrecy</code> 之类区分开了。</p>
<p>作者对这个 crate 的产品定义也很清楚：如果你的敏感信息还待在 <code>String</code> / <code>Vec&lt;u8&gt;</code> 这种普通堆内存里，即便你后面做了清理，泄漏面也还是偏大。所以 memsafe 提供的 <code>Secret&lt;N&gt;</code> 会尽量把 secret <strong>直接写进受保护页</strong>，避免先落到常规 heap；对于已经持有 owned bytes 的情况，也会在复制后对源数据做显式 zeroization。底层则结合 <code>mlock</code> / <code>VirtualLock</code>、<code>mprotect</code>、<code>MADV_DONTDUMP</code> 等机制，把“不要换出、不要出现在 dump 里、非使用期不可读”这些目标串成一套更完整的防护链。</p>
<p>更难得的是，作者没有把它吹成“万能安全方案”，而是把 trade-off 写得很实。README 里直接给了 benchmark：创建一个 64B secret 大约要微秒级，单次读写 guard 也大约是 <strong>1 微秒</strong> 量级，远高于普通 heap，但对 API key、token、密码这类低频访问场景仍然很值。与此同时，它也明确列出不防什么：比如 root / ptrace、休眠落盘、侧信道、寄存器残留等。对安全工具来说，敢把边界讲清楚，本身就是加分项。</p>
<p>如果说很多 Rust 安全 crate 解决的是“语言层的误用”，那 memsafe 更像是在把 <strong>OS 级内存保护</strong> 包装成一个更不容易用错的安全抽象。对于需要在本地长期持有密钥、令牌、认证材料的桌面应用、CLI、代理服务来说，这类库会很有吸引力。</p>
<p>项目仓库：https://github.com/po0uyan/memsafe
crate 地址：https://crates.io/crates/memsafe
文档地址：https://docs.rs/memsafe</p>
<p>原文链接：https://github.com/po0uyan/memsafe</p>
<h2>p2p-tunnel：基于 Iroh 的点对点内网穿透，npx 一条命令把本机端口分享出去</h2>
<p>这条项目不大，但切的问题非常实用。作者用 <strong>Iroh</strong> 做了一个叫 <strong>p2p-tunnel</strong> 的小工具，目标不是再造一套复杂公网代理，而是把开发者最常见的一个动作做轻：<strong>把本机某个 localhost 端口，尽量无痛地临时分享给另一台机器</strong>。如果你平时为了演示一个本地服务，经常要在云主机、中转层、Ngrok、Cloudflare Tunnel 之间兜圈子，这个方向会很有代入感。</p>
<p>它的使用方式也刻意压到了最短路径。你可以本地直接跑 <code>cargo run -- share 3000</code>，也可以不装全局包，直接 <code>npx p2p-tunnel share 3000</code>；另一边拿到生成的 ticket 后，再执行 <code>connect &lt;TICKET&gt;</code> 就能接进来。项目 README 里还专门强调了 <strong>npm installer</strong> 和 <code>cargo-dist</code> 这套发布流程，明显就是想降低“Rust 工具很好，但同事装起来太麻烦”的现实门槛。</p>
<p>从传播性上说，这类项目打动人的并不是技术新颖度，而是它把 <strong>Iroh 这种 Rust P2P 能力</strong> 包装成了一个开发者马上能试的 end-user 工具。相比“看起来很强、但要自己读一堆 API 才能上手”的底层库，这种面向最终场景的小工具更容易让人迅速建立直觉：Rust 做网络工具，不只是高性能 server，也能做这种拿来就用的 developer utility。</p>
<p>现在的功能还很克制，比如 <code>connect</code> 默认只绑定到本地 <code>127.0.0.1:8080</code>，但作为第一版，它已经足够把“点对点分享本机服务”这个价值讲清楚。如果后面再补权限控制、更多端口策略、会话管理，这条线完全有机会变成不少开发者的轻量备用隧道工具。</p>
<p>项目仓库：https://github.com/drmikesamy/p2p-tunnel
npm 地址：https://www.npmjs.com/package/p2p-tunnel
Reddit 讨论：https://old.reddit.com/r/rust/comments/1un8hbi/a_p2p_alternative_to_ngrok_or_cloudflare_tunnels/</p>
<p>原文链接：https://github.com/drmikesamy/p2p-tunnel</p>
]]></description><pubDate>2026-07-05 01:10:27</pubDate></item><item><title>Looking for Execution Systems Dev Focus: Trading Infrastructure / Execution Systems</title><link>https://rustcc.cn/article?id=96725c50-b22c-45c7-9ee6-eb9ccfcc5d1a</link><description><![CDATA[<p>Requirements</p>
<ol>
<li>Programming &amp; Systems</li>
</ol>
<ul>
<li>熟悉 Rust ，精通 Python</li>
<li>对编译原理、Data Warehouse 有自己的理解</li>
</ul>
<ol start="2">
<li>Trading Pipeline Tooling
专注交易链路上的工具开发，包括：</li>
</ol>
<ul>
<li>data pipeline 、message bus 扩展</li>
<li>risk control tools</li>
<li>execution algo</li>
</ul>
<ol start="3">
<li>Framework</li>
</ol>
<ul>
<li>了解 Nautilus Trader 框架 https://github.com/nautechsystems/nautilus_trader</li>
</ul>
<p>Compensation</p>
<ul>
<li>Base Salary: $5,000 – $10,000 USD / month</li>
<li>Bonus: Project based bonus</li>
<li>Office Location: Hong Kong / Remote</li>
</ul>
<p>Contact Email: profilesai@proton.me
(请直接提交简历和个人 GitHub)</p>
]]></description><pubDate>2026-07-04 08:18:14</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>