<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-09-07 Gecko 模拟器大更新可玩 Galaxy</title><link>https://rustcc.cn/article?id=b4da6455-056e-4e46-a2fa-07c782cb634b</link><description><![CDATA[<h2>Gecko：GameCube/Wii 模拟器大幅提速与存档</h2>
<p>Gecko 是用 Rust 写的 GameCube/Wii 模拟器与调试器，目标同时覆盖 Homebrew、逆向与可玩体验。核心使用 Cranelift 做 PowerPC、DSP 与顶点解码 JIT，渲染走 wgpu，并提供 egui 调试 UI、FIFO 录制/回放、Lua 脚本与 MCP 等开发者工具。跨平台支持 Windows/Linux/macOS，以及 x86 与 ARM。</p>
<p>本轮更新带来整体性能提升，许多原先“能启动但帧率不够玩”的游戏现已可玩，例如 LEGO Batman。新增 savestates，可在任意时刻存档并恢复。调试器支持 freecam：解锁相机后可脱离游戏镜头观察场景。作者表示 Super Mario Galaxy 系列与多款 LEGO 游戏现已 fully playable，并放出 Galaxy 实机视频；dev/nightly 分支还有使 Xenoblade Chronicles 可工作的修复。GitHub 提供每日 nightly 构建，截图兼容库见 emu.layle.dev。作者还预告了基于同一核心的多系统模拟器，浏览器端 SNES 已可运行。</p>
<p>原文链接：https://github.com/ioncodes/gecko</p>
<h2>用 ESP32 在 Rust 里实现 STM32 的 SWD 编程器</h2>
<p>文章是嵌入式实验笔记：在没有 ST-Link 的情况下，用 ESP32 DevKit 充当 SWD 主机，对 STM32F103C8T6 Blue Pill 实现 Serial Wire Debug 通信。本篇完成用 Rust bit-bang SWD 读取目标 DP 的 IDCODE；真正刷写固件留到下一篇。</p>
<p>硬件上 ESP32 与 Blue Pill 各自 USB 供电，信号线为 GPIO18→SWCLK(PA14)、GPIO19→SWDIO(PA13)，并共地。文中从 SWD 双线协议讲起，用 Flex 管理双向 SWDIO，实现 <code>clock</code>、<code>write_bit</code>/<code>read_bit</code> 以及 LSB-first 的多位读写；并按 ARM 规范完成 JTAG→SWD 切换序列（不少于 50 个 SWDIO 高电平时钟周期、发送 0xE79E、再拉高 50 周期）。示例代码仓库为 implferris/swd-rust-idcode。</p>
<p>原文链接：https://blog.implrust.com/posts/2026/09/swd-protocol-programmer-embedded-rust/</p>
<h2>Empower：Rust + egui 可视化编程引擎</h2>
<p>Empower 是作者业余约三年开发的可视化编程引擎，用 Rust 与 egui 实现，架构接近游戏引擎，希望同时服务初学者与需要复杂逻辑的开发者。开发者在图形编辑器中搭程序，再导出为面向目标操作系统的独立可执行文件。</p>
<p>仓库拆成三块：Empower Engine 提供节点图、资源、项目、编译、执行与导出等基础能力；Empower Studio 是 immediate-mode GUI 前端，含停靠窗口、项目/导出编辑与可视化日志；Empower Application 是目标机上的运行时可执行文件，规划支持 Linux/macOS/Windows。当前首发定位为 proof of concept。本地可 <code>cargo build</code> 后 <code>cargo run -p empower-studio</code>。</p>
<p>原文链接：https://github.com/Stretchyfish/empower</p>
<h2>Drop 顺序应否写入函数 API 契约</h2>
<p>作者在实现类似 smallvec 的 toy crate 时，注意到 <code>Iterator::nth_back</code> 的默认实现会反复调用 <code>next_back</code>，元素按反向顺序 drop；若为性能改用 <code>ptr::drop_in_place</code> 批量销毁，则变成正向 drop。标准库 <code>std::vec::IntoIter::nth_back</code> 同样是正向 drop。</p>
<p>讨论点在于：Rust 里 drop guard 很常见，跳过中间元素时的析构顺序是否应被视为函数 API 契约的一部分，还是仅作实现细节。相关行为可对照 Rust Reference 的 destructors 章节。</p>
<p>原文链接：https://www.reddit.com/r/rust/comments/1w8o4wx/do_you_think_drop_order_should_be_part_of_a/</p>
<hr>
<p>From Rust中文社区 Mike</p>
<p>社区学习交流平台订阅：</p>
<ul>
<li><a href="https://rustcc.cn/" rel="noopener noreferrer">Rustcc论坛: 支持rss</a></li>
<li><a href="https://rustcc.cn/article?id=ed7c9379-d681-47cb-9532-0db97d883f62" rel="noopener noreferrer">微信公众号：Rust语言中文社区</a></li>
</ul>
]]></description><pubDate>2026-09-07 01:05:02</pubDate></item><item><title>一次相差 2378 倍的 SQLx 查询：TeaQL 如何把业务边界变成执行计划</title><link>https://rustcc.cn/article?id=e072b86b-268c-4818-9164-6bfefff660fc</link><description><![CDATA[<h1>一次相差 2378 倍的 SQLx 查询：TeaQL 如何把业务边界变成执行计划</h1>
<p>最近我们在 MusicBrainz 数据集上测试了这样一个业务请求：</p>
<blockquote>
<p>查询最新的 100 条、存在关联作品的 Recording，并为每条 Recording 加载最多 10 条 Work 关联。</p>
</blockquote>
<p>我们分别用两种 SQLx 查询方案执行了这个请求。两条路径返回了完全相同的结果：</p>
<ul>
<li>100 条 Recording；</li>
<li>103 条 Relation；</li>
<li>103 个 Link；</li>
<li>103 个 LinkType；</li>
<li>相同的 Work ID 校验值。</li>
</ul>
<p>但两种 SQLx 实现的耗时分别是：</p>
<table>
<thead>
<tr>
<th>实现方式</th>
<th align="right">PostgreSQL 中位耗时</th>
</tr>
</thead>
<tbody>
<tr>
<td>SQLx：直观的全局窗口查询</td>
<td align="right">5871.169 ms</td>
</tr>
<tr>
<td>SQLx：先选根对象的两阶段查询</td>
<td align="right"><strong>2.469 ms</strong></td>
</tr>
<tr>
<td>TeaQL Rust：类型化受治理图查询</td>
<td align="right">2.864 ms</td>
</tr>
</tbody>
</table>
<p>前两项是在同一个 SQLx 对照实验中测得的，性能差距约为 <strong>2378 倍</strong>。</p>
<p>TeaQL 的数据来自另一轮保留测试，用于提供背景参考，并不参与这个 2378 倍比值的计算。</p>
<p>因此，这个结果并不是说：</p>
<blockquote>
<p>TeaQL 的 PostgreSQL 驱动比 SQLx 快 2000 多倍。</p>
</blockquote>
<p>TeaQL 和优化后的 SQLx 最终都让数据库执行了范围明确的工作。真正造成巨大差异的，是两种执行计划所处理的数据量。</p>
<h2>“显而易见”的 SQLx 写法</h2>
<p>面对“每个父对象最多取 N 条子记录”这样的需求，熟悉 SQL 的开发者通常会想到窗口函数：</p>
<pre><code>WITH ranked AS (
    SELECT
        relation.*,
        row_number() OVER (
            PARTITION BY relation.entity0
            ORDER BY relation.link_order ASC, relation.id DESC
        ) AS rn
    FROM recording_work_relation relation
)
SELECT ...
FROM recording
JOIN ranked
    ON ranked.entity0 = recording.id
   AND ranked.rn &lt;= 10
WHERE ...
ORDER BY recording.id DESC
LIMIT 100;
</code></pre>
<p>从 SQL 表达能力来看，这条语句没有问题：</p>
<ol>
<li>按照 Recording 对 Relation 分组；</li>
<li>对每组 Relation 排序；</li>
<li>每组最多保留 10 条；</li>
<li>最终返回最新的 100 条 Recording。</li>
</ol>
<p>它看起来也很“优雅”：一个 SQL 语句、一个窗口函数，一次完成查询。</p>
<p>问题在于，数据库可能需要先对整个 Relation 集合进行分组和排序，然后才能知道哪些 Relation 属于最终选中的 100 条 Recording。</p>
<p>在这次 MusicBrainz 测试中，这个直观查询为了最终返回 103 条 Relation，对大约 <strong>270 万行数据</strong>进行了排名处理。</p>
<p>应用只需要一个很小的页面，数据库却先完成了一次全局工作。</p>
<h2>业务请求其实给出了更强的边界</h2>
<p>原始请求并不是：</p>
<blockquote>
<p>找出所有 Recording 的前 10 条 Relation，再从中选择 100 条 Recording。</p>
</blockquote>
<p>它真正表达的是：</p>
<ol>
<li>先确定最新的 100 条目标 Recording；</li>
<li>只为这 100 条 Recording 查询 Relation；</li>
<li>每个 Recording 最多加载 10 条 Relation；</li>
<li>只加载这些 Relation 引用的 Link、LinkType 和 Work。</li>
</ol>
<p>一旦先确定根对象，后续所有工作都可以限定在这 100 个 Recording ID 之内。</p>
<p>这意味着数据库不必对 270 万条 Relation 做全局排名。它只需要处理当前页面真正可能用到的数据。</p>
<p>这不是数据库驱动的性能差异，而是业务边界是否及时进入执行计划的差异。</p>
<h2>优化后的 SQLx：先选择根对象</h2>
<p>为了验证这一点，我们又手工编写了一版优化后的 SQLx 查询。</p>
<p>它采用两阶段执行：</p>
<h3>第一阶段：查询根对象</h3>
<p>先找出符合条件的 100 个 Recording ID：</p>
<pre><code>SELECT recording.id
FROM recording
WHERE EXISTS (
    SELECT 1
    FROM recording_work_relation relation
    WHERE relation.entity0 = recording.id
)
ORDER BY recording.id DESC
LIMIT 100;
</code></pre>
<h3>第二阶段：只处理这些根对象的关联</h3>
<p>然后把第一阶段得到的 ID 作为参数传给第二个查询：</p>
<pre><code>WITH ranked AS (
    SELECT
        relation.*,
        row_number() OVER (
            PARTITION BY relation.entity0
            ORDER BY relation.link_order ASC, relation.id DESC
        ) AS rn
    FROM recording_work_relation relation
    WHERE relation.entity0 = ANY($1)
)
SELECT ...
FROM ranked
JOIN link ...
JOIN link_type ...
JOIN work ...
WHERE ranked.rn &lt;= 10;
</code></pre>
<p>窗口函数仍然存在，但它只对已经选中的 Recording 所对应的 Relation 进行排名。</p>
<p>查询结果没有变化，耗时却从 5871.169 ms 降到了 2.469 ms。</p>
<p>这说明 SQLx 本身没有性能问题。只要开发者明确写出正确的执行方案，SQLx 同样可以非常快。</p>
<p>真正的问题是：应用开发者是否应该在每一个类似场景里，亲自发现、实现并维护这种优化？</p>
<h2>TeaQL 如何表达同一个请求</h2>
<p>TeaQL 是一个模型驱动的应用运行时。它从语义模型生成类型化的查询 API、已加载状态表达式和受治理的图写入 API。</p>
<p>它目前可以面向 Rust、Java、TypeScript、Go、Swift、.NET 和 Python 生成相应的语言 API。</p>
<p>在 TeaQL 中，应用代码声明的是业务请求的形状。简化后可以理解为：</p>
<pre><code>Q::recordings()
    // 只选择存在 Work 关联的 Recording
    .which_have_work_relations()

    // 加载 Recording 的 Work 关联
    .select_work_relations_with(
        Q::recording_work_relations()
            .select_link()
            .select_link_type()
            .select_work()
            .order_by_link_order_asc()
            .order_by_id_desc()
            .limit(10)
    )

    // 根对象按 ID 倒序，只取 100 条
    .order_by_id_desc()
    .limit(100)

    // 保留查询意图和操作目的
    .comment("加载 MusicBrainz Recording-Work 图")
    .purpose("展示有界的 Recording-Work 明细")

    .execute_for_list(&amp;context)
    .await?;
</code></pre>
<p>以上代码是为了说明请求结构而做的简化示意，完整可执行版本可以在公开的基准仓库中找到。</p>
<p>这里最重要的并不是 Rust API 的具体命名，而是 TeaQL 能同时获得这些信息：</p>
<ul>
<li>根对象最多 100 条；</li>
<li>每个根对象的 Relation 最多 10 条；</li>
<li>Relation 有明确的排序方式；</li>
<li>只需要加载被引用的 Link、LinkType 和 Work；</li>
<li>查询需要继承租户、权限、版本和审计策略；</li>
<li>查询携带 <code>comment</code> 和 <code>purpose</code> 等可观察性信息。</li>
</ul>
<p>有了这些语义，运行时就可以先选择根对象，再把关联查询限定在这些根对象之内。</p>
<p>应用代码不需要退回手写 SQL，也不需要自己维护两条查询之间的语义一致性。</p>
<h2>代码量并没有出现“魔法般”的减少</h2>
<p>我们还统计了三种实现中用于声明和执行查询的非空代码行数。</p>
<p>连接池初始化、计时、结果校验和报告代码没有计入；应用直接持有的 SQL 文本则计入代码量。</p>
<table>
<thead>
<tr>
<th>实现方式</th>
<th align="right">查询代码行数</th>
</tr>
</thead>
<tbody>
<tr>
<td>SQLx：直观的全局窗口查询</td>
<td align="right">26 行</td>
</tr>
<tr>
<td>SQLx：先选根对象的专家方案</td>
<td align="right">34 行</td>
</tr>
<tr>
<td>TeaQL：完整展开的类型化图查询</td>
<td align="right">27 行</td>
</tr>
</tbody>
</table>
<p>这个数字并不夸张，TeaQL 也没有通过代码高尔夫制造一种“只需一行”的错觉。</p>
<p>它完整展开后的查询，与直观 SQL 的代码量基本相当。</p>
<p>区别主要在于这些代码表达和保留了什么：</p>
<ul>
<li>优化后的 SQLx 需要维护两条 SQL；</li>
<li>应用负责传递根对象 ID；</li>
<li>应用负责数组参数绑定；</li>
<li>两个查询阶段必须使用一致的过滤条件；</li>
<li>开发者还要自己组装类型化对象图。</li>
</ul>
<p>TeaQL 则在同一个请求中声明一次根对象边界、关联边界和治理语义，由运行时负责将它们转化为相应的执行步骤。</p>
<p>所以这里的优势不是“代码更短”，而是减少需要由应用开发者手工维护的执行细节。</p>
<h2>性能只是问题的一半</h2>
<p>有经验的 SQL 开发者完全可以写出 2.469 ms 的 SQLx 方案。这次实验已经证明了这一点。</p>
<p>更值得关注的问题是：每一次手工优化都会增加新的策略执行点。</p>
<p>例如，在两阶段查询中：</p>
<ul>
<li>第一条查询可能包含租户条件，第二条遗漏了；</li>
<li>根对象应用了权限范围，子对象却没有；</li>
<li>一边应用了软删除或版本策略，另一边没有；</li>
<li>隐私字段屏蔽、审计信息和链路追踪可能在改写过程中丢失。</li>
</ul>
<p>当 AI 编程工具参与查询优化时，这类问题会更加隐蔽。生成的 SQL 可能确实更快，却在第二阶段意外加载了其他租户的子对象。</p>
<p>TeaQL 的目标并不是阻止优化，而是让两个查询阶段继续处于同一个受治理的执行模型中。</p>
<p>性能优化可以由运行时完成，但租户隔离、授权范围、版本策略、加载状态以及操作意图不能因此丢失。</p>
<h2>“一个 SQL”不一定比“多个 SQL”更高效</h2>
<p>这个实验也说明，单纯用查询次数评价性能是不够的。</p>
<p>“一次数据库往返”听起来通常优于“两次数据库往返”，但一个看起来很漂亮的单语句查询，可能让数据库处理数百万条与当前页面无关的数据。</p>
<p>相比之下，两条范围明确的查询可能只处理几百条记录。</p>
<p>因此，更有意义的问题不是：</p>
<blockquote>
<p>这个功能执行了几条 SQL？</p>
</blockquote>
<p>而是：</p>
<blockquote>
<p>数据库为完成这个业务请求，实际做了多少工作？</p>
</blockquote>
<p>查询次数只是成本的一部分。参与扫描、连接、排序和排名的数据量，往往更重要。</p>
<h2>这次基准证明了什么</h2>
<p>它证明了两件事情。</p>
<p>第一，业务 API 中的语义信息可以帮助运行时避免大量无效的数据库工作。</p>
<p>第二，根对象数量、关联数量和排序规则等业务边界，如果能在执行计划形成之前被保留下来，就可能产生非常显著的性能差异。</p>
<h2>它没有证明什么</h2>
<p>这次实验并不意味着：</p>
<ul>
<li>TeaQL 的 PostgreSQL 驱动天然比 SQLx 快；</li>
<li>所有窗口函数都很慢；</li>
<li>多条 SQL 永远优于一条 SQL；</li>
<li>2378 倍可以推广到其他数据集、硬件、索引或数据库；</li>
<li>TeaQL 在所有场景中都比 SQLx 快 2378 倍。</li>
</ul>
<p>更准确的结论是：</p>
<blockquote>
<p>直观的 SQLx 查询为了返回当前页面所需的 103 条 Relation，对约 270 万行数据进行了排名。先应用业务边界，再执行窗口排名后，绝大部分工作都消失了。</p>
</blockquote>
<p>TeaQL 的价值不在于拥有一个“更快的数据库驱动”，而在于让正确的边界可以被声明、类型检查、复用，并在统一的治理模型中执行。</p>
<h2>如何复现实验</h2>
<p>完整的测试代码和证据保存在公开仓库中：</p>
<p>https://github.com/teaql/teaql-runtime-benchmark</p>
<p>其中包含四组相关测试：</p>
<ul>
<li><strong>B001</strong>：Rust TeaQL、Diesel 和 SeaORM 在 MusicBrainz 上的类型化图查询；</li>
<li><strong>B002</strong>：PostgreSQL 和 DuckDB 上的原生 JDBC 测试；</li>
<li><strong>B003</strong>：Java TeaQL 在 PostgreSQL 和 DuckDB 上的测试；</li>
<li><strong>B004</strong>：本文使用的两种 Rust SQLx 查询方案及正确性校验。</li>
</ul>
<p>B004 保存了数据来源、查询结构、运行环境、预热次数、测量结果、返回数量和校验值，还包含一个可执行的代码行数统计工具。</p>
<p>2378 倍是一个真实、可复现但适用范围明确的结果。</p>
<p>更广泛的 TeaQL 主张并不是“永远比 SQLx 快”，而是：</p>
<blockquote>
<p>让好的执行方案成为可声明、类型化、可复用且受治理的业务查询，而不是散落在应用中的一次性手工优化。</p>
</blockquote>
<p>英文原文：</p>
<p>https://teaql.io/blog/teaql-2000x-obvious-sqlx-query/</p>
]]></description><pubDate>2026-09-06 13:54:35</pubDate></item><item><title>【Rust日报】2026-09-06 图解 dyn Trait 的 vtable</title><link>https://rustcc.cn/article?id=005bac8d-7d7f-44e6-89da-37ec8792de70</link><description><![CDATA[<h2>图解 Rust dyn Trait 的 vtable</h2>
<p>文章是 C++ 开发者视角的内存实验笔记，对照 C++ virtual functions / CRTP 与 Rust 的 monomorphization、<code>dyn Trait</code>。代码与实验在 GitHub 仓库 sofiabelen/rust-vtables-for-cpp-programmers。</p>
<p>静态分发下，编译器为每个具体类型生成函数副本，运行时无额外开销，但类型须在编译期已知。空结构体在 Rust 中是 ZST，<code>size_of</code> 可为 0；身份由所有权与借用检查跟踪，不依赖“对象至少 1 字节”的地址约定。debug 下本地 ZST 可能占占位地址，release 下地址可折叠，编译器不对 ZST 地址作保证。</p>
<p>动态分发使用 <code>&amp;dyn Draw</code>：普通引用约 8 字节，trait object 约 16 字节，是 data 指针加 vtable 指针的宽指针；同类型实例共享同一 vtable。调度策略在调用点选择：<code>&amp;Circle</code> 静态，<code>&amp;dyn Draw</code> 动态，vtable 不嵌在对象内。一个类型实现多个 trait 时，每个 (Type, Trait) 对有独立 vtable。可 <code>dyn</code> 的 trait 需满足 object safety，例如方法不能返回 <code>Self</code>、不能带泛型参数。</p>
<p>原文链接：https://sofiabelen.github.io/projects/visualizing-rusts-vtables-how-dyn-trait-works-in-memory/</p>
<h2>GSim-RS：体积毛坯 G-code 仿真</h2>
<p>GSim-RS 是用 Rust 写的 G-code 仿真器，面向立式 CNC 铣床的 Fanuc 风格 G-code。解析并解释 G-code，维护机床状态，并对刀路与切削做体积仿真。新版本支持立方体毛坯体积仿真，毛坯与刀具尺寸可通过程序 JSON 配置；仿真支持环绕、平移、缩放，以及等距/顶视/前视/右视切换。</p>
<p>架构拆成解析、解释、几何构建、仿真渲染与机床状态渲染。GUI 用 WGPU 在主线程做 G-code 解析、执行与仿真渲染，TUI 用 Ratatui 在另一线程显示活动机床状态。线程间按数据类型分别用 <code>Arc&lt;Mutex&gt;</code>（可丢弃更新）和 <code>mpsc::channel</code>（必达）通信。体积仿真对每帧做部分 GPU buffer 更新，避免整块 stock 重传；内向体素面启动时隐藏，邻接体素被切掉后再显露。可 <code>cargo install gsim-rs</code> 安装。</p>
<p>原文链接：https://github.com/navrajkalsi/gsim-rs</p>
<h2>shotdock：Wayland 截图录屏与窗口装帧</h2>
<p>shotdock 是面向 Wayland 的截图与录屏工具，带窗口装帧和可选浮动 dock，支持 Hyprland、Sway、River、Niri 等合成器。区域/窗口 snip 基于 slurp，可对截图加圆角、高斯阴影、macOS 风格标题栏和渐变画布；也支持 <code>shotdock frame &lt;file&gt;</code> 对已有图片离线装帧。</p>
<p>功能还包括区域选择时冻结屏幕（hyprpicker）、60 FPS H.264 录屏（wf-recorder，监视器/窗口/区域）、OCR 抽文字到剪贴板（tesseract），以及可选的 GTK4 LayerShell 浮动工具栏。可通过 AUR、GitHub Release 二进制或 <code>cargo build --release</code> 安装；依赖 grim、slurp、ImageMagick、wl-clipboard 等。</p>
<p>原文链接：https://github.com/sirrryasir/shotdock</p>
<h2>jsonquery_gui：egui 大 JSON 多方言查询</h2>
<p>jsonquery_gui 是用 Rust 与 egui/eframe 写的原生桌面应用，用于浏览和查询较大 JSON：拖入文件或粘贴内容后，可用 jq、JSON Pointer、JSONPath 或 JMESPath 查询。jq 通过内嵌 jaq 解释器执行，不 shell 到外部进程；查询可流式输出且可取消，大整数按字节精确往返，并支持 NDJSON。</p>
<p>界面提供源文档与结果的树视图/原文视图切换、搜索、右键复制节点路径或保存节点；Linux 与 Windows 有预编译包，Linux 另有 Homebrew tap。当前 Phase 1 为全量内存解析，适合中小文件；多 GB 级需尚未实现的 Phase 2 内存映射懒解析索引。MIT 许可。</p>
<p>原文链接：https://github.com/nujufas/jsonquery_gui</p>
<hr>
<p>From Rust中文社区 Mike</p>
<p>社区学习交流平台订阅：</p>
<ul>
<li><a href="https://rustcc.cn/" rel="noopener noreferrer">Rustcc论坛: 支持rss</a></li>
<li><a href="https://rustcc.cn/article?id=ed7c9379-d681-47cb-9532-0db97d883f62" rel="noopener noreferrer">微信公众号：Rust语言中文社区</a></li>
</ul>
]]></description><pubDate>2026-09-06 01:03:55</pubDate></item><item><title>【Rust日报】2026-09-05 OpenVMM 跨平台 Rust VMM 进展</title><link>https://rustcc.cn/article?id=c6cf73ee-aa12-4a74-a0f8-f460fcf28769</link><description><![CDATA[<h2>OpenVMM：微软开源跨平台 Rust VMM 进展</h2>
<p>OpenVMM 是微软用 Rust 写的开源跨平台 VMM（Virtual Machine Manager），MIT 许可，仓库为 microsoft/openvmm。它既可在 OpenHCL paravisor 中作为客户机内执行环境组件，也可作为宿主机上的传统 VMM。自 2024 年开源以来，社区已有 100+ 贡献者与 200+ forks，Intel 与 Arm 等持续贡献。</p>
<p>跨平台方面增强了 MSHV 后端的 aarch64 支持，并改进 KVM 虚拟化正确性。设备模型新增 Arm VM 的 SMMUv3 仿真、CXL 仿真、PCIe NUMA/ACS 暴露、现代 VFIO 直通接口，以及 NetVSP 网络的完整 VLAN 支持。机密计算上，Intel TDX 机密 VM 在 Azure 的 paravisor 支持已 GA，CPU/内存密集负载下开销约从 3% 降到 1%；Arm CCA Realm 基础使能已合入，AMD SEV-SNP 持续优化。宿主支持 Linux / Windows / macOS，后端包括 MSHV、WHP、KVM、hypervisor.framework，架构覆盖 x64 与 AArch64。文档站点：https://openvmm.dev</p>
<p>原文链接：https://techcommunity.microsoft.com/blog/windowsosplatform/the-openvmm-project/4547237</p>
<h2>p2pmux：多用户点对点终端复用器</h2>
<p>p2pmux 是面向 macOS 与 Linux 的点对点终端复用器。每个 pane 是开启者机器上的真实 PTY，参与者可带着自己的环境、密钥与工具加入同一会话，并 hop 到他人 pane，而不是 SSH 进同一台机器。连接基于 iroh 1.0，端到端加密，必要时经 relay；标签栏可显示 direct / relayed 延迟。</p>
<p>交互接近 Zellij：Ctrl+P 管 pane、Ctrl+T 管 tab；Ctrl+O 打开 inbox，汇总会话内各机器上检测到的 coding agent。上限约 8 名成员、9 个 tab、每 tab 8 个 pane。协调者笔记本合盖后，其他机器上的 pane 继续跑；约五分钟后由最早加入的存活节点接管。可用 <code>curl -fsSL https://p2pmux.com/install.sh | sh</code>、Homebrew 或 <code>cargo install p2pmux --locked</code> 安装。join code 等同密码，需按信任模型分享。</p>
<p>原文链接：https://github.com/pelazas/p2pmux</p>
<h2>PurRDF 1.0.0：一次实现的跨语言 RDF 1.2 引擎</h2>
<p>PurRDF 是用 Rust 实现一次、再带到 Python、WebAssembly/JavaScript 与 C 的 RDF 1.2 工具集，目标是同一图在各语言中语义一致。支持 Turtle、TriG、N-Triples、N-Quads、RDF/XML、TriX、HexTuples、JSON-LD、YAML-LD；含 SPARQL 1.1 与部分 1.2、SHACL、ShEx、entailment、确定性序列化/规范化、内容寻址 dataset pack 与 GTS 传输。</p>
<p>在 Rust 宿主上，SPARQL 还可做确定性 BM25 全文检索、基于精确有理数几何的 GeoSPARQL，以及向量 kNN；作者称这三类常要旁挂 PostgreSQL 的能力可在同一数据集内完成。工作区刻意无 Cargo feature flags，避免条件编译造成语义分叉。1.0 起按常规语义化版本发布；MIT OR Apache-2.0，覆盖约 21 个 Rust crate 以及 PyPI 与 npm。优化后的 wasm 约 11.1 MB。</p>
<p>原文链接：https://github.com/Blackcat-Informatics/purrdf</p>
<h2>MiniPdf：无 Office 依赖的 Office 转 PDF</h2>
<p>MiniPdf 是把 Office 文档直接转为 PDF 的轻量库与 CLI，运行时不依赖 Microsoft Office、LibreOffice、Adobe Acrobat 或 COM。面向容器、serverless、CI 与跨平台应用。Rust 实现目前为实验性，支持 XLSX / DOCX / PPTX；同仓库另有更成熟的 .NET 实现，以及 Java / Python / Node.js / Go 等实验路径。</p>
<p>Rust 侧可 <code>cargo add minipdf</code> 嵌入，或 <code>cargo install minipdf-cli</code> 使用命令行，例如 <code>minipdf report.docx -o output.pdf</code>，并支持注册自定义字体。输出 PDF 1.4；Apache-2.0。作者说明目标是实用转换而非完整还原复杂 Office 版式，复杂模板可能有差异，仓库提供视觉 benchmark 报告。</p>
<p>原文链接：https://github.com/mini-software/MiniPdf</p>
<hr>
<p>From Rust中文社区 Mike</p>
<p>社区学习交流平台订阅：</p>
<ul>
<li><a href="https://rustcc.cn/" rel="noopener noreferrer">Rustcc论坛: 支持rss</a></li>
<li><a href="https://rustcc.cn/article?id=ed7c9379-d681-47cb-9532-0db97d883f62" rel="noopener noreferrer">微信公众号：Rust语言中文社区</a></li>
</ul>
]]></description><pubDate>2026-09-05 01:03:38</pubDate></item><item><title>【Rust日报】2026-09-04 Rust 1.98.1 修复 vtable 误编译</title><link>https://rustcc.cn/article?id=de6680f7-1e30-4ee4-aea4-dd01d759a40f</link><description><![CDATA[<h2>Rust 1.98.1 修复 vtable 误编译</h2>
<p>Rust 1.98.1 是 1.98.0 的点版本，修复 vtable 生成中的误编译。在 1.98.0 的某些情况下，rustc 会在 trait object vtable 里写入空指针而非函数指针，导致未定义行为，可能表现为段错误或其他任意效果。</p>
<p>已用 rustup 安装的用户执行 <code>rustup update stable</code> 即可升级。相关跟踪 issue 为 rust-lang/rust#161441。</p>
<p>原文链接：https://blog.rust-lang.org/2026/09/03/Rust-1.98.1/</p>
<h2>static-generics：零成本泛型静态量</h2>
<p>static-generics 是为零成本泛型静态量提供的 crate。Rust 没有 C++ 式 generic statics，常见 <code>HashMap&lt;TypeId, …&gt;</code> 每次访问都要加锁、分配与哈希。该 crate 用内联汇编与 monomorphization 发出 per-T 存储，支持平台上访问约 1–2 条指令（<code>lea</code> / <code>adrp</code> 等）；不支持平台回退 <code>HashMap</code> + <code>Mutex</code>。</p>
<p>仅 <code>Zeroable</code> 类型可直接存放；无 Drop，跨 crate / 动态库不保证同一地址。支持 x86_64、aarch64、riscv、loongarch、powerpc、s390x 等；wasm32 需 nightly <code>asm!()</code>。思路参考 cynecx/generic-statics。</p>
<p>原文链接：https://crates.io/crates/static-generics</p>
<h2>标准库验证竞赛：Autoharness 做到万级 harness</h2>
<p>这是 Rust Foundation 与 AWS 关于标准库验证竞赛的进展说明。标准库约 3.4 万函数；人工约一年写出 725 个 Kani harness 后增长停滞。AWS Kani 团队的 Autoharness 在 MIR 层自动生成 16748 个 harness，其中 11970 个通过 Kani 支持的 UB 检查，989 个带完整契约。</p>
<p>另有 VeriFast 对 <code>LinkedList</code> 的分离逻辑证明。未发现未知内存安全漏洞，但修了 SIMD 移位结果、unsafe 标注、SAFETY 注释与 panic 文档等问题。约 9600 个泛型函数以及并发/原子仍是缺口。RustConf 2026 将有专场更新。</p>
<p>原文链接：https://rustfoundation.org/media/how-the-rust-standard-library-verification-contest-scaled-past-manual-proof-engineering/</p>
<h2>tokio_rcu：面向 Tokio 的异步 RCU</h2>
<p>tokio_rcu 是面向 Tokio 的 RCU 库，提供 lock-free / wait-free 的共享状态更新。核心原语 <code>synchronize_rcu</code> 等待 grace period 以便回收被换出的数据；高层抽象是 <code>RcuPtr</code>。读路径基本是一次原子指针 load，适合读多写少、延迟可预测的场景。</p>
<p>静止状态定义为 Tokio 的 <code>on_after_task_poll</code>；RCU 保护的指针不能跨 await。需启用 <code>tokio_unstable</code>。仓库：https://github.com/roeeshoshani/tokio_rcu</p>
<p>原文链接：https://github.com/roeeshoshani/tokio_rcu</p>
<hr>
<p>From Rust中文社区 Mike</p>
<p>社区学习交流平台订阅：</p>
<ul>
<li><a href="https://rustcc.cn/" rel="noopener noreferrer">Rustcc论坛: 支持rss</a></li>
<li><a href="https://rustcc.cn/article?id=ed7c9379-d681-47cb-9532-0db97d883f62" rel="noopener noreferrer">微信公众号：Rust语言中文社区</a></li>
</ul>
]]></description><pubDate>2026-09-04 03:39:56</pubDate></item><item><title>【Rust日报】2026-09-03 FMA 揭出标准库缺陷</title><link>https://rustcc.cn/article?id=58f08623-83c3-48a0-9b59-1669c352a3ec</link><description><![CDATA[<h2>FMA 仿真揭出 C/Rust 标准库 rounding 缺陷</h2>
<p>FMA（fused multiply-add）是一次舍入完成 <code>a * b + c</code> 的浮点原语，用于高精度三角函数等。作者 Sergey "Shnatsel" Davidoff 为 <code>fearless_simd</code> 在无硬件 FMA 的平台上做 SIMD 仿真，按 Boldo/Melquiond 2008 年 Coq 证明算法实现后，发现 Rust 标准库 <code>f32::mul_add</code> / <code>std::simd</code> 以及 musl libc 的 <code>fmaf</code>（源自 FreeBSD 2005–2011 代码）在 subnormal 的 halfway rounding 上处理错误。</p>
<p>无 AVX2 的 x86（Firefox 硬件调查约 15%）仍需软件 FMA；作者的 SIMD 路径相对 <code>std::simd</code> 约快 5×。<code>fearless_simd</code> 已修复；compiler-builtins 补丁仍在审；musl 邮件列表已有修复讨论但尚未合并。可用 <code>a=0x97000800</code>、<code>b=0x1cfff001</code>、<code>c=0x00010002</code> 检查：正确结果 <code>0x00010001</code>，错误结果 <code>0x00010002</code>。</p>
<p>原文链接：https://shnatsel.github.io/implementing-fma-finding-bugs-in-std/</p>
<h2>rustup 1.29.1：并行检查更新与并发装组件</h2>
<p>rustup 是官方 Rust 工具链安装器。1.29.1 让 <code>rustup update</code> 先并行检查更新，<code>component add</code> 多个组件可并发安装；<code>rustup-init</code> 与部分 rustup 调用中不再隐式安装当前工具链并给出警告；<code>rustup doc</code> 新增 <code>--serve</code>，用本地 HTTP 提供文档。</p>
<p>在 64 位 Windows 安装 <code>i686-pc-windows-*</code> host 需 <code>--force-non-host</code>；取消安装时不再留下意外文件；修复 <code>rustup-init.sh</code> 在 Windows 上可能失败的问题；文案将 “target triple” 改为 “target tuple”（CLI 选项未破坏兼容）。正式支持 <code>aarch64-pc-windows-gnullvm</code> host。已安装用户可执行 <code>rustup self update</code> 或 <code>rustup update</code>。</p>
<p>原文链接：https://blog.rust-lang.org/2026/09/01/Rustup-1.29.1/</p>
<h2>rustc_codegen_gcc #43：unwinding 做到 100%</h2>
<p>rustc_codegen_gcc 是 rustc 的 GCC 代码生成后端，用于 LLVM 未覆盖的架构。Progress Report #43 称 unwinding 已从约 80% 做到 100%；函数/变量 attributes 从 22% 升到 60%。UI 测试通过 7699（+543），失败 21（-59）。</p>
<p>作者称 rustup 分发版本尚未包含该 unwinding 修复，同步问题大多已解决；还向 Rust unwind 库提交了修复。下一步计划先编译若干常用 crate，再做 crater run。赞助方包括 Futurewei、Shnatsel 与 Rust Foundation。</p>
<p>原文链接：https://blog.antoyo.xyz/rustc_codegen_gcc-progress-report-43</p>
<h2>Unstruct：把 XML 批量拆成可入库的 TSV</h2>
<p>Unstruct 是把一份或多份 XML 转成一份 UTF-8 TSV 的命令行工具，便于批量导入关系数据库。作者称该工具已处理数十亿条 XML CDR（通话详单）。通过 parser 配置选择输出列、recording 元素、过滤器、属性与命名空间；每个匹配的 recording 生成一行，嵌套 recording 继承外层值。</p>
<p>批处理 fail-fast：全部成功后原子写入输出，失败不发布部分结果。MIT 许可，仓库提供 BAG/CDR/FTP/统计等示例。</p>
<p>原文链接：https://github.com/Roenbaeck/unstruct</p>
<hr>
<p>From Rust中文社区 Mike</p>
<p>社区学习交流平台订阅：</p>
<ul>
<li><a href="https://rustcc.cn/" rel="noopener noreferrer">Rustcc论坛: 支持rss</a></li>
<li><a href="https://rustcc.cn/article?id=ed7c9379-d681-47cb-9532-0db97d883f62" rel="noopener noreferrer">微信公众号：Rust语言中文社区</a></li>
</ul>
]]></description><pubDate>2026-09-04 03:39:46</pubDate></item><item><title>rustpbx 0.5.0 超高性能的电话服务器(IPPBX)</title><link>https://rustcc.cn/article?id=ebe8910b-de31-4924-b7fa-35fb5b17a453</link><description><![CDATA[<h3>1. 媒体引擎全面重构：更快、更稳、更省</h3>
<p>0.5.0 把媒体桥接、转码、混音、录音统一到全新引擎上：</p>
<ul>
<li><strong>呼叫建立时延 ~6.4 ms</strong>（端到端），较上一代媒体层快 <strong>2.7 倍</strong>；</li>
<li>同编解码通话走<strong>零拷贝 fast-path 中继</strong>，单路仅 ~0.14% 核 / ~0.57 MB；</li>
<li><strong>WebRTC 与传统 SIP/RTP 互通</strong>是原生能力：800 路跨传输中继 0% 丢包，500 路 Opus↔PCMU 实时转码同样 0% 丢包；</li>
<li>18,000 通电话压力下内存平坦无泄漏，全程无序列/时间戳跳变——没有音频毛刺。</li>
</ul>
<h3>2. 5000 路并发全录音压测：0% 丢包，不到 4 核</h3>
<p>这不是实验室数字，是真实场景（CPS=200 呼叫风暴 + 全程立体声录音 + MySQL CDR）的完整回归：</p>
<table>
<thead>
<tr>
<th>阶段</th>
<th>rustpbx CPU</th>
<th>MySQL QPS</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>起呼 — 200 CPS INVITE 风暴</td>
<td>280–380%</td>
<td>2–6</td>
<td>认证与分机查询全部命中缓存</td>
</tr>
<tr>
<td>稳态 — 4,943 路并发，全部录音</td>
<td><strong>380–390% (≈3.9 核)</strong></td>
<td>2–4</td>
<td>录音单路成本仅 +0.13% 核 / +0.13 MB</td>
</tr>
<tr>
<td>拆链 — ~5k BYE/CDR 突发（~90 s）</td>
<td>递减</td>
<td>峰值 277</td>
<td>4980 个立体声 WAV（4.4 GB）零丢弃</td>
</tr>
</tbody>
</table>
<p>完成率 4943/5000 接听、100% 进展、0.00% 丢包，任务与话务对象全部回收，无泄漏。通话进行时数据库几乎空闲——容量规划简单到可以按 <strong>每路录音 ≈ 0.08 核</strong> 估算，线性扩展，16 核机器理论可扛 16000+ 路并发。</p>
<h3>3. 队列与呼叫中心能力进入社区版</h3>
<p>排队/ACD 是社区核心能力，0.5.0 补齐了生产级呼叫中心的最后几块拼图：</p>
<ul>
<li><strong>技能组路由</strong>与<strong>升级策略（escalation plan）</strong>：按技能匹配坐席，等待超时自动升级；</li>
<li><strong>并发拨打</strong>：多个坐席同振，先接先得，杜绝坐席漏接；</li>
<li><strong>主管四件套</strong>：<code>listen</code>（监听）/ <code>whisper</code>（耳语）/ <code>barge</code>（强插）/ <code>takeover</code>（接管），全程走 RWI 一条命令；</li>
<li>坐席签入签出、话后处理（wrapup）、排队轨迹追踪一应俱全。</li>
</ul>
<h3>4. RWI 实时控制面进化：这是给 Voice Agent 的遥控器</h3>
<p>RWI 是 RustPBX 的实时控制协议——JSON over WebSocket，延续了 Asterisk AMI 的 action/event 模型，但去掉了所有历史包袱。0.5.0 中它覆盖了更完整的通话生命周期：</p>
<ul>
<li><strong>早期媒体即可录音</strong>（<code>record.start</code> 支持媒体阶段启动，含回铃音阶段）；</li>
<li><strong>盲转 REFER 的 B2BUA 兜底</strong>：对端不支持 REFER 时自动降级为桥接转接，转移不再丢通话；</li>
<li><strong>跨会话主管接管</strong>、双向 PCM 实时音频流（<code>voip_bridge</code>）、会议 <code>create/add/remove/mute/destroy</code> 命令族。</li>
</ul>
<p>配合 HTTP Router 的入呼 webhook，一套接口完成"接听、放音、转人工、录音、监控"全流程。</p>
<h3>5. 实时转写与预测外呼：AI 场景的一等公民</h3>
<ul>
<li><strong>实时转写 SSE</strong>：<code>GET /cc/calls/{call_id}/transcript</code>，主/被叫双路独立 ASR 流，采用 Deepgram 兼容流式协议（16 kHz PCM / 20ms 帧）；懒启动设计——有人订阅才启动，无人订阅自动停止，不改变媒体路径，与录音、监听和平共处；</li>
<li><strong>离线转写</strong>：本地 SenseVoice 引擎，通话结束即出全文记录，数据不出内网；</li>
<li><strong>预测外呼 SSE</strong>：<code>POST /ami/v1/outbound/dial</code> 一个 HTTP 请求发起呼叫，标准 RWI 事件（created → ringing → answered → bridged）通过 SSE 实时回流，<code>on_answer</code> 可直接挂接后续动作。</li>
</ul>
<p>ASR 结果、DTMF、通话状态事件同步推送——你的语音机器人只需要一个 WebSocket 连接。</p>
<h2>传统 PBX 该有的底座，一样不少</h2>
<p>RustPBX 不是只会跑 AI 的玩具——作为一台可以接生产话务的 PBX，基础功全部到位：</p>
<p><strong>路由表与中继管理</strong></p>
<ul>
<li>静态路由规则引擎：正则匹配、号码改写、按优先级排序，Web 控制台可视化编辑，0.5.0 新增<strong>路由策略栈</strong>视图与运行时排序调整；</li>
<li>SIP 中继（Trunk）完整管理：注册型/对等型中继、<strong>健康检查</strong>、故障自动切换、自定义 SIP 头透传，多中继负载均衡；</li>
<li>静态路由打不了的复杂决策，自动回落到 HTTP 动态路由（<code>fallback_to_static</code> 双向兜底）。</li>
</ul>
<p><strong>IVR 引擎</strong></p>
<ul>
<li>内置 IVR 流程引擎：DTMF 菜单分支、放音收号、转接动作用<strong>统一 ActionNode 协议</strong>描述，步骤式（step mode）流程可以交给外部 Provider 逐步决策——你的 Python 服务就能当 IVR 大脑；</li>
<li><strong>IVR Exec</strong>：通话进行中随时插入 IVR 流程，播完自动返回原通话；</li>
<li>会话级 IVR Fallback：流程异常自动接管播报，TTS 音频缺失自动回退，不会让用户听到死寂。</li>
</ul>
<p><strong>呼叫转移</strong></p>
<ul>
<li>盲转、协商转（attended）原生支持；对端不支持 REFER 时自动降级为 <strong>B2BUA 桥接转接</strong>，转移永不丢通话；</li>
<li>队列转接携带业务参数（UUI/自定义头），坐席接手时上下文不丢。</li>
</ul>
<p><strong>安全配置</strong></p>
<ul>
<li><strong>ACL 访问控制</strong>：内联规则 + 规则文件目录双模式，支持热更新；</li>
<li><strong>DoS 防护</strong>：CPS（每秒呼叫数）限流、并发通话数上限、SIP worker 线程隔离，恶意洪水打不穿信令面；</li>
<li>URI 合法性校验、in-dialog 认证缓存、SBC/可信代理链支持（X-Forwarded-For 式的多跳场景）；</li>
<li>TLS/SRTP + ACME 自动证书、RBAC 权限体系、AMI API IP 白名单、<code>/metrics</code> 端点 Bearer Token 保护。</li>
</ul>
<p><strong>数据库与存储：一行 URL，录音直传 S3</strong></p>
<ul>
<li>SQLite（默认，零依赖起步）/ PostgreSQL / MySQL 三选一，改一行 <code>database_url</code> 即完成切换；话单库还可与主库分离，话务高峰互不干扰——实测 5000 路并发全录音场景数据库稳态 QPS 仅 2–4；</li>
<li><strong>录音默认就能直传 S3</strong>：<code>type = "s3"</code> 通话结束自动上传任意 S3 兼容对象存储，本地先落盘、上传确认成功才清理，失败自动留痕标记，网络抖动不丢录音；CDR JSON、SIP 信令 JSONL 同样支持直传 S3/HTTP——一个 bucket 收齐全部话务数据；</li>
<li>录音策略精细可配：文件名模板（<code>{caller}/{callee}/{timestamp}</code>）、按方向与主被叫黑白名单决定录谁不录谁（比如 911 免录）、回铃阶段即可开录；</li>
<li>节点无需共享存储：录音和话单都进对象存储，多副本部署天生无状态。</li>
</ul>
<p><strong>TLS 与证书：全栈加密开箱即用</strong></p>
<ul>
<li>管理台/API/Webhook 内置 HTTPS 监听，配一对证书路径即可；</li>
<li>SIP 加密（SIPS，TLS 5061）与 WebRTC（WSS）原生支持，出站连接可校验对端证书链；</li>
<li><strong>ACME 自动证书</strong>：Let's Encrypt 自动签发与续期，certbot 定时任务可以删了。</li>
</ul>
<p><strong>集群：多节点组网，配置即声明</strong></p>
<ul>
<li><code>[cluster]</code> 配置里列出所有节点即完成组网；</li>
<li>会话注册表实时回答"这通电话在哪个节点"，控制请求自动转发到所属节点，不必关心话务落在谁身上；</li>
<li>心跳保活 + 宕机节点会话回收窗口，节点掉线话务可重新调度；</li>
<li>集群级配置一键下发刷新全部节点，控制台日志查看器可直接切换远端节点。</li>
</ul>
<p><strong>线上日志与诊断：不用 SSH 的排障</strong></p>
<ul>
<li>控制台内置<strong>日志查看器</strong>：实时跟随新输出、按节点切换、行数选项与一键复制；日志文件按天/小时自动轮转；</li>
<li><strong>诊断工具箱</strong>：活跃对话一览、中继测试（发起真实测试呼叫 / OPTIONS 探测）、<strong>路由评估器</strong>（输入主被叫号码立即预览路由决策结果，改路由前先演练）、分机注册定位查询；</li>
<li>AMI API 健康检查与原始 dialog 检视，配合 <code>/healthz</code> 探针与 Prometheus <code>/metrics</code> 全指标暴露。</li>
</ul>
<p><strong>运维体验</strong></p>
<ul>
<li><strong>配置热重载</strong>：<code>POST /reload/trunks</code>、<code>/reload/routes</code>、<code>/reload/acl</code>、<code>/reload/queues</code>、<code>/reload/ivr</code>——按对象粒度热更新，不重启进程、不挂断进行中的通话；路由/中继/ACL 还支持数据库配置模式，控制台改完即用；</li>
<li><strong>优雅关机</strong>：SIGTERM 后先排水再退出，打完最后一通电话才下线；</li>
<li><strong>SipFlow 可视化排障</strong>：Web 控制台里直接查看每通电话的完整 SIP 信令流与 RTP 质量统计，录音在线回放、WAV 导出，JSONL 一键归档——sngrep 可以退休了。</li>
</ul>
]]></description><pubDate>2026-09-04 01:23:04</pubDate></item><item><title>rsvm：多版本 Rust 工具链</title><link>https://rustcc.cn/article?id=4b0e7eea-8152-488d-97ef-c9a6b40c419a</link><description><![CDATA[<p>大家好，</p>
<p>我写了 <strong>rsvm</strong>：</p>
<p>https://github.com/rsvm-sh/rsvm</p>
<p>它从 <code>static.rust-lang.org</code> 下载官方工具链，校验 SHA256，装到 <code>~/.rsvm/versions/</code> 下面。可以同时装着好几个版本；切换时只改<strong>当前这个终端</strong>的 <code>PATH</code>，别的窗口不受影响。也不会调用 <code>rustup default</code>。</p>
<h2>多版本</h2>
<p>每个版本单独一个目录，互不覆盖：</p>
<pre><code>~/.rsvm/versions/1.85.0/
~/.rsvm/versions/1.98.0/
~/.rsvm/versions/1.100.0-nightly-2026-08-30/
</code></pre>
<p><code>rsvm install</code> 只往里加，不会把已经装好的删掉。<code>rsvm list</code> 看装了哪些。<code>rsvm uninstall 1.85.0</code> 只删这一个，缓存里的安装包也会清掉。正在用的版本不能删，先 <code>rsvm use</code> 切走再卸。</p>
<pre><code>rsvm install stable
rsvm install beta
rsvm install nightly         # 目录名会带上日期
rsvm install 1.85.0

rsvm list
rsvm current
rsvm which 1.85.0
rsvm uninstall 1.85.0
</code></pre>
<p><code>stable</code> / <code>beta</code> / <code>nightly</code> 安装时取该通道当时的最新版。<code>rsvm alias default 1.85</code> 表示默认用已安装的最新 <code>1.85.x</code>，新开终端会跟这个 default。</p>
<h2>切换版本</h2>
<p><code>rsvm use &lt;version&gt;</code> 把这个版本的 <code>bin</code> 放到当前终端 <code>PATH</code> 最前面。已经打开的其他终端不会跟着变。</p>
<pre><code>rsvm use stable
rustc --version

rsvm use 1.85.0
rustc --version

rsvm use system              # 当前终端不再走 rsvm，改用系统里的 rustc
</code></pre>
<p>不想切整个终端，只想用某个版本跑一条命令：</p>
<pre><code>rsvm run 1.85.0 cargo test
rsvm-exec 1.85.0 cargo build
</code></pre>
<p>项目里可以放一个 <code>.rust-version</code> 指定版本。命令不写版本号时，<code>rsvm install</code>、<code>rsvm use</code>、<code>rsvm-exec --</code> 会从当前目录往上找最近的这个文件。</p>
<pre><code>echo 1.85.0 &gt; .rust-version
rsvm use
rsvm-exec -- cargo test
</code></pre>
<p>这个文件只有 rsvm 会读。Cargo 和 rust-analyzer 认的是 <code>rust-toolchain.toml</code>。</p>
<p>切版本只作用于当前终端，不会改全局默认，也不会调用 <code>rustup default</code>。</p>
<h2>现状</h2>
<ul>
<li>v0.1.0，MIT</li>
<li>bash / zsh，需要 <code>curl</code> 和 <code>tar</code></li>
<li>暂时没有 Windows 和 fish</li>
<li>装下来的是官方这套：<code>rustc</code>、<code>cargo</code>、<code>rustdoc</code>、<code>std</code></li>
</ul>
<h2>安装</h2>
<pre><code>curl -o- https://raw.githubusercontent.com/rsvm-sh/rsvm/v0.1.0/install_sh.sh | bash
source ~/.zshrc   # 或 ~/.bashrc
rsvm help
</code></pre>
<p>欢迎提意见、开 issue 和 PR。</p>
<p><strong>仓库：</strong> https://github.com/rsvm-sh/rsvm
<strong>许可证：</strong> MIT</p>
]]></description><pubDate>2026-09-02 11:14:23</pubDate></item><item><title>嵌入式磁盘版的redis（比redis快20倍）</title><link>https://rustcc.cn/article?id=546f947a-cff2-493c-8640-42c6382d7a67</link><description><![CDATA[<p>https://github.com/webc-site/wedb_embed</p>
<h1>wedb_embed : Redis 兼容的嵌入式 LSM-Tree 磁盘数据库引擎</h1>
<p>嵌入式数据库引擎，提供 Redis 兼容数据结构与接口，基于 <a href="https://github.com/fjall-rs/fjall" rel="noopener noreferrer">fjall</a> LSM-Tree 存储引擎构建。</p>

<ul>
<li>
<p><strong>磁盘 I/O 影响</strong>：
WeDb 为磁盘数据库，性能表现与底层存储硬件 I/O 吞吐紧密相关。
在 Apple M2（NVMe SSD）实测综合性能约为 Redis 的 40 倍；
在 GitHub Actions（Linux 云端虚拟磁盘）实测综合性能约为 Redis 的 20 倍。
两者的性能倍率差异主要源于底层磁盘硬件的 I/O 吞吐与读写延迟差异。</p>
</li>
<li>
<p><strong>内存预算控制</strong>：
常驻内存由 <code>cache_size</code>（默认 512MB 块缓存）与 <code>max_memtable_size</code> 参数控制，
不随磁盘数据量线性膨胀。</p>
</li>
</ul>
<hr>
<h2>为什么需要嵌入式 Redis 引擎</h2>
<p>如同 SQLite 之于 MySQL/PostgreSQL，<code>wedb_embed</code> 是 Redis 生态的嵌入式磁盘数据库引擎。</p>
<p>在传统关系型数据库体系中，MySQL 采用独立服务端守护进程与网络套接字通信架构；
SQLite 则以进程内嵌入式库直接将数据持久化于本地磁盘文件，无需独立守护进程与跨进程调用。</p>
<p>在键值与复合数据结构领域，传统 Redis 采用独立守护进程与物理内存常驻架构。
在单机部署、边缘计算、命令行工具与微服务场景中，该架构存在以下系统层面的瓶颈：</p>
<ul>
<li>
<p><strong>进程间通信与协议开销</strong>：
每次数据读写均需经过序列化、操作系统套接字缓冲区、进程上下文切换与 RESP 协议解析。
即便在本地主机通信，套接字往返延迟通常也在 20~50 微秒区间，并持续消耗 CPU 周期。</p>
</li>
<li>
<p><strong>物理内存成本与容量边界</strong>：
Redis 将业务数据与内部指针常驻于物理内存中。
当数据规模增长至数十 GB 时，内存硬件成本上升，且严格受限于单机物理内存容量。
开启 AOF 或 RDB 持久化时，写时复制（Copy-on-Write）机制可能导致内存翻倍。</p>
</li>
<li>
<p><strong>部署与守护运维复杂度</strong>：
独立进程需要额外的进程守护、端口监听、配置同步与健康检查，
增加了软件交付与运维的维护负担。</p>
</li>
</ul>
<p><code>wedb_embed</code> 将存储引擎直接编译并运行在应用程序进程空间内：</p>
<ul>
<li>
<p><strong>进程内直接调用</strong>：
所有 Redis 兼容数据操作通过 Rust 函数直接调用，
避免了套接字 I/O、系统调用与跨进程上下文切换。
在同等硬件下，核心指令的 P95 延迟降低至纳秒与微秒级。</p>
</li>
<li>
<p><strong>LSM-Tree 磁盘持久化与内存预算控制</strong>：
数据经过 LZ4 块压缩存储于本地磁盘文件。
常驻内存不随数据总量线性增长，由 LSM-Tree 参数严格控制：</p>
<ul>
<li><code>cache_size</code>（默认 512MB）：全局共享的 SSTable 块缓存上限，用于缓存热点数据页；</li>
<li><code>max_memtable_size</code>（数据分区 64MB / 元数据分区 32MB）：内存写缓冲上限，达到阈值后自动异步刷盘生成不可变 SSTable；</li>
<li><code>with_kv_separation</code>（大 Value 分离阈值 4KB）：大对象直接写入独立 Blob 文件，降低写放大与内存占用。
在 5GB 结构化数据实测中，Redis 物理内存占用达 4814 MB（RSS），
<code>wedb_embed</code> 常驻内存为 334 MB（降低 93%），物理落盘体积由 Redis AOF 的 7652 MB 压缩至 1180 MB（减少 85%）。</li>
</ul>
</li>
<li>
<p><strong>16 种 Redis 兼容数据模型</strong>：
在底层键值引擎之上，支持 String、Hash（支持字段级 TTL）、List、Set、ZSet、Bitmap、JSON、
Bloom/Cuckoo 过滤器、TimeSeries、Geo、HyperLogLog、TDigest、SortedInt、Stream、全文检索与 HNSW 向量检索。</p>
</li>
<li>
<p><strong>多租户与多库物理隔离</strong>：
原生支持 $2^{64}$ 个独立租户与数据库。
命名空间（<code>ns</code>）与数据库（<code>db</code>）传入 <code>None</code> 时自动分配递增编号并创建新实例。</p>
</li>
<li>
<p><strong>崩溃一致性保障</strong>：
基于预写日志 WAL 与跨分区原子批处理 <code>WriteBatch</code>，保障断电与异常退出场景下的数据完整性。</p>
</li>
</ul>
<hr>
<h2>快速上手</h2>
<h3>添加依赖</h3>
<pre><code>cargo add wedb_embed
</code></pre>
<h3>基础用法与多租户</h3>
<pre><code>use anyhow::Result;
use wedb_embed::{Fjall, WeDb};

fn main() -&gt; Result&lt;()&gt; {
    // 打开存储引擎并初始化 WeDb 实例
    let engine = Fjall::open("./data/quickstart_db")?;
    let wedb = WeDb::new(engine);

    // 租户命名空间与多库切换（ns 传入 None 创建新命名空间，db 传入 None 创建新数据库）
    let default_db = wedb.ns(0)?.db(0)?; // 默认命名空间 (0) 与默认数据库 (0)
    let tenant_ns = wedb.ns(None)?;      // 创建新命名空间 (ns_id &gt;= 1)
    let db = tenant_ns.db(None)?;        // 租户下创建新数据库 (db_id &gt;= 1)

    // 字符串读写 (String / KV)
    db.set(b"site", b"webc.site", &amp;[])?;
    let val = db.get(b"site")?;
    assert_eq!(val.as_deref(), Some(&amp;b"webc.site"[..]));

    // 哈希表与有序集合等数据结构操作
    db.hset(b"user:100", &amp;[(b"name".as_slice(), b"Alice".as_slice())])?;
    db.zadd(b"rank", &amp;[(100.0, b"player1".as_slice())], &amp;[])?;

    // 流式遍历活跃命名空间与数据库
    for ns in wedb.iter(0) {
        println!("Namespace: {}", ns.id());
        for db_id in ns.iter(0) {
            println!("  DB: {db_id}");
        }
    }

    // 级联删除与目录注销 (rm)
    db.rm()?;        // 级联删除并注销当前数据库
    tenant_ns.rm()?; // 级联删除并注销该命名空间下的全部数据库

    Ok(())
}
</code></pre>
<p><a href="https://github.com/webc-site/wedb_embed/tree/main/examples" rel="noopener noreferrer">点此查看完整示例代码（包含 16 种数据结构与多租户详细用法）</a></p>
<hr>
<h2>性能与资源实测对比</h2>
<p>&lt;+ ./bench/zh.md &gt;</p>
<hr>
<h2>存储架构与编码设计</h2>
<pre><code>graph TD
  Client["应用业务代码 (Rust API)"] --&gt; WeDb["WeDb 数据库引擎"]
  WeDb --&gt; NS["Namespace 租户句柄&lt;br/&gt;(零堆分配结构体)"]
  NS --&gt; DB["Db 数据库句柄&lt;br/&gt;(作用域隔离)"]

  subgraph KeyComposer["键编排与紧凑编码 (KeyComposer)"]
    Tag["1 字节紧凑标签 (#[repr(u8)] KeyTag)"]
    OPPV["OPPV 保序变长整型 (1~9 字节)"]
    SmallKey["SmallKey 64B 栈上缓冲 (零堆分配)"]
    Subkey["SubkeyComposer 前缀内存复用"]
  end

  subgraph Engine["存储引擎与事务层 (LSM-Tree Core)"]
    Batch["DbBatch 原子批处理 (跨分区 WAL)"]
    Catalog["Catalog 元数据目录与 $2^{64}$ 租户索引"]
    Blob["KV 分离引擎 (大 Value Blob 存储 &gt;= 4KB)"]
  end

  subgraph Storage["Fjall LSM-Tree 双分区存储引擎"]
    DataKS["data 数据分区&lt;br/&gt;(String 数据、复合子键与 Blob 引用 ｜ 8KB 块 ｜ LZ4 压缩)"]
    MetaKS["meta 元数据分区&lt;br/&gt;(结构元数据、版本号与租户 Catalog ｜ 4KB 块 ｜ 100% 内存哈希索引)"]
  end

  DB --&gt; KeyComposer
  KeyComposer --&gt; Engine
  Engine --&gt; DataKS
  Engine --&gt; MetaKS
  Engine --&gt; Blob
</code></pre>
<h3>双分区物理存储与前缀编排</h3>
<ul>
<li>
<p><strong>双分区物理架构 (<code>data</code> / <code>meta</code>)</strong>：</p>
<ul>
<li><strong>数据分区 (<code>data</code>)</strong>：
存储 String 原始值、复合结构子键与大 Value Blob 引用。
采用 8KB 块大小与 LZ4 块压缩，配置大对象 KV 分离（大 Value 写入独立 Blob 文件以降低写放大）。</li>
<li><strong>元数据分区 (<code>meta</code>)</strong>：
存储复合结构元数据（<code>KeyMeta</code>）、版本计数器与 Catalog 租户目录。
采用 4KB 块大小与内存哈希索引，保障元数据点查的亚微秒级延迟。</li>
</ul>
</li>
<li>
<p><strong>1 字节紧凑标签 (<code>KeyTag</code>)</strong>：
复合结构元数据与子键前缀采用 <code>#[repr(u8)] KeyTag</code> 编码（如 <code>\x01[key]</code>），
避免字符串标签带来的存储与解析开销。</p>
</li>
<li>
<p><strong>作用域前缀与多租户隔离</strong>：
多租户与多数据库统一由 <code>KeyComposer</code> 编码为 <code>\x00[oppv(ns_id)][oppv(db)]</code> 物理前缀，
支持 $2^{64}$ 个租户与数据库的隔离存储。</p>
</li>
</ul>
<h3>保序变长整型编码</h3>
<ul>
<li>数据库编号与租户 ID 采用 <strong>OPPV（Order-Preserving Prefix Varint）</strong> 编码：
<ul>
<li>数值 $0 \sim 127$ 仅占用 1 字节，较固定 8 字节大端序降低空间占用；</li>
<li>编码后的字节序与原始数值大小顺序一致：$\forall a &lt; b \implies \text{encode}(a) &lt; \text{encode}(b)$，支持底层直接进行范围扫描。</li>
</ul>
</li>
</ul>
<h3>租户目录编排与级联注销</h3>
<ul>
<li>命名空间与数据库采用 <code>u64</code> 编号体系，<code>ns</code> 或 <code>db</code> 传入 <code>None</code> 自动分配全局递增 ID。</li>
<li>通过 Catalog 目录（<code>\x00\x71[oppv(ns_id)][oppv(db_id)]</code>）持久化维护激活索引。</li>
<li>支持数据库级与租户级的级联删除与注销（<code>rm()</code>），清理数据并更新 Catalog 目录索引。</li>
</ul>
<h3>内存友好流式迭代</h3>
<ul>
<li>
<p><strong><code>WeDb::iter(&amp;self, begin: u64) -&gt; Namespaces</code></strong>：
基于 Catalog 前缀流式扫描已激活的租户命名空间列表，
支持从指定 <code>begin</code> 起始偏移开始扫描，辅助内存复杂度为 $O(1)$。</p>
</li>
<li>
<p><strong><code>Namespace::iter(&amp;self, begin: u64) -&gt; Dbs</code></strong>：
基于 Catalog 前缀流式解析该租户下所有已激活的数据库编号。</p>
</li>
</ul>
<hr>
<h2>运行时生态与线程模型设计</h2>
<p><code>wedb_embed</code> 针对基于 Linux <code>io_uring</code> 的 Thread-per-Core（单线程单核心）异步运行时（例如 <code>compio</code>）进行协同设计，
利用单核独占、无共享状态与绑核特性发挥硬件缓存局部性。</p>
<pre><code>graph LR
  subgraph CompioModel["compio 线程模型 (Thread-per-Core)"]
    direction TB
    C1["CPU 核心 0 (工作线程 0)&lt;br/&gt;绑定物理核心"] --&gt; S1["栈缓冲 SmallKey (64B)&lt;br/&gt;L1/L2 缓存命中"]
    C2["CPU 核心 1 (工作线程 1)&lt;br/&gt;绑定物理核心"] --&gt; S2["栈缓冲 SmallKey (64B)&lt;br/&gt;L1/L2 缓存命中"]
    S1 --&gt; IO1["同步调用 / io_uring&lt;br/&gt;无工作窃取 ｜ 无上下文切换"]
    S2 --&gt; IO2["同步调用 / io_uring&lt;br/&gt;无工作窃取 ｜ 无上下文切换"]
  end

  subgraph TokioModel["Tokio 工作窃取模型 (Work-Stealing)"]
    direction TB
    T1["工作线程 A"] &lt;--&gt;|"跨核任务窃取&lt;br/&gt;L1/L2 缓存失效 ｜ NUMA 迁移"| T2["工作线程 B"]
    T1 --&gt; ST["堆分配状态机 (Send + 'static)&lt;br/&gt;互斥锁竞争 ｜ 破坏栈生命周期"]
    T2 --&gt; SB["阻塞线程池切换&lt;br/&gt;线程上下文切换 ｜ 延迟放大"]
  end
</code></pre>
<h3>一线程一核心架构设计</h3>
<ul>
<li>
<p><strong>栈上生命周期与零堆分配</strong>：
物理键构建采用 <code>SmallKey</code> 64 字节栈缓冲与 <code>SubkeyComposer</code> 前缀复用。
在单线程单核心模型下，执行上下文保持在当前 CPU 核心的栈帧内，
无需向全局堆分配器（如 <code>jemalloc</code> 或 <code>glibc malloc</code>）申请内存，避免了多线程堆分配锁争用。</p>
</li>
<li>
<p><strong>CPU 缓存行局部性</strong>：
工作线程与物理 CPU 核心静态绑定，任务执行不发生跨核迁移。
热点数据结构（LSM-Tree 内存表索引、布隆过滤器位图、Catalog 元数据缓存）常驻于当前 CPU 的 L1/L2 缓存，
避免多核缓存一致性协议广播无效化消息导致的缓存行失效。</p>
</li>
<li>
<p><strong>无锁与轻量并发元数据</strong>：
命名空间与租户目录采用 <code>papaya::HashMap</code> 无锁并发哈希表，读操作无等待；
低频的元数据写操作采用 <code>parking_lot</code> 自适应自旋锁，在单核独占环境下自旋即完成，
不触发内核态 Futex 上下文挂起与跨核唤醒。</p>
</li>
<li>
<p><strong>同步内嵌调用</strong>：
存储引擎 API 均为同步内存与磁盘直接调用。
在单线程事件循环中，微秒级与纳秒级查找就地执行，
与底层 <code>io_uring</code> 异步 I/O 配合，避免将 Future 包装为跨线程状态机。</p>
</li>
</ul>
<h3>传统多线程工作窃取运行时问题分析</h3>
<p>在基于多线程工作窃取与 <code>epoll</code> 的通用异步运行时中，存在以下性能与调度开销：</p>
<ul>
<li>
<p><strong>跨核心任务窃取导致缓存失效</strong>：
调度器在工作线程空闲时跨核窃取任务。
同一请求的 Future 在 <code>await</code> 恢复后可能被调度到其他 CPU 核心或跨 NUMA 节点，
导致 L1/L2 缓存失效，增加访存延迟与 P99 尾部延迟。</p>
</li>
<li>
<p><strong>破坏栈生命周期约束</strong>：
通用异步任务通常要求满足 <code>Send + 'static</code> 约束，
栈上分配的短期借用结构（如 <code>&amp;[u8]</code> 切片、栈上 <code>SmallKey</code>）无法跨 <code>await</code> 点存活，
需重新分配至堆内存（<code>Box</code> / <code>Arc</code> / <code>Vec&lt;u8&gt;</code>），增加了堆分配开销。</p>
</li>
<li>
<p><strong>事件循环阻塞与线程池切换</strong>：
在单线程事件循环中直接执行磁盘读取或耗时查找可能阻塞事件循环；
若转发至阻塞线程池，则引入线程上下文切换、跨线程通道传递与调度排队，
使微秒级操作延迟增加。</p>
</li>
<li>
<p><strong>多核心内存总线争用</strong>：
多线程并发访问共享实例时，跨核心原子操作与锁争用会导致内存总线锁争用，
限制高并发下的吞吐扩展能力。</p>
</li>
</ul>
<hr>
<h2>技术栈</h2>
<ul>
<li><strong>开发语言</strong>：Rust Edition 2024</li>
<li><strong>存储引擎</strong>：<code>fjall</code> LSM-Tree 存储引擎</li>
<li><strong>JSON 引擎</strong>：<code>sonic-rs</code> SIMD 指令解析</li>
<li><strong>非加密哈希</strong>：<code>rapidhash</code></li>
<li><strong>字符串与内存</strong>：<code>hipstr</code> 紧凑字符串</li>
<li><strong>并发同步</strong>：<code>parking_lot</code></li>
<li><strong>集合与位运算</strong>：<code>roaring</code>、<code>memchr</code>、<code>crc32fast</code>、<code>fastrand</code></li>
<li><strong>时间戳处理</strong>：<code>coarsetime</code></li>
<li><strong>数值与浮点序列化</strong>：<code>zmij</code>、<code>itoa</code></li>
<li><strong>枚举派生</strong>：<code>strum</code></li>
<li><strong>错误处理</strong>：<code>thiserror</code></li>
</ul>
]]></description><pubDate>2026-09-02 10:45:53</pubDate></item><item><title>【Rust日报】2026-09-02 全球首个 Rust 安全认证产品</title><link>https://rustcc.cn/article?id=dfe4f217-e687-4a5e-b8ee-fab8ee96c543</link><description><![CDATA[<h2>Sonair ADAR One：全球首个用 Rust 写出的安全认证产品</h2>
<p>ADAR One 是挪威 Sonair 的 3D 超声波传感器产品，面向机器人与机械安全场景：发射全向超声波脉冲，多换能器接收反射并生成 3D 点云；当保护区内出现目标时，通知机器人或设备减速/停机。公司宣布 ADAR One 成为首个以 Rust 实现并完成安全认证的嵌入式系统。</p>
<p>团队原为 C/C++ 嵌入式背景，启动时无人正式交付过 Rust 产品。选型主要针对 C/C++ 内存与并发痛点；当时编译器安全认证尚未完成，需沿 Ferrocene 路径推进。技术负责人 Espen Albrektsen 撰文记录从语言选型、bare metal 实现到最终认证的过程与难点。</p>
<p>原文链接：https://www.sonair.com/journal/how-we-safety-certified-the-worlds-first-rust-implementation</p>
<h2>Wasmi 2.0：WASM 解释器相对 1.0 约快 2.2×</h2>
<p>Wasmi 是高效 WebAssembly 解释器，用于 IoT、插件系统（Typst、Zellij、Josh）、云主机、智能合约（Soroban、Ripple）与轻量游戏主机（Firefly Zero）等。作者 Robin Freyler 发布 Wasmi 2.0：约 8 个月聚焦执行性能的引擎大改。</p>
<p>在 Apple M2 Pro 上，wasmi-benchmarks 几何均值显示相对 Wasmi 1.0 约快 2.2×，并与 Wasm3、WAMR fast-interpreter、Wasmtime Pulley、Makepad Stitch 等对比。2.0 还提供 <code>validate</code> feature 缩小产物、稳定 fuel metering、WebAssembly deterministic profile 支持与改进 CLI。项目获 Stellar Development Foundation 自 2024-10 起赞助。</p>
<p>原文链接：https://wasmi-labs.github.io/blog/posts/wasmi-v2.0/</p>
<h2>Sonora：Rust + GPUI 跨平台原生音乐播放器</h2>
<p>Sonora 是学生团队用 Rust 与 GPUI 实现的原生音乐客户端，支持 Spotify、YouTube（可选登录）与本地曲库，并提供虚拟播放列表、收藏、同步/卡拉 OK 歌词（含本地音乐）、罗马化、库内管理、无缝播放、音量归一化、跨平台与自定义主题。</p>
<p>UI 受 Zed/GPUI 启发，并对 GPUI fork 做了效果与 profiling 相关补丁；作者强调相对 Spotify Desktop 更低的内存占用。macOS 可用 Homebrew cask 安装，Linux 提供 AUR 等路径。仓库当前约 95 star，版本到 0.28.1。</p>
<p>原文链接：https://github.com/nolight132/sonora</p>
<h2>Rust-GB：用 Pure Rust 写出 Game Boy ROM</h2>
<p>Rust-GB 目标是把 Rust 编译为 Game Boy ROM。早期经 SDCC / GBDK；作者现已写出 LLVM-Z80，可将 LLVM IR 直接编到 Game Boy 机器码，并据此构建 Game Boy 侧 Rust 库，从而能用 Pure Rust 编写 Game Boy 程序。</p>
<p>仓库提供 HAL/runtime（<code>rust-gb</code>、<code>gb-rt</code> 等）、<code>cargo-gb</code> 等 host 工具与示例 ROM；相关产物还包括 llvm-z80 / rust-z80。示例 sprite ROM 可下载；作者还在推进 Game Boy 硬件的 safe Rust API。GitHub 约 291 star。</p>
<p>原文链接：https://github.com/zlfn/rust-gb</p>
<hr>
<p>From Rust中文社区 Mike</p>
<p>社区学习交流平台订阅：</p>
<ul>
<li><a href="https://rustcc.cn/" rel="noopener noreferrer">Rustcc论坛: 支持rss</a></li>
<li><a href="https://rustcc.cn/article?id=ed7c9379-d681-47cb-9532-0db97d883f62" rel="noopener noreferrer">微信公众号：Rust语言中文社区</a></li>
</ul>
]]></description><pubDate>2026-09-02 01:10:01</pubDate></item><item><title>Ramag v0.0.2 发布：新增 SSH、SFTP 与 JumpServer 导入</title><link>https://rustcc.cn/article?id=691353be-7377-4388-86ac-edd20b706443</link><description><![CDATA[<p>Ramag 是一个使用 Rust + GPUI 构建的本地优先开发者桌面工作台，将数据库、Git、SSH 和剪贴板放进同一个原生应用。</p>
<pre><code>数据库 ↔ Git ↔ SSH / SFTP ↔ 剪贴板
</code></pre>
<ul>
<li>GitHub：https://github.com/tools-rs/ramag</li>
<li>下载：https://github.com/tools-rs/ramag/releases/tag/v0.0.2</li>
<li>更新记录：https://github.com/tools-rs/ramag/blob/main/CHANGELOG.md</li>
</ul>
<p><img src="https://cdn.jsdelivr.net/gh/tools-rs/ramag@main/docs/screenshots/v0.0.2/home-light.png" alt="Ramag v0.0.2 首页"></p>
<h2>v0.0.2 主要更新</h2>
<h3>SSH 连接管理</h3>
<p>新增完整的 SSH 管理入口，支持：</p>
<ul>
<li>密码、系统 SSH 配置和密钥认证</li>
<li>解析 <code>ssh user@host -p 2222 -i /path/to/key</code> 命令</li>
<li>连接测试、默认目录和生产连接标记</li>
<li>多连接标签和连接搜索</li>
<li>本机加密保存密码与敏感连接参数</li>
</ul>
<p>终端连接复用系统 OpenSSH，因此可以继续使用现有的 <code>~/.ssh/config</code>、SSH Agent、密钥和 <code>known_hosts</code>。</p>
<p><img src="https://cdn.jsdelivr.net/gh/tools-rs/ramag@main/docs/screenshots/v0.0.2/ssh-connections-empty-light.png" alt="SSH 连接管理"></p>
<h3>内嵌终端与 SFTP 文件工作区</h3>
<p>连接成功后，可以在同一个工作区中使用内嵌终端和远程文件浏览：</p>
<ul>
<li>ANSI 终端显示和常用键盘输入</li>
<li>一个连接下打开多个终端标签</li>
<li>始终保留至少一个终端，避免误关后留下空页面</li>
<li>远程目录浏览、路径导航和名称搜索</li>
<li>文本预览与编辑、日志跟随</li>
<li>文件和目录上传下载</li>
<li>覆盖确认、取消和传输进度</li>
</ul>
<p>还可以从当前路径、目录或文件所在位置创建新终端。新终端会进入对应远程目录，不影响已有终端的运行状态。</p>
<p>生产连接会禁止 SFTP 上传、编辑、重命名和删除。终端命令仍由远端账号权限与服务器策略约束。</p>
<p><img src="https://cdn.jsdelivr.net/gh/tools-rs/ramag@main/docs/screenshots/v0.0.2/ssh-workspace-light.png" alt="SSH 内嵌终端与远程文件工作区"></p>
<h3>JumpServer 导入</h3>
<p>支持保存多个 JumpServer 登录，并直接读取：</p>
<ul>
<li>组织与资产树</li>
<li>已授权资产</li>
<li>资产平台和地址</li>
<li>可用的授权账号</li>
</ul>
<p>选择资产与授权账号后，可以导入为普通 SSH 连接，继续编辑、测试和打开。未开放 SSH 协议的资产会明确提示，不会错误导入。</p>
<h3>数据库改进</h3>
<ul>
<li>结果搜索支持字符串 ID 与整数 ID 双向转换。</li>
<li>内置 Base10、Base16、Base36、Base58 Bitcoin、Base58 Flickr 和自定义字符表。</li>
<li>支持带路径、超时和输出限制的外部转换器。</li>
<li>修复 MySQL <code>SHOW WARNINGS</code> 被识别为普通分页查询的问题。</li>
<li>数据库连接导入导出迁移到全局设置，可统一处理 MySQL、PostgreSQL、Redis 和 MongoDB。</li>
<li>编辑连接时完整回填现有参数，生产连接继续受只读保护。</li>
</ul>
<h3>Git 与剪贴板改进</h3>
<p>Git 工作台新增明确的克隆入口，Markdown 文件默认渲染预览，并重新整理了分支、远端、Tag 和 Stash 操作。同时修复新建分支导致的界面崩溃、分栏宽度串联和部分标签无法通过 <code>⌘W</code> / <code>Ctrl+W</code> 关闭的问题。</p>
<p>剪贴板仍默认关闭。启用状态、采集行为、全局热键和“清空全部历史”统一迁移到全局设置；关闭后会隐藏入口并释放全局快捷键。</p>
<pre><code>macOS：⌘⇧V
Windows：Ctrl+Shift+V
</code></pre>
<h2>本地优先与安全边界</h2>
<p>Ramag 不要求登录账号，也不会把数据库连接、SSH 凭据、Git 仓库或剪贴板内容上传到 Ramag 服务。</p>
<ul>
<li>敏感配置使用 AES-256-GCM 加密。</li>
<li>主密钥保存在 macOS Keychain 或 Windows Credential Manager。</li>
<li>SSH 认证、主机校验和 Git 网络操作复用系统已有配置。</li>
<li>数据库和 SSH 生产连接会限制界面中的写操作。</li>
</ul>
<p>Git 功能仍处于试验阶段。生产数据库操作、Git 写操作和远程终端命令仍需要使用者确认目标环境和影响范围。</p>
<h2>下载</h2>
<p>v0.0.2 Release：</p>
<p>https://github.com/tools-rs/ramag/releases/tag/v0.0.2</p>
<p>当前提供：</p>
<pre><code>Ramag-0.0.2-macos-arm64.dmg
Ramag-0.0.2-macos-x86_64.dmg
Ramag-0.0.2-windows-x64-setup.exe
SHA256SUMS.txt
</code></pre>
<p>支持 macOS 12+ Apple Silicon、macOS 12+ Intel 和 Windows 10/11 x64，暂不支持 Linux。</p>
<p>当前 Windows 安装包尚未做 Authenticode 签名，macOS 安装包尚未完成 Developer ID 签名与 Apple 公证。请只从项目 Releases 下载，并使用同一页面的 <code>SHA256SUMS.txt</code> 校验文件。</p>
<p>从源码运行：</p>
<pre><code>git clone https://github.com/tools-rs/ramag.git
cd ramag
make develop
</code></pre>
<p>如果你正在使用 Rust、GPUI、数据库工具或 SSH/SFTP 工作流，欢迎下载体验。遇到问题时，可以在 GitHub Issues 中附上操作系统、Ramag 版本和复现步骤；提交前请删除服务器地址、用户名、密码和业务数据。</p>
<ul>
<li>Issues：https://github.com/tools-rs/ramag/issues</li>
<li>源码：https://github.com/tools-rs/ramag</li>
</ul>
]]></description><pubDate>2026-08-04 09:46:17</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></channel></rss>