<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 重写的 Pi 比原生 TypeScript 版快多少</title><link>https://rustcc.cn/article?id=9d06ec94-40ee-4f95-bbf3-458465d7bf93</link><description><![CDATA[<h2>起因</h2>
<p>rpi 是 TypeScript 版 pi 的 Rust 移植。这类"重写"项目最常见的宣传口径是"Rust 更快"，
但<strong>快多少、快在哪、以及除了快还差在哪</strong>，通常没人讲清楚。</p>
<p>所以我把两个工具装在同一台机器上，用同一套协议跑了一遍，同时把两边的代码结构也摊开对比。</p>
<h2>怎么测才公平</h2>
<p>两个工具都是终端里的 coding agent，一个原生二进制，一个 Node 脚本，很容易测成"比谁启动快"这种没信息量的对比。所以口径先定死：</p>
<table>
<thead>
<tr>
<th>约束</th>
<th>做法</th>
<th>为什么</th>
</tr>
</thead>
<tbody>
<tr>
<td>同一终点</td>
<td>双方都通过同一套 JSONL 命令通道，测"发出 <code>get_state</code> 到收到响应"</td>
<td>这才是"agent 可用了"，不是"参数解析完了"</td>
</tr>
<tr>
<td>冷且隔离</td>
<td>每轮用全新的空配置目录：pi 用 <code>PI_CODING_AGENT_DIR</code>，rpi 用 <code>RPI_CODING_AGENT_DIR</code></td>
<td>两边变量名不同，只设一个会漏掉另一边</td>
</tr>
<tr>
<td>离线</td>
<td>pi 用 <code>PI_OFFLINE=1</code>，rpi 用 <code>RPI_OFFLINE=1</code></td>
<td>测运行时开销，不是网络</td>
</tr>
<tr>
<td>占位凭据</td>
<td>给一个假 <code>ANTHROPIC_API_KEY</code></td>
<td>rpi 在空目录下没凭据就不建会话，离线也不会真的用</td>
</tr>
<tr>
<td>中性目录</td>
<td>双方都在空临时目录里启动</td>
<td>避免谁去扫了这个大仓库</td>
</tr>
<tr>
<td>不采样</td>
<td>计时窗口内不做任何 RSS 采样</td>
<td>否则采样本身会拖慢被测进程</td>
</tr>
<tr>
<td>无 shell</td>
<td>绕过 npm 的 <code>.bin/pi</code> shim，直接 <code>node cli.js</code></td>
<td>否则把 <code>cmd.exe</code> 的启动算进 pi 的时间里</td>
</tr>
</tbody>
</table>
<h2>结果</h2>
<p>环境：Windows 11 (26200) / x86_64、rustc 1.97.1 <code>--release</code>、Node v25.9.0、rpi 0.3.6、pi 0.87.1。3 轮预热后测 11 轮，取中位数。</p>
<table>
<thead>
<tr>
<th>指标</th>
<th align="right">rpi（Rust）</th>
<th align="right">pi（TypeScript）</th>
<th align="right">差距</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--version</code></td>
<td align="right">17.5 ms</td>
<td align="right">169.5 ms</td>
<td align="right">9.7×</td>
</tr>
<tr>
<td>冷启动</td>
<td align="right">17.7 ms</td>
<td align="right">189.5 ms</td>
<td align="right">10.7×</td>
</tr>
<tr>
<td>常驻内存</td>
<td align="right">12.0 MiB</td>
<td align="right">91.5 MiB</td>
<td align="right">7.6×</td>
</tr>
<tr>
<td>安装体积</td>
<td align="right">21.8 MiB（单二进制）</td>
<td align="right">约 385 MiB（另加 91 MiB Node）</td>
<td align="right">约 18-22×</td>
</tr>
</tbody>
</table>
<p>原始数据（单位 ms）：</p>
<h2>项目</h2>
<p>rpi 是 Rust 原生开发，在开发中借鉴了大量 Pi 的思想，并做了一部分优化，有感兴趣的伙伴欢迎PR</p>
<ul>
<li>GitHub：https://github.com/bigfish1913/pi-rust</li>
<li>官网：https://rpi.laofu.online/</li>
<li>免 Rust 工具链安装：<code>curl -fsSL https://raw.githubusercontent.com/bigfish1913/pi-rust/main/scripts/install.sh | sh</code></li>
</ul>
]]></description><pubDate>2026-09-30 16:23:24</pubDate></item><item><title>写了个纯 Rust 实现的 Luau 语言与 JIT 虚拟机 ulua</title><link>https://rustcc.cn/article?id=ae4f691d-a220-4903-8989-78f2847f109b</link><description><![CDATA[<p><a href="https://github.com/webc-site/ulua" rel="noopener noreferrer">GitHub</a> · <a href="https://webc-site.github.io/ulua" rel="noopener noreferrer">Playground 在线体验</a> · <a href="https://crates.io/crates/ulua" rel="noopener noreferrer">Crates.io</a></p>
<p>最近开源了 ulua，一个用纯 Rust 实现的 Luau 语言运行时。Luau 是 Roblox 基于 Lua 5.1 深度改造演进的脚本语言，具备渐进静态类型系统、寄存器虚拟机和编译优化管线。</p>
<p>在此之前，在 Rust 中嵌入使用 Luau 主要依赖基于 C++ 源码的 FFI 绑定（例如 mlua 的 luau feature）。使用 C++ 绑定需要依赖 C/C++ 交叉编译工具链，在需要静态编译、移植到嵌入式平台或编译为 WebAssembly 在浏览器内无缝运行时，配置流程会繁琐许多。</p>
<p>ulua 项目基于早期从 C++ 源码转译的 Rust 代码底座进行深度重写，包含从词法解析、AST 语法树、字节码编译、寄存器虚拟机、垃圾回收、静态类型推导到 A64/X64 本地机器码即时编译（CodeGen JIT）的完整处理管线，无需任何 C/C++ 编译依赖。</p>
<h3>现状与开发重心</h3>
<p>需要主动说明的是，项目目前处于活跃的重构与持续开发阶段：</p>
<p>当前阶段的主要工作，是集中消除 C/C++ 转译遗留下来的原始指针操作、精简 unsafe 代码块，并彻底移除各类警告忽略属性（#![allow(...)]）。我们正在逐步将大量 C 风格的数据流改写为惯用的 Rust 模式，以构建一个安全、可维护的现代化代码库。</p>
<p>目前代码尚未做细致的底层微架构性能调优。当前的执行效率主要得益于 Luau 本身简洁紧凑的寄存器虚拟机架构。我们计划在后续将所有 C 风格残余代码完全清洗、重构为纯正 idiomatic Rust 并且架构稳定后，再集中开展系统级的性能与 GC 优化。</p>
<h3>性能基准实测数据</h3>
<p>为了客观评估执行开销，测试套件采用纯 Rust 进程内微基准（消除外部子进程创建与终端 I/O 带来的干扰），在 Apple Silicon (Apple M2 Max, arm64) 平台上运行了 16 组经典计算密集用例（涵盖二叉树分配与 GC、40 万次协程切换、全排列、递归、曼德博分形、矩阵乘法、天体轨道模拟、谱范数逼近等），取各引擎多次运行的耗时中位数（单位：毫秒 ms）：</p>
<table>
<thead>
<tr>
<th align="left">基准测试用例</th>
<th align="center">ulua (纯 Rust 解释)</th>
<th align="center">mlua/luau (C++ 解释)</th>
<th align="center">ulua (JIT)</th>
<th align="center">mlua/luau (C++ JIT)</th>
<th align="center">LuaJIT (JIT)</th>
<th align="center">Lua 5.4</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">binarytrees (树分配/GC)</td>
<td align="center">105.0</td>
<td align="center">135.7</td>
<td align="center">70.4</td>
<td align="center">89.9</td>
<td align="center">49.6</td>
<td align="center">116.9</td>
</tr>
<tr>
<td align="left">coroutines (协程切换)</td>
<td align="center">83.1</td>
<td align="center">108.2</td>
<td align="center">82.8</td>
<td align="center">130.8</td>
<td align="center">26.1</td>
<td align="center">145.3</td>
</tr>
<tr>
<td align="left">fannkuch (全排列翻转)</td>
<td align="center">33.1</td>
<td align="center">28.8</td>
<td align="center">7.9</td>
<td align="center">7.8</td>
<td align="center">3.6</td>
<td align="center">22.6</td>
</tr>
<tr>
<td align="left">fib (深度递归)</td>
<td align="center">83.7</td>
<td align="center">92.6</td>
<td align="center">35.2</td>
<td align="center">34.8</td>
<td align="center">8.4</td>
<td align="center">74.9</td>
</tr>
<tr>
<td align="left">mandel (分形浮点迭代)</td>
<td align="center">93.2</td>
<td align="center">93.5</td>
<td align="center">25.4</td>
<td align="center">23.3</td>
<td align="center">11.0</td>
<td align="center">56.2</td>
</tr>
<tr>
<td align="left">matmul (矩阵乘法)</td>
<td align="center">51.8</td>
<td align="center">45.8</td>
<td align="center">12.2</td>
<td align="center">12.5</td>
<td align="center">4.3</td>
<td align="center">38.3</td>
</tr>
<tr>
<td align="left">nbody (引力轨道模拟)</td>
<td align="center">93.4</td>
<td align="center">95.7</td>
<td align="center">92.5</td>
<td align="center">92.7</td>
<td align="center">4.8</td>
<td align="center">72.1</td>
</tr>
<tr>
<td align="left">spectralnorm (谱范数)</td>
<td align="center">79.9</td>
<td align="center">74.6</td>
<td align="center">20.7</td>
<td align="center">19.9</td>
<td align="center">2.2</td>
<td align="center">90.9</td>
</tr>
<tr>
<td align="left">tablesort (快速排序)</td>
<td align="center">64.5</td>
<td align="center">66.9</td>
<td align="center">63.8</td>
<td align="center">67.4</td>
<td align="center">115.2</td>
<td align="center">241.1</td>
</tr>
<tr>
<td align="left">跨 16 组几何平均耗时比</td>
<td align="center">1.0 (基准)</td>
<td align="center">1.02</td>
<td align="center">0.52</td>
<td align="center">0.57</td>
<td align="center">0.16</td>
<td align="center">1.17</td>
</tr>
</tbody>
</table>
<p>实测数据显示，在解释执行模式下，ulua 的几何平均耗时与官方 C++ Luau 基本一致（1.0 vs 1.02），且比官方 Lua 5.4 快约 17%；在开启 JIT 机器码生成（features = ["jit"]）后，计算密集型场景耗时降低约 48%（相对解释模式由 1.0 降至 0.52），水平与官方 C++ Luau JIT (0.57) 相当。</p>
<h3>浏览器端 WebAssembly Playground</h3>
<p>由于脱离了 C++ 编译链依赖，ulua 可以直接编译到 wasm32 目标。我们部署了一个纯静态的在线演练场：</p>
<p>https://webc-site.github.io/ulua</p>
<p>页面在浏览器端运行 WebAssembly 虚拟机，支持 Luau 代码的即时语法高亮、双向静态类型推导诊断以及虚拟机执行。</p>
<h3>快速上手示例</h3>
<p>在 Cargo.toml 中添加依赖：</p>
<pre><code>[dependencies]
ulua = "0.1"
</code></pre>
<p>基础求值与字节码执行：</p>
<pre><code>use ulua::{compile, eval, eval_bytecode};

fn main() -&gt; Result&lt;(), Box&lt;dyn std::error::Error&gt;&gt; {
  // 直接求值
  eval("assert(1 + 1 == 2)")?;

  // 编译为二进制字节码
  let bytecode = compile("assert(10 * 20 == 200)")?;

  // 直接执行预编译字节码
  eval_bytecode(&amp;bytecode)?;

  Ok(())
}
</code></pre>
<p>宿主环境交互与 JIT 控制：</p>
<pre><code>use ulua::prelude::*;

fn main() -&gt; Result&lt;()&gt; {
  let lua = Lua::new();

  // 默认使用纯解释执行
  lua.load("print('Hello from ulua interpreter')").exec()?;

  // 可通过 API 动态开关 JIT 机器码生成
  #[cfg(feature = "jit")]
  {
    lua.enable_jit(true)?;
    let sum: i64 = lua.load(r#"
      local total = 0
      for i = 1, 1000000 do
        total += i
      end
      return total
    "#).eval()?;
    assert_eq!(sum, 500000500000);
  }

  // 向 Luau 注册 Rust 函数
  let add = lua.create_function(|_, (a, b): (i64, i64)| Ok(a + b))?;
  lua.globals().set("add", add)?;

  let res: i64 = lua.load("return add(10, 20)").eval()?;
  assert_eq!(res, 30);

  Ok(())
}
</code></pre>
<h3>交流与反馈</h3>
<p>项目已在 GitHub 开源，发布于 crates.io。目前我们正在逐步清理各类历史遗留的 unsafe 块并补全测试用例。欢迎大家试用、提出 issue 反馈或提交 PR 共同改进。</p>
]]></description><pubDate>2026-09-30 07:58:47</pubDate></item><item><title>开源｜TeaQL Registry：用 Rust 做一个面向 AI Agent 与 CI/CD 的多格式制品仓库</title><link>https://rustcc.cn/article?id=4952e2e6-6b40-4dd7-a898-5d708e8df914</link><description><![CDATA[<p>最近我们开源了 <a href="https://github.com/teaql/teaql-registry" rel="noopener noreferrer">TeaQL Registry</a>。它是一个使用 Rust、Axum、Tokio、TeaQL 和 PostgreSQL 构建的多格式制品仓库，目标不是替代 Nexus 或 JFrog 这样的企业主仓库，而是部署在 AI 编码沙箱、构建节点或集群内部，承担高频、短生命周期的中间制品交换与缓存。</p>
<h2>为什么又做一个 Registry？</h2>
<p>AI Agent 和高吞吐 CI 的工作方式与人工发版不太一样：一次任务可能反复经历“改代码—打包—部署—测试—再修改”，产生很多只存活几分钟或几小时的 snapshot、crate、wheel、容器层和工具链压缩包。把这些中间产物全部推到中心仓库，会引入网络延迟、限流，也会让长期仓库承担不必要的清理压力。</p>
<p>TeaQL Registry 更像是靠近 Runner 的 L1 Registry：</p>
<pre><code>AI Agent / CI Runner
        │ 高频 publish / pull
        ▼
TeaQL Registry（本地或集群内）
        │ 只提升最终验证通过的版本
        ▼
Nexus / JFrog / 云端主仓库
</code></pre>
<h2>目前支持什么？</h2>
<p>项目目前支持 14 种制品格式：Docker Registry v2、Maven2、npm、PyPI、Cargo、Go Modules、NuGet、Swift Package Registry、Dart Pub、RubyGems、Composer、Conan 2、Hex，以及 Raw/Generic。</p>
<p>这里的重点是“软件开发过程中的组件”，而不是面向终端用户的软件商店。比如跨平台预编译工具链，我们建议直接用 Raw/Generic 保存按 OS、架构和版本组织的 <code>.tar.gz</code> / <code>.zip</code>；如果交付物本身是容器镜像，就使用 Docker/OCI；如果它属于某个语言生态，再选择对应的原生协议。</p>
<p>服务端支持 S3 兼容存储（RustFS、MinIO、AWS S3）、文件系统和内存模式，Blob 使用 SHA-256 内容寻址去重。管理侧提供多租户、RBAC、PAT、审计记录、保留策略和孤儿 Blob GC，并带有嵌入式 Web Console 与独立的 Ratatui TUI。</p>
<p><img src="https://raw.githubusercontent.com/teaql/teaql-registry/main/docs/images/teaql-registry-tui.png" alt="TeaQL Registry TUI"></p>
<p>TUI 适合只有 SSH 的跳板机或构建节点，可以查询服务状态、仓库和组件，执行搜索、检查、清理、GC 与临时令牌生成。</p>
<h2>Rust 在这里解决了什么？</h2>
<p>Registry 的数据面有大量流式上传下载、校验、协议路由和并发请求。Rust 让我们可以在一个进程里完成协议适配、元数据服务和 Blob 流式传输，同时把资源生命周期和错误路径尽量前移到编译期处理。Axum/Tokio 负责 HTTP 与异步 I/O，TeaQL 负责领域模型、权限和可审计的数据变更，PostgreSQL 保存元数据；Web Console 被嵌入最终二进制，容器运行时镜像可以保持很小。</p>
<p>另一个实际收益是部署形态：服务端和 <code>registry-tui</code> 都可以构建成独立二进制，适合放进构建集群或受限网络环境。</p>
<h2>TeaQL 在这个项目里做了什么？</h2>
<p>Registry 很适合检验一个业务框架是否真的能处理复杂场景：它既有多租户和权限边界，又有大量列表查询，还要承受高频写入、版本竞争和跨实体关系读取。在 TeaQL Registry 中，TeaQL（下文简称 TQ）不是只负责把表映射成 Rust struct，而是把查询意图、租户上下文、变更审计、Mutation Policy 治理和数据加载状态一起带进业务代码。本文中的 Mutation Policy Approval 基于 TeaQL Rust Runtime 5.0.5。</p>
<p>这套 TeaQL 开发工具链已经支持 Java、Rust、TypeScript、Go、Swift、.NET 和 Python 七个运行时。同一份语义模型可以生成对应语言的类型化领域库与应用工作区，并通过模型感知 Assist 提供查询、分页、创建、修改、删除和表达式等开发指导。TeaQL Registry 选择 Rust 实现，但下面这些 TQ 编程模型并不只服务于 Rust。</p>
<h3>1. 在 TQ 执行入口集中实施租户隔离</h3>
<p>多租户隔离最怕依赖开发者记忆：如果要求每个 Service 都手写一次 <code>filter_by_tenant(...)</code>，迟早会有新接口漏掉。TeaQL Registry 把隔离放在 TQ 的统一执行入口 <code>RequestPolicy</code> 中。每一条查询在交给数据服务之前，都会经过 <code>enforce_select</code>；策略从可信 context 取得当前租户，并把条件与原查询合并：</p>
<pre><code>impl RequestPolicy for TeaQLRegistryTenantRequestPolicy {
    fn enforce_select(
        &amp;self,
        context: &amp;UserContext,
        query: &amp;mut SelectQuery,
    ) -&gt; Result&lt;(), RuntimeError&gt; {
        // 集中维护模型中带 tenant 关系的实体集合
        if tenant_scoped_entities.contains(&amp;query.entity.as_str()) {
            let tenant_id = context
                .get_resource::&lt;TenantInfo&gt;()
                .map(|tenant| tenant.tenant_id)
                .unwrap_or(1);
            let tenant_filter = Expr::eq("tenant_id", tenant_id);
            query.filter = Some(match query.filter.take() {
                Some(existing) =&gt; existing.and_expr(tenant_filter),
                None =&gt; tenant_filter,
            });
        }
        Ok(())
    }
}
</code></pre>
<p>HTTP 认证层只负责解析 Token、<code>X-TeaQL-Tenant</code> 或带租户限定的用户名，确认用户属于目标租户，然后创建请求级 TQ Runtime。<code>set_tenant(...)</code> 会同时写入 <code>TenantInfo</code> 并安装上述策略。后续业务查询只接收这个 context，不再传递 <code>tenant_id</code>，也不需要重复拼租户条件：</p>
<pre><code>let repositories = RepositoryService::list(&amp;tenant_context).await?;
let users = SecurityService::list_users(&amp;tenant_context).await?;
</code></pre>
<p>项目的多租户集成测试会创建两个 context，然后用完全相同、没有租户参数的调用分别查询仓库、BlobStore 和用户，验证结果只来自各自租户。隔离因此是“默认发生”的基础设施能力，而不是散落在业务代码里的编码约定。</p>
<p>PAT 也绑定 <code>tenant_id + user_id + username</code>；即使原生包管理器把 Token 放在 Basic Auth 密码位置，服务端仍会重新校验这三者与当前租户是否一致。</p>
<h3>2. 分页查询仍然保留类型和意图</h3>
<p>管理端的服务日志查询是一个直接例子。过滤条件、稳定排序和分页都通过模型生成的 <code>Q</code> API 表达：</p>
<pre><code>let rows = Q::service_logs_minimal()
    .select_self_fields()
    .order_by_id_desc()
    .offset(offset as u64, limit as u64)
    .comment("what: query registry service logs with bounded pagination")
    .purpose("why: render best-effort operational package history")
    .execute_for_list(context)
    .await?;
</code></pre>
<p>这段查询故意没有 <code>filter_by_tenant(...)</code>：租户条件会在 TQ 执行入口由 <code>RequestPolicy</code> 自动注入。<code>order_by_id_desc()</code> 给翻页提供确定顺序，<code>offset/limit</code> 限制单次物化规模；字段选择、关系选择和业务过滤方法则来自模型生成代码，字段改名或关系不存在会在编译阶段暴露。对于需要同时返回分页元数据的列表，TQ 还提供 <code>execute_for_page(context, offset, limit)</code>，返回 <code>SmartList&lt;T&gt;</code>，并由 Runtime 统一执行硬上限和查询策略。</p>
<p>这里的 <code>comment</code> 与 <code>purpose</code> 也不是装饰：它们把“查询了什么”和“为什么查询”带到执行阶段，缺失查询意图可以被策略拒绝，便于审计和诊断慢查询。</p>
<h3>3. 修改数据走完整实体、校验和审计链</h3>
<p>项目不会把外部 DTO 直接翻译成 <code>UPDATE</code>。修改组件前先通过同一个租户 context 加载完整实体，再调用模型生成的 <code>update_xxx</code> 方法，最后附加业务原因并保存。例如删除组件采用软删除：</p>
<pre><code>let rows = Q::components()
    .select_self_fields()
    .with_id_is(component_id)
    .limit(1)
    .comment("what: query registry component metadata")
    .purpose("why: resolve package components and their assets")
    .execute_for_list(context)
    .await?;

if let Some(mut component) = rows.into_iter().next() {
    component.update_kind("deleted");
    component.update_name(format!("[DELETED_{}]", component.id()));
    component
        .audit_as("Deleting component")
        .save_with(context)
        .await?;
}
</code></pre>
<p>完整加载很重要：目标实体首先经过集中租户策略查询，TQ 的 checker 可以看到完整业务状态，实体原始版本也能参与乐观锁判断。<code>audit_as(...)</code> 让每次持久化都携带明确的业务原因；保存继续使用同一个请求 context，使查询、修改和审计处在同一条请求链路中。</p>
<h3>4. 在事务之前完成 Mutation Policy Approval</h3>
<p><code>audit_as(...)</code> 解决的是“这次修改为什么发生”，Mutation Policy Approval 解决的是另一个问题：“允许这类修改的规则本身，是否经过了明确评审？”这不是订单、工单一类业务单据的人工审批流，而是对应用自定义写策略的版本化批准。</p>
<p>TeaQL 5.0.5 会在 Checker/Fix 和完整 Graph Planning 之后生成不可变的 <code>MutationPlan</code>。其中包含根实体、审计原因、Create/Update/Delete/Recover 操作、原始版本以及实际变更字段。Runtime 在开启数据库事务、执行第一条写入之前，把整份计划交给 <code>MutationPolicy::review</code>：</p>
<pre><code>实体变更
   │
   ▼
Checker / Fix ──► 不可变 MutationPlan
                         │
                         ▼
                MutationPolicy::review
                    │              │
                  Deny           Allow
                    │              │
          事务开始前返回错误     精确查找 Policy Approval
                                   │
                                   ▼
                           事务写入 + 审计事件
</code></pre>
<p>应用策略可以对整张变更图做判断，而不必把规则散落在每个 Service 方法中。例如，Registry 可以要求每次写入都带审计原因，并限制一次保存携带的操作数量：</p>
<pre><code>use teaql_runtime::{
    MutationDecision, MutationPlan, MutationPolicy, MutationPolicyIdentity, UserContext,
};

struct RegistryMutationPolicy;

impl MutationPolicy for RegistryMutationPolicy {
    fn identity(&amp;self) -&gt; MutationPolicyIdentity {
        MutationPolicyIdentity::new(
            "teaql-registry-write-policy",
            "1.0.0",
            "sha256:&lt;reviewed-policy-fingerprint&gt;",
        )
    }

    fn review(&amp;self, _context: &amp;UserContext, plan: &amp;MutationPlan) -&gt; MutationDecision {
        let missing_reason = plan
            .audit_reason
            .as_deref()
            .map(str::trim)
            .map(str::is_empty)
            .unwrap_or(true);
        if missing_reason {
            return MutationDecision::deny(
                "REGISTRY-AUDIT-REASON-REQUIRED",
                "registry mutations require an audit reason",
                ["audit_reason"],
            );
        }
        if plan.operations.len() &gt; 128 {
            return MutationDecision::deny(
                "REGISTRY-MUTATION-TOO-LARGE",
                "one registry mutation may contain at most 128 operations",
                ["operations"],
            );
        }
        MutationDecision::allow()
    }
}
</code></pre>
<p>Policy 通过后，Runtime 再向可信的 <code>MutationPolicyApprovalProvider</code> 查询批准记录。批准必须同时匹配 Policy 的 <code>id + version + fingerprint</code>；只改版本号或代码指纹而沿用旧批准，都会被识别为未批准。Policy Registry、Approval Provider 和告警 Sink 都在服务端组装 <code>UserContext</code> 时安装，HTTP 或 TFP 请求不能从外部替换它们。</p>
<p>这里还有一个值得明确的兼容边界：没有安装客户 Policy 时，Runtime 允许原有写入并产生 <code>MUTATION-POLICY-001</code>；安装了客户 Policy 但没有精确批准时，会产生 <code>MUTATION-POLICY-002</code>。这两个状态目前是治理告警，不会冒充强制审批。真正的 Policy <code>Deny</code> 则会在事务开始前终止保存，保证没有部分数据落库。允许执行时，Policy 身份、Approval 状态、告警码和变更字段摘要会随 <code>MutationGovernanceSnapshot</code> 进入审计事件，让“哪一版规则批准了哪类变更”成为可追踪证据。</p>
<p>这对 AI Agent 尤其有意义：Agent 仍然可以高频修改对象，但能够放行写入的规则是有身份、有版本、有指纹的；策略代码变化后必须重新批准，不能静默继承上一版信任。</p>
<h3>5. 用 E 表达式区分“空值”和“根本没加载”</h3>
<p>Rust 的 <code>Option</code> 能表达“有值或为空”，但数据库投影还有第三种状态：字段或关联根本没有被查询。若把后两者都当成 <code>None</code>，程序可能把一个漏写 <code>select</code> 的编码错误误判成正常业务空值，并继续产生错误结果。</p>
<p>TQ 根据模型生成 <code>E</code> 表达式，保留三种状态：</p>
<ul>
<li><code>Value(value)</code>：字段已加载且有值；</li>
<li><code>Null</code>：字段已加载，业务上确实为空；</li>
<li><code>NotLoaded</code>：查询没有预加载该字段或关联，是程序错误。</li>
</ul>
<p>例如读取 Asset 的 Blob 关联和路径时，查询端必须先明确选择要遍历的关联，然后再用 <code>E</code> 读取：</p>
<pre><code>let assets = Q::assets()
    .select_self_fields()
    .select_asset_blob()
    .with_id_is(asset_id)
    .limit(1)
    .comment("what: load one asset and its blob metadata")
    .purpose("why: verify package content before download")
    .execute_for_list(context)
    .await?;

let asset = assets.into_iter().next().expect("asset exists");
let blob = E::asset(&amp;asset).get_asset_blob().eval();
let path = E::asset(&amp;asset)
    .get_path()
    .or_if_null("unknown-path".to_owned());
</code></pre>
<p><code>eval()</code> 会把已加载的值或空值转换成 <code>Option</code>，但遇到 <code>NotLoaded</code> 会带着缺失访问路径立即失败；<code>or_if_null(...)</code> 也只为真正的数据库 <code>NULL</code> 提供默认值，不会掩盖漏加载。</p>
<p>当前模型生成的 <code>E</code> facade 已覆盖 Asset 的标量、<code>ContentRepository</code> 和 <code>AssetBlob</code> 前向关系。项目现有手写 Service 对 <code>select_self_fields()</code> 后的标量仍采用直接 accessor，尚未把跨关系读取迁移到 <code>E::</code>；所以上面的代码展示的是已经生成并验证过的安全访问能力，而不是声称所有业务路径都已完成迁移。后续涉及最小投影和跨关系遍历时，优先使用 E 表达式，可以让“数据为空”和“程序写错了查询”保持清晰边界。这种尽早失败比在很远的业务分支里产生错误制品元数据更容易定位，也更适合 Registry 这类基础设施服务。</p>
<h2>5 秒启动</h2>
<p>镜像和 <code>.env</code> 已准备好时，启动只有一条命令：</p>
<pre><code>docker compose up -d
</code></pre>
<p>服务端和 TUI 都是 Rust 编译出的独立二进制，没有 JVM 或脚本运行时的预热过程；在本地或集群节点热启动时，应用进程通常可以在数秒内进入工作状态。这里的“5 秒”指应用自身的启动体验，首次拉取镜像以及等待 PostgreSQL、S3 就绪的时间仍取决于网络和部署环境。</p>
<p>首次部署只需先准备仓库和环境变量：</p>
<pre><code>git clone https://github.com/teaql/teaql-registry.git
cd teaql-registry
cp .env.example .env
# 在 .env 中设置 POSTGRES_PASSWORD 和 S3_SECRET_KEY
# 然后执行上面的 docker compose up -d
</code></pre>
<p>启动后可访问：</p>
<ul>
<li>Web Console：<code>http://localhost:8081/</code></li>
<li>REST API：<code>http://localhost:8081/service/rest/v1/...</code></li>
<li>Prometheus Metrics：<code>http://localhost:8081/metrics</code></li>
<li>快速帮助：<code>http://localhost:8081/help</code></li>
</ul>
<p>TUI 可以这样运行：</p>
<pre><code>cargo run -p registry-tui -- \
  --endpoint http://localhost:8081 \
  --username admin
</code></pre>
<p>管理员初始密码默认随机生成并写入凭据目录，也可以在首次启动前通过 <code>ADMIN_PASSWORD</code> 显式设置；项目没有内置默认密码。</p>
<h2>当前状态与我们希望得到的反馈</h2>
<p>仓库已经有 workspace 测试、协议生命周期测试和覆盖多个生态的原生客户端验证。不同生态的边角兼容性仍然很多，尤其是代理/聚合仓库行为、签名元数据以及各版本客户端差异，这些正是我们接下来希望继续打磨的部分。</p>
<p>如果你正在做 AI Agent、内部构建平台、离线开发环境，或者只是对“用 Rust 实现多协议 Registry”感兴趣，欢迎试用、提 Issue 或 PR。我们尤其希望听到下面几类反馈：</p>
<ol>
<li>你最常用、但目前还缺失的开发制品格式；</li>
<li>Proxy / Group 仓库在真实团队里的使用方式；</li>
<li>跨平台预编译工具链的命名、索引和分发体验；</li>
<li>哪些协议值得优先补齐更严格的官方兼容测试。</li>
</ol>
<p>项目地址：<a href="https://github.com/teaql/teaql-registry" rel="noopener noreferrer">https://github.com/teaql/teaql-registry</a></p>
<p>许可证：Apache-2.0</p>
]]></description><pubDate>2026-09-30 07:46:08</pubDate></item><item><title>【Rust日报】2026-09-30 MySQL 读负载降 99%</title><link>https://rustcc.cn/article?id=a00ffddb-aa21-44a0-a116-2e3addbeaa61</link><description><![CDATA[<h2>rivet：直接读 MySQL binlog 进 BigQuery</h2>
<p>rivet 是一个开源 Rust CLI，从 OLTP 数据库读取变更并写入数仓。作者完成了第一轮生产试点：MySQL 由 rivet 直接读 binlog，写成 Parquet 后放到 Google Cloud Storage，再进入 BigQuery。链路上是一台虚拟机加 cron，不经过 Kafka、Debezium 或 Airflow。</p>
<p>试点库有 154 张表、32 亿行，每天刷新 8 次。原管道用日期窗口拉取变更，每周再全量重载一次；没有 <code>updated_at</code> 变化的修改要等到周重载才会被看见。对照同一数据库上的原管道：第一天发现 880 行事后被改过、但时间戳没动的记录，抽查后确认是过期余额。MySQL 副本读负载约降 99%，每月约 4 TB 读取（其中约 95% 来自每周全量）变成约 24 GB binlog 流。AWS 到 GCP 出站流量约降 98%，每天约 150 MB 压缩 Parquet，不再每周传整库快照。BigQuery 的 MERGE 作业少 25%，每次 MERGE 的字节少 30%。同等刷新频率下，BigQuery、GCS 与出站合计基础设施成本约降 50%。存储变化约在 ±2%。</p>
<p>完整一轮目前约 68 分钟：读完全部 154 张表的 binlog 约 9 分钟，其余是逐表加载和合并。日志里 BigQuery 忙碌时间约占 25%–30%。作者估计升级到最多 16 张表并行后，一轮可能到 10–25 分钟，试点升级后的数字还没测完。仓库：<code>panchenkoai/rivet</code>。说明页：https://panchenkoai.github.io/rivet/cheat-sheet.html</p>
<p>原文链接：https://www.reddit.com/r/rust/comments/1wtm323/replaced_a_weekly_full_reload_mysql_bigquery_with/</p>
<h2>用 Iced 重写整个界面</h2>
<p>Git Cherry Tree 是一个用 Rust 写的 Git 客户端。作者把原先的 Tauri + React 界面换成 Iced。</p>
<p>换之前，Linux 上的 Tauri 构建大约 90 MB，也不能像 Windows 那样直接发一个免安装二进制。Windows 上跨进程调用的延迟至少 3 ms。Tauri command 大约 2–10 MB/s；一次传很大的负载大约 100 MB/s，传输期间界面会卡住；绕过 Tauri command API 的内部下载协议大约 40 MB/s。有杀毒软件把启动浏览器这一步报成诈骗软件。</p>
<p>换完后，界面代码从约 2.4 万行 TypeScript 增到约 4.5 万行 Rust，其中包含为了打磨效果而放进仓库的一部分 Iced 组件。二进制从 Web 版的 15 MB，到第一版 Iced 的 8 MB，功能补齐后到 23 MB，再压回 15.3 MB；若把 panic 设为 abort，大约还能到 11 MB。高负载下帧时间从约 9 ms 降到 3–6 ms。Linux 上可以打成单个可运行二进制。作者自写了一个只生成着色 span 的语法高亮，支持 30 多种语言、70 个文件扩展名；百万行 diff 从点击到显示大约 700 ms，大约比 Web 版快 3–4 倍。图标没有用完整 SVG 渲染器，那个依赖大约会增加 3 MB。</p>
<p>原文链接：https://www.gitcherrytree.com/blog/rewriting-the-ui-in-rust/</p>
<h2>用 Rust 写 Linux 内核模块</h2>
<p>这是一篇在 Ubuntu 26.04 和 Raspberry Pi 4 上搭建 Rust 内核模块开发环境的记录。目标是给 Pi 4 上的 16x2 LCD 写驱动；本文只写环境，下一篇给驱动规格和最小示例。环境按 2026-09-27 在 Ubuntu 26.04 LTS 上验证。</p>
<p>Raspbian 内核默认没有打开 Rust。作者按树莓派文档交叉编译内核，并关掉与 Rust 不兼容的 <code>CONFIG_MODVERSIONS</code>。模块目录里把 rustup 工具链固定到 1.93.1 后，交叉编译出的 <code>hello_rust.ko</code> 可以在 Pi 上 <code>insmod</code>，dmesg 打出 <code>Hello from Rust!</code> 和 <code>Goodbye from Rust!</code>。本机 Ubuntu 构建则不用 rustup：需要 apt 安装 <code>rust</code>、<code>rust-src</code> 和与当前内核版本对应的 <code>linux-lib-rust-$(uname -r)</code>，并且不要加 <code>LLVM=1</code>，因为 Ubuntu 预编译内核走的是 gcc。作者注明 Hello Rust 示例里有一部分代码来自 Gemini，Makefile 做了小改。</p>
<p>原文链接：https://thehecknow.hashnode.dev/implementing-a-linux-device-driver-in-rust</p>
<h2>cargo-crap 0.6：给重复函数做类型化分级</h2>
<p>cargo-crap 是用来找出 Rust 代码里重复函数的命令行工具。0.5.0 的 <code>--duplicates</code> 用 syn 把函数解析成树，抹掉函数名、变量名、字段名、路径名和字面量，再按子树指纹的 Jaccard 相似度比较。默认阈值 0.82。在作者自己的源码上，这一步报出 24 对，其中既有改名后的同一段逻辑，也有只是都调用了 <code>writeln!</code> 的相似形状。</p>
<p>0.6.0 增加可选 triage：把每一对函数体和相似度发给 TypeSafe 的 System One 模型（默认 <code>jev-latest</code>），返回四种类型之一（<code>same-logic</code>、<code>parameterisable</code>、<code>shared-shape-only</code>、<code>structural-obligation</code>）、是否值得合并，以及一边修 bug 另一边会不会漏修的概率。置信度默认低于 0.5 时只标 <code>uncertain</code>。结果缓存在 <code>target/</code>，键包含两边函数体、模型和问题集的哈希。triage 只加注释，不改变退出码；没有密钥、没有网络或某一对请求失败时，整段分级会被丢掉，退回原来的结构报告。默认关闭，要在 <code>.cargo-crap.toml</code> 里打开，并且环境变量里有 <code>TYPESAFE_API_KEY</code>。发布二进制自带该功能；从源码安装需要 <code>--features triage</code>。</p>
<p>原文链接：https://minikin.me/blog/cargo-crap-triage</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-30 01:07:47</pubDate></item><item><title>【Rust日报】2026-09-29 24 核机器在 Polars 里空转</title><link>https://rustcc.cn/article?id=1a8f128e-b431-4568-b118-2a7a1f4ddec8</link><description><![CDATA[<h2>24 核机器在 Polars 里空转</h2>
<p>这是一篇对 Polars 并行执行的性能剖析，记录一个 profiling 周期里的第一批发现。测试机是 24 核 / 48 线程的 Intel Xeon Gold 5412U，负载用 TPC-H；scale factor 30 时 lineitem 约 1.8 亿行，orders 约 4500 万行。</p>
<p>一条 <code>count(*) ... where l_comment like '%special%'</code> 在 1 线程上约 20.6 秒，24 线程上约 2.76 秒，加速大约 7.5 倍。过滤被下推到 Parquet 读取。正则库用缓冲池借出 scratch 内存：池里有一个免锁槽留给最先用到该正则的线程，其余线程走互斥锁。Polars 把同一个正则对象交给每个解码线程后，其余 23 个线程会堵在借还缓冲上。修复是给每个线程一份自己的正则。8 通道内存机器上，<code>like '%special%'</code> 从 2544.0 ms 降到 1085.9 ms（-57.23%，6/6 轮），<code>like 'the%'</code> 从 1835.3 ms 降到 677.8 ms（-62.85%）；24 线程扩展从约 8.6 倍升到约 20.4 倍，单线程时间基本不动。</p>
<p>同一周期里，窗口函数也出现空转。同样约 1.8 亿行上，<code>sum() over (partition by ...)</code> 约 2617 ms、约 23.06 核忙碌；带 <code>order by</code> 的 <code>row_number()</code> 约 50492 ms、约 2.29 核忙碌。组数很少时，组内排序只占少数线程。顶层调用在组数少于线程数时改走 Rayon 并行排序后，3 个分组的查询从 20402 ms 降到 10557 ms（约 -48%），50 个分组的对照查询保持持平。</p>
<p>原文链接：https://abokhalill.github.io/missing-cores-part-1/</p>
<h2>fframes 1.0：用 Rust 和 SVG 在 GPU 上渲染视频</h2>
<p>fframes 是用 Rust 写视频、用 SVG 描述画面、再在 GPU 上渲染的视频框架。作者在 r/rust 宣布该项目约五年后发布 1.0。每一帧由 Rust 函数通过 <code>svgr!</code> 宏返回一棵 SVG 树，静态片段在编译期哈希并缓存。GPU 后端用 Skia：macOS 走 Metal，Linux / Windows 走 Vulkan。仓库说明该后端约比内置 CPU 后端快 10 倍。编码直接链接 ffmpeg 的 libav 库。SVG 不够时可以叠 SkSL 或 Shadertoy GLSL shader。</p>
<p>仓库示例称一段 128 秒视频渲染耗时 36 秒。命令行可以导出时间轴、抽帧、接触表和音量分析，并写出 mp4；<code>render --draft</code> 用半尺寸编码单个场景。crate 名为 <code>fframes</code>，仓库为 <code>dmtrKovalenko/fframes</code>。</p>
<p>原文链接：https://github.com/dmtrKovalenko/fframes</p>
<h2>Philbin：带运行时 CPU 探测的纯 Rust AEGIS 库</h2>
<p>Philbin 是 AEGIS 认证加密算法的纯 Rust 实现，crate 名 <code>philbin</code>。AEGIS 是 CAESAR 竞赛获胜算法之一，对应规范为刚发布的 RFC 10032。文章称，在有 AES 硬件加速的 CPU 上，它快于 AES-GCM 和 ChaCha20-Poly1305。作者说明这是目前唯一用运行时 CPU 探测分发到对应 SIMD 实现的纯 Rust 版本：普通 release 二进制覆盖 x86-64 与 AArch64，并带软件回退，不需要 <code>target-cpu=native</code>。</p>
<p>实现里 <code>unsafe</code> 块一共两行，用 CPU feature token 做编译期和运行期检查。测试包含 IETF 与 Rooterberg 向量，并相对 libaegis 做差分 fuzz；另有 TVLA harness 检查与密钥相关的非常量时间。API 分成 <code>easy</code> 和 <code>careful</code>。<code>easy</code> 内部处理 nonce，使用操作系统 CSPRNG，默认 256 位认证标签和 AEGIS-256X4。库会拒绝全零密钥和全零 nonce。仓库：<code>Valloric/philbin</code>。</p>
<p>原文链接：https://val.markovic.io/articles/philbin-the-safest-and-fastest-aegis-library</p>
<h2>Rust 领导理事会九月更新</h2>
<p>这是 Rust Leadership Council 自 7 月 6 日更新以来的工作说明。9 月代表选举结果：Infrastructure 仍由 Jakub Beránek（@kobzol）代表，Language 由 Pete LeVasseur（@PLeVasseur）代表，Library 由 Mark Rousskov（@Mark-Simulacrum）代表，Moderation 仍由 Oli Scherer（@oli-obk）代表。离任代表是 TC 与 Josh Triplett。下一轮代表选举在 2027 年 3 月。</p>
<p>理事会已启动两名 Rust Foundation 项目董事选举，提名已收集，投票在 10 月进行。Funding 团队在此前 5 万美元之外再获 12 万美元，与 Rust Foundation Maintainers Fund 一起支持了 7 名贡献者，其中 5 名 Maintainer in Residence、2 名 Maintenance Grantee。项目优先级预算改为当年预算当年用完。Mentors 团队的 2026 年 12 月 Outreachy 轮次追加 3.2 万美元。差旅预算在原有 10 万美元之外再追加 32325 美元。</p>
<p><code>rust-lang/rust</code> 仓库已有正式 LLM 政策，多个相关仓库跟进。理事会决定新建 LLM policy 团队，负责项目级政策与后续更新。理事会观察员政策已更新。Rust Foundation 已全职聘用此前由项目优先级预算资助的 Tomáš Šedovič，担任项目经理。此前负责撰写理事会更新的 Eric Huss 已离开理事会。</p>
<p>原文链接：https://blog.rust-lang.org/inside-rust/2026/09/28/leadership-council-update/</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-29 01:10:10</pubDate></item><item><title>keepane，AI 时代的终端多路复用</title><link>https://rustcc.cn/article?id=026712a5-f981-4e33-9350-11cdb1424207</link><description><![CDATA[<p>keepane 是用 Rust 写的，并非单纯的终端多路复用器：每个 pane（窗格）之间可以互相通信、投递消息，配过对的两台电脑之间也行，手机扫个码也能控制和观测。平时用起来和 tmux 一样，快捷键、命令都照旧，配置文件也基本兼容，同时也支持跨平台。</p>
<p>几个 pane 里各跑一个 agent 的时候，想让它们配合起来很麻烦，就慢慢往这个方向做了。现在大概有这些：</p>
<ol>
<li>pane 之间通信。每个 pane 有自己的收件箱，可以从一个 pane 给另一个 pane 发消息，或者让它执行一条命令，结果和输出之后都能查到。pane 有三种模式：手动模式，消息先放着，程序自己来取；shell 模式，收到就当命令执行；ai 模式，交给里面的 agent 处理。</li>
<li>跨机器通信。两台电脑在同一个局域网，或者都连着 tailscale，配对一次（和 ssh-copy-id 差不多，之后就不用再管密钥了），一台上的 pane 就能给另一台上的 pane 发消息，回信会回到发消息的那个 pane。也能看对方机器的状态，看命令在那边跑得怎么样；对方同意的话，还能看它某个 pane 的屏幕。安全上我做得比较保守：对方发来的消息默认只交给 agent，想让它在 shell 里直接执行，得你自己在本机手动打开，pane 里的 agent 开不了这个权限。</li>
<li>MCP。agent 可以通过 MCP 自己开 session、开 pane、给别的 pane 发消息，另一台电脑上的也可以。</li>
<li>手机上控制。扫码就能看到所有 session、window 和 pane，也能往里发命令，命令还是在电脑上跑。pane 可以调成手机屏幕的大小，看全屏的程序会方便一些。</li>
<li>dashboard。可以看每个 pane 现在在做什么、之前做过什么，收件箱和任务也在里面。</li>
<li>剩下的和 tmux 一样。关了终端，pane 里的程序还在跑；电脑重启以后，用 <code>keepane resume</code> 能把 session 恢复回来；原来的 <code>.tmux.conf</code> 可以用 <code>keepane import-config</code> 导进来。</li>
</ol>
<p><img src="https://dfine.tech/keepane/img/keepane-tour.gif" alt=""></p>
<p>安装的话，macOS 和 Linux 用 <code>brew install newdee/tap/keepane</code>；Windows 用 scoop（<code>scoop install https://raw.githubusercontent.com/newdee/keepane/master/packaging/scoop/keepane.json</code>），或者去 Release 下载 MSI、zip。装了 Rust 工具链的话，也可以 <code>cargo install --git https://github.com/newdee/keepane --locked</code>。</p>
<p>还在一直改，肯定有很多没考虑到的地方。欢迎试用，也欢迎提 issue 或者直接在这里回复。</p>
<p>项目地址：<a href="https://github.com/newdee/keepane" rel="noopener noreferrer">https://github.com/newdee/keepane</a>
详细介绍：<a href="https://dfine.tech/posts/626063bd/" rel="noopener noreferrer">https://dfine.tech/posts/626063bd/</a></p>
]]></description><pubDate>2026-09-28 14:30:55</pubDate></item><item><title>Noeio v0.2.0 分享一个适用于 Sandbox 的轻量、无状态的三层组网工具</title><link>https://rustcc.cn/article?id=ac303e81-3904-4f90-b55f-fa666353b02a</link><description><![CDATA[<p>Noeio v0.2.0</p>
<p>最近新增了一下[子网路由]的功能。主要场景是支持组网内的节点通过路由转发的能力去访问内网环境的服务。</p>
<p>Noeio 是我之前写的一个 3 层轻量级无状态的组网工具，其实和 Tailscale 类似，这个项目的初衷还是出于学习的目的。主要是分享一下，同时探索一下能否在 Sandbox 中有更好的应用场景，AI 时代感觉未来这种组网互联的需求应该蛮旺盛的</p>
<p>下一步：</p>
<ol>
<li>实现一下 port-forward 端口转发的能力</li>
</ol>
<p>GitHub：https://github.com/noeio-net/noeio-core
子网路由介绍：https://noeio.net/zh/guides/intranet-gateway</p>
]]></description><pubDate>2026-09-28 03:30:09</pubDate></item><item><title>【Rust日报】2026-09-28 PolyXOR128：带 Lean 证明的高速 128 位通用哈希</title><link>https://rustcc.cn/article?id=279a3fd3-fe7f-46e4-9017-223cf5a6f23a</link><description><![CDATA[<h2>PolyXOR128：带 Lean 证明的高速 128 位通用哈希</h2>
<p>PolyXOR128 是用 Rust 实现的 128 位通用哈希（universal hash），面向文件/数据流完整性校验，并强调密码学强度下的低碰撞概率。作者 orlp（此前有 polymur-hash、foldhash 等）发布仓库 <code>orlp/polyxor</code>：在密钥与输入独立且随机的前提下，长度至多 n 字节的两条消息碰撞概率上界为 (n/4096 + 3) / 2^128，证明用 Lean 完整形式化。</p>
<p>吞吐上，作者在中大型输入上对比 crates.io 常见实现：除 CRC64 外快于所测主流哈希；在带 AVX-512 的机器上 bulk 吞吐可接近 foldhash 的约两倍。单线程示例数据包括 Ryzen 9950X 最高约 130 GB/s、Apple M2 Pro 约 60 GB/s，并给出 Xeon 等图示。实现依赖硬件加速的无进位乘法（carryless multiply），对 AArch64 / x86-64 做动态分发；便携参考实现极慢，嵌入式可能受限。密钥展开实例约 5 KB 内存；小串（如 ≤256 字节）目前零填充到 128 字节倍数，尚未专项优化。</p>
<p>通用哈希要求密钥对数据保密且不公开；跨不安全信道应使用 <code>finalize_mac</code>（用 AES 封装哈希）再传输。它不能替代无法保密密钥时的抗碰撞哈希（例如公开包校验和）。设计上对 128 字节块混入密钥、奇偶扩展后做类似 CLHASH 的 NH 变体，SIMD 累加后再进入多项式归约；另有 <code>design.md</code>。作者说明正文与最终代码主要为人工撰写，Lean 形式化由 AI 生成但结论经其核验且可被 Lean 检查。相关帖曾被误判为非人工后已由版主恢复。</p>
<p>原文链接：https://github.com/orlp/polyxor</p>
<h2>Casita：面向源码与构建产物的内容寻址存储层</h2>
<p>Casita 是 Cachix 团队在用 Rust 重写 Nix、并按层拆分现代化时交出的第一层独立组件：内容寻址对象存储，覆盖共享存储、校验、同步与垃圾回收，以 Rust 库和 CLI 预发布，目标平台 Linux / macOS / Windows。背景是 agent 开发会更快堆出多版源码与 <code>target/</code> 产物，全留占盘、全丢又要重编。</p>
<p>存储模型类似广义的 Git 对象库：不可变 blob 与不可变对象记录；blob 用完整字节的 BLAKE3，并可附带 Bao outboard 做区间校验。目录/文件等记录在 blob 之上形成图；改文件会换新 ID 并向上传播，未改文件共享原 blob。应用通过 root 命名保留可达图；可重建数据（如 Cargo <code>target/</code>）可用 evictable root。导入支持文件系统、tar、NAR 与 Git；Casitar 可打包完整图；另有实验性 Gix ODB 适配、CasitaFS 只读挂载（Linux FUSE / macOS FSKit），以及本地与可选 SSH 同步。共享 S3 后端仍实验性。</p>
<p>团队还在 Cargo 上加了 <code>ArtifactStorage</code> 接口与 Casita backend（实验分支），用于 registry/Git 依赖与 workspace 构建产物的导入恢复，目前性能尚未追上文件系统后端。0.1 前仍需压测、拓宽基准与真实工作流验证。仓库：<code>cachix/casita</code>。</p>
<p>原文链接：https://casita.rs/blog/introducing-casita-a-content-addressed-store-for-source-code-and-build-artifacts/</p>
<h2>mbrotli 进入上游 lzbench：安全 Rust 实现的 Brotli 可对照官方基准</h2>
<p>mbrotli 是用安全 Rust 编写的 Brotli 编解码器。此前多数对比数据主要来自项目自有仓库；现在 mbrotli 0.5.2 已进入上游 <code>inikep/lzbench</code>（PR #333），可与 Google Brotli 等编解码器在同一 harness 下跑分。</p>
<p>作者在 WSL2、默认 lzbench 构建、window 22、单线程、Silesia 语料上对比捆绑的 Google Brotli 1.2.0：解压在各 quality 上 mbrotli 更快（约 1.09–1.16×）；q0–q1 压缩基本持平或略快；q2–q9 压缩目前约慢 1–9%；q10 / q11 压缩显示约 1.18× / 1.35× 更快。q0–q9 压缩体积一致；q10/q11 的微小体积差来自 lzbench 对 C Brotli 使用了 <code>-ffast-math</code>，会改变最高 quality 下的浮点代价计算，按常规方式构建 C Brotli 后输出可再次对齐。作者强调不必过度解读 q10/q11 对比，但进入上游 lzbench 后可以用公共基准继续看优势与短板。</p>
<p>原文链接：https://github.com/inikep/lzbench</p>
<h2>Taipei：与 Tower 集成的服务过载治理，附交互式说明</h2>
<p>Taipei 是与 Tokio Tower 集成的库，目标是让服务在压力下仍表现稳定，并减少手调。功能以 Tower layer 形式提供，可包在已有 <code>Service</code> 外；文档站点用 WASM 跑真实 crate，配合交互仿真讲解过载相关原则，即使不直接用该 crate 也可作参考。</p>
<p>示例路径包括：用 <code>InstrumentedTokioRuntime</code> 观测 CPU；<code>CpuBackpressureLayer</code> 在 CPU 高于阈值时托住请求；<code>QueueLayer</code> 做排队与超时，并把队列错误映射为 HTTP 503 等。文档从手写并发上限讲起，说明为何需要队列、背压与运行时观测，再对比“拍脑袋 limit”的局限。仓库：<code>nhawkes/taipei</code>；说明入口：https://taipei-book.pages.dev/intro 。作者注明文案自写，代码有 AI 辅助。</p>
<p>原文链接：https://taipei-book.pages.dev/intro</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-28 01:06:58</pubDate></item><item><title>写一个语音编程扩展插件</title><link>https://rustcc.cn/article?id=f6451308-3a2a-4f0a-bb5c-b0a4d6e97b4f</link><description><![CDATA[<p>这几天花时间更新了一个 package ,使用语音模式和 agent 交互 提高输入效率。</p>
<h2>怎么用</h2>
<ul>
<li><code>/voice</code>：说一段，停顿一下自动收尾，转写结果落进输入框。</li>
<li><code>/voice ptt</code>：按住空格说话，页脚有实时电平条，松手发送。</li>
<li><code>/voice auto</code>：免手操，它读完麦克风自己开，你打字就暂停。</li>
</ul>
<p>你敲键盘就取消，识别错了可以先改。回复会用 Edge TTS 读出来，朗读前把 Markdown 剥掉，不会念一堆星号和 URL。你一打字或按 PTT，正在播的立刻静音。</p>
<p>转写用的是 SenseVoice（sherpa-onnx 静态链进扩展），支持中英日韩粤，自动判语言，自带标点。模型约 240MB 放在 <code>~/.rpi/agent/models/sense-voice/</code>，首次使用自动下载。
朗读使用Microsoft TTS 默认音色 <code>zh-CN-XiaoxiaoNeural</code>，<code>/voice set zh-CN-YunxiNeural</code> 换男声。</p>
<h2>装起来</h2>
<p><strong>装 rpi</strong>（单二进制，挑一个）：</p>
<pre><code>curl -fsSL https://raw.githubusercontent.com/bigfish1913/pi-rust/main/scripts/install.sh | sh   # 预编译，Linux/macOS
cargo install rpi-cli                  # 从 crates.io，需要 Rust 1.78+
brew tap bigfish1913/tap &amp;&amp; brew install rpi     # macOS(ARM)/Linux
scoop install rpi                      # Windows，先 scoop bucket add bigfish1913 https://github.com/bigfish1913/scoop-bucket
</code></pre>
<p><strong>装 rpi-voice</strong>，想全离线就得自己编译（原生库塞不进默认包）：</p>
<p>开箱即用 <code>rpi install rpi-voice</code></p>
<p><strong>跑起来</strong>：<code>rpi</code>，然后 <code>/voice model download</code> 预取模型，<code>/voice ptt</code> 开按住说话，<code>/voice status</code> 看输入设备和当前音色。</p>
<p>跑长任务的时候用 <code>/voice auto</code> 追加约束，改完一段让它读一遍当听觉 review，写 diff 描述直接口述</p>
<p>仓库在 <a href="https://github.com/pi-rust/rpi-package" rel="noopener noreferrer">https://github.com/pi-rust/rpi-package</a>，扩展在 <code>packages/rpi-voice/</code>，命令和环境变量的完整清单在那边 README 里。</p>
<p>目前package 支持扩展 。欢迎大家PR和star https://github.com/bigfish1913</p>
]]></description><pubDate>2026-09-27 15:20:48</pubDate></item><item><title>【Rust日报】2026-09-27 Qt 官方推出面向 Rust 的 UI Bridge</title><link>https://rustcc.cn/article?id=877c7ed2-1d4b-4844-9471-ca714f25f9bc</link><description><![CDATA[<h2>Qt 官方推出面向 Rust 的 UI Bridge</h2>
<p>Qt 公司博客介绍 Qt Bridges 面向 Rust 的公开 beta：在保留 Rust 业务代码的前提下，接入 Qt Quick 的 UI 能力、硬件加速与跨平台支持（Linux / macOS / Windows）。目标是把汽车仪表、医疗与工业场景已验证的 Qt 成熟度提供给 Rust 开发者；相对 Iced、egui 等仍在生产落地中的框架，强调功能完整度与商业支持。</p>
<p>此前生态里有 qmetaobject-rs（QML + Rust，现偏维护状态）与 CXX-Qt（更适合已有 C++ 团队）。Qt Bridge 侧重“只写 Rust”：不直接碰 C++ 头文件与手工 FFI，UI 用 QML（可辅以 JavaScript），业务侧通过属性宏与 trait 的精简 API 交互。QML 对象在 Rust 侧以 <code>Rc&lt;RefCell&lt;T&gt;&gt;</code> 共享引用暴露，借用规则在运行时检查；编译期为 Rust 类型生成 Qt 包装，负责对象创建、方法调用、属性访问与 signal 等。用户代码本身无 C++，但当前版本构建仍需本机 C++ 工具链与 qmake；官方计划通过预编译产物等方式减轻该要求。用法上在 <code>Cargo.toml</code> 加 <code>qtbridge</code> 依赖，用 <code>#[qobject]</code> / <code>#[qslot]</code> 等标注后即可在 QML 中引用。示例仓库：<code>qt/qtbridge-rust-examples</code>。路线图是向 Technology Preview（TP）推进。</p>
<p>原文链接：https://www.qt.io/blog/rust-ui-framework-via-bridging-technology</p>
<h2>rust-mqtt 0.6.0：嵌入式向 MQTT 客户端补齐 MQTTv5</h2>
<p><code>rust-mqtt</code> 是面向 <code>no_std</code> 的异步 MQTT 客户端，I/O 基于 <code>embedded_io_async</code>；当前以 MQTT 5.0 为主。作者约一年前接手无人维护的项目并重写，半年前曾发帖说明进展；0.6.0 补齐此前缺失的 MQTTv5 能力，重点是完整的增强认证（enhanced authentication），并加入手动/延迟确认（manual acknowledgments）。</p>
<p>设计上尽量用类型系统贴近规范：会话状态、配置、QoS 投递与重试由用户掌控，不做自动重连、keepalive 循环或后台任务等意见化连接管理，提供可取消安全的协议原语，适合上层客户端与资源受限嵌入式。已覆盖 Will、QoS 0/1/2 双向发布、流控、会话恢复、主题别名、共享/通配订阅、消息过期、Request/Response、User Property 与增强认证等。已知限制包括单包多主题订阅尚未支持、有序主题保证未内建（单 packet id 并发时约束可放宽）。后续计划包括 MQTT 3.1.1、更灵活的内存与 I/O，以及在坚持 <code>no_std</code>/<code>no_alloc</code> 主目标的同时改善 std/桌面体验。</p>
<p>原文链接：https://github.com/obabec/rust-mqtt</p>
<h2>Moxy：面向过程宏的 Rust 语法工具栈</h2>
<p>Moxy 是一套面向过程宏的 Rust 语法工具，覆盖 token、类型化语法树、模板、格式化与诊断。作者此前发布过过程宏模板引擎 zyn，社区反馈希望少依赖 <code>syn</code> / <code>proc-macro2</code> / <code>quote</code>；Moxy 从零重写，非可选外部依赖目前主要是 <code>unicode-ident</code>，其余核心语法栈自研。</p>
<p>目标包括：依赖尽量少；性能上与 <code>syn</code> 对标（作者基准里 compile time 仍偏 <code>syn</code>，runtime 有时 Moxy 更好，目标并非全面超越）；用自维护 EBNF 与系统化 grammar fixtures（attributes、表达式、泛型、items、宏、paths、patterns、语句、类型、可见性与 lexer 等）持续校验解析覆盖；保留 zyn 风格的模板/<code>paste</code> 类准引用；并类似 <code>proc-macro2</code> 同时支持 stable 与 nightly，以便在 stable 上提供编译器诊断能力。功能按 feature 分层（<code>token</code>/<code>ast</code>/<code>template</code>/<code>fmt</code>/<code>diagnostic</code> 等）。项目仍早期、API 会变，作者欢迎语法覆盖与架构反馈。仓库：https://github.com/aacebo/moxy</p>
<p>原文链接：https://github.com/aacebo/moxy</p>
<h2>module-cycles：用 Dylint 检测兄弟模块循环依赖</h2>
<p><code>module-cycles</code> 是一个 Dylint lint，用于报告“兄弟模块”之间的循环依赖。Clippy 自 2020 年起有相关 issue（#5782）尚未落地，作者据此实现。兄弟模块指共享同一父模块的子树；若 <code>model</code> 与 <code>report</code> 彼此引用则告警。父子互用（把一个模块拆成多文件）不报。依赖来自 rustc 名字解析结果：路径、<code>use</code>/re-export、方法调用与关联函数都算；外部 crate 宏展开的引用会跳过，本 crate 宏写出的引用会计入并标在调用点。</p>
<p>每个环只报一次，并 note 最短环上的其余边；建议把共享项挪到其中一侧或双方共同依赖的模块。作者在自己的 workspace 扫到 11 个环（最长经 12 个模块），在 Clippy 测试用的 26 个 crate 上扫到 41 个（含 tokio）。通过 workspace metadata 挂 Dylint library 后执行 <code>cargo dylint</code>；可用 <code>DYLINT_RUSTFLAGS="-D module_cycles"</code> 把警告升为失败。</p>
<p>原文链接：https://github.com/HardMax71/module-cycles</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-27 01:04:39</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>