Rust AI CLI 开发复盘:两周踩过的 7 个坑和对应的防范模式

TrapDoor供应链攻击:AI助手配置投毒与多生态恶意包分析 软件供应链安全是保障现代软件开发流程的基础,其核心在于确保从开源依赖到构建工具的每一个环节都可信。攻击者常利用包管理器的自动化机制,如npm的postinstall钩子、Python的import动态加载和Rust的build.rs编译脚本,注入恶意代码实现持久化驻留。这类攻击的技术价值在于突破了传统代码审计的边界,将威胁载体扩展到开发环境配置和AI助手行为指令。在AI编程助手深度集成的应用场景下,攻击者通过污染.cursorrules等配置文件,并利用零宽字符隐藏恶意指令,使AI成为数据窃取的代理。Tra 阅读详情

Rust AI CLI 开发复盘:两周踩过的 7 个坑和对应的防范模式

一、这个项目的由来:我为什么用 Rust 写 AI CLI

这个 AI CLI 项目最开始的需求很简单:在终端里直接调用 OpenAI 兼容的 API,读取本地文件作为上下文,然后把 AI 的回答直接输出到终端或者写回文件。听起来不复杂,对吧?Python 写大概半天就能跑。但我偏要用 Rust 来写,一方面想练手,另一方面也是被 Rust 的单文件编译能力吸引——编译完就是一个二进制文件,拷到哪都能跑。

结果呢?两周时间踩了 7 个比较典型的坑。这篇文章就当一次完整复盘,分享踩坑经历和对应的预防方法。

flowchart LR
    A["需求分析\n想用Rust写AI CLI"] --> B["原型开发\n快速搭出demo"]
    B --> C["踩坑阶段\n网络错误/内存暴涨\n/序列化bug/异步混乱"]
    C --> D["重构\n引入错误处理模式\n/流式解析/资源管理"]
    D --> E["可发布版本\n单文件二进制\n/支持多平台"]

二、踩坑实录:7 个坑的完整还原

坑 1:HTTP 客户端无超时,请求卡死 3 分钟

我最早用的是 reqwest 的默认配置,直接 reqwest::get(url)。有次网络波动,API 服务响应很慢,终端直接卡住没有任何反馈,我一度以为是死循环。

原因:reqwest 默认不设超时,TCP 连接可以挂很久。

教训:任何网络请求必须显式设置连接超时和读取超时。

use reqwest::Client;
use std::time::Duration;

fn build_client() -> Result<Client, reqwest::Error> {
    // 构建带超时配置的 HTTP 客户端
    Client::builder()
        // TCP 连接最多等 10 秒
        .connect_timeout(Duration::from_secs(10))
        // 整个请求最多等 60 秒(AI API 响应可能比较慢)
        .timeout(Duration::from_secs(60))
        // 连接池空闲超时
        .pool_idle_timeout(Duration::from_secs(90))
        .build()
}

坑 2:流式响应解析用了错误的解析方式

AI API 的流式响应是 SSE(Server-Sent Events)格式,一行一个 data: {...}。我最开始想当然地用 serde_json::from_str 直接解析整行,结果发现有些行前面有空格、有些行是 data: [DONE](不是合法 JSON),还有空行。

正确做法是逐行读取、跳过空行和 [DONE] 标记、再解析 JSON:

use futures::StreamExt;
use serde_json::Value;

async fn parse_sse_stream(
    mut stream: impl StreamExt<Item = reqwest::Result<bytes::Bytes>> + Unpin,
) {
    while let Some(chunk) = stream.next().await {
        let chunk = match chunk {
            Ok(c) => c,
            Err(e) => {
                eprintln!("读取流数据失败: {}", e);
                break;
            }
        };
        let text = String::from_utf8_lossy(&chunk);
        for line in text.lines() {
            let line = line.trim(); // 去除前后空格
            if line.is_empty() {
                continue; // 跳过空行
            }
            // SSE 格式:以 "data: " 开头
            let data = match line.strip_prefix("data: ") {
                Some(d) => d,
                None => continue,
            };
            // 流结束标记,不是 JSON
            if data == "[DONE]" {
                println!("\n[AI 响应完成]");
                return;
            }
            // 解析 JSON 并取出文本内容
            if let Ok(json) = serde_json::from_str::<Value>(data) {
                if let Some(content) = json["choices"][0]["delta"]["content"].as_str() {
                    print!("{}", content);
                }
            }
        }
    }
}

坑 3:文件读取时忘记处理非 UTF-8 编码

在读取用户指定的本地文件作为 AI 上下文时,我用 std::fs::read_to_string,结果用户给了一个 GBK 编码的 Windows 批处理文件,程序直接 panic。

修正方案:使用 encoding_rs crate 做编码检测和转换。

use encoding_rs::*;
use std::fs;

fn read_file_auto_encoding(path: &str) -> Result<String, Box<dyn std::error::Error>> {
    // 先按字节读取,不做 UTF-8 假设
    let bytes = fs::read(path)?;

    // 尝试 UTF-8(绝大多数情况)
    if let Ok(s) = String::from_utf8(bytes.clone()) {
        return Ok(s);
    }

    // UTF-8 失败,用 encoding_rs 检测编码
    // GBK 是中文 Windows 环境最常遇到的非 UTF-8 编码
    let (cow, encoding, _had_errors) = GBK.decode(&bytes);
    if !encoding.is_utf8() {
        eprintln!("注意:文件编码为 {},已自动转换", encoding.name());
    }
    Ok(cow.into_owned())
}

坑 4:命令行参数解析不支持中文路径

用 clap 处理命令行参数时,我在 Windows 上测试发现中文路径传入后被截断。查了半天发现是终端编码设置问题,但最终决定用 clap 的 OsString 参数类型,绕过编码转换。

坑 5:异步运行时混用导致 panic

我在 main 里用了 tokio::main,但在某个函数里又 block_on 了一个嵌套 runtime,结果 panic 说 "Cannot start a runtime from within a runtime"。这是新手常见的异步混淆——在已有 tokio 上下文中又新建 runtime。

修正:统一使用 async/await,不要混用 block_on。

坑 6:无节制的字符串拼接导致内存暴涨

流式输出时,我把每次 AI 返回的 delta 内容追加到一个 String 上。处理一个 10 万字的文档时,内存从几十 MB 飙到 500MB+。因为 String 的 push_str 在容量不足时会重新分配并复制全部数据。

对策:用 Vec<String> 收集片段,最后 join("") 或预先 reserve 容量。

坑 7:API Key 硬编码在代码里差点提交到 GitHub

这个太不好意思说了——我居然在 main.rs 写了 let api_key = "sk-xxxxxxxx";。还好提交前用 cargo check 的时候无意中看了一眼 diff,惊出一身冷汗。

正确做法:环境变量 + .env 文件 + .gitignore:

use std::env;

fn load_api_key() -> Result<String, String> {
    // 优先读环境变量,不存在时读 .env 文件
    env::var("OPENAI_API_KEY")
        .or_else(|_| {
            // 加载 .env 文件(开发环境用)
            dotenvy::dotenv().ok();
            env::var("OPENAI_API_KEY")
        })
        .map_err(|_| "未找到 OPENAI_API_KEY,请设置环境变量或 .env 文件".into())
}

三、防范模式总结:从踩坑中提炼的三条原则

flowchart TD
    A["外部输入"] --> B{"编码假设检查"}
    B -->|已知编码| C["按预期处理"]
    B -->|未知编码| D["自动检测 + 降级"]
    E["网络调用"] --> F{"超时设置"}
    F -->|已设置| G["正常请求"]
    F -->|未设置| H["阻断:必须设置超时"]
    I["资源使用"] --> J{"有上界吗?"}
    J -->|有| K["安全"]
    J -->|无| L["加限制:pre-allocate / buffer-cap"]
  1. 所有外部输入均做编码检测:不假设文件、网络响应、命令行参数是 UTF-8。
  2. 所有 I/O 操作均设超时:HTTP 请求、文件读取、子进程调用都要有 timeout。
  3. 资源使用必须有上界:缓冲区大小、并发数、内存使用量都要设上限。

四、对自学者的建议:工具该有的样子

这个项目让我学到最重要的不是某个技术点,而是一种意识转变——从"能跑就行"到"能在各种极端情况下稳健运行"。自学容易陷入"写出来就完了"的心态,但真正的工程是要考虑边界情况的。

我的建议是:把你写的工具发给朋友用。他一定会在你意想不到的场景下触发你的 bug。这是最快的成长方式。

mindmap
  root((AI CLI 开发心得))
    编码
      不假设 UTF-8
      用 encoding_rs
      测试 GBK、Shift-JIS
    网络
      超时必须设
      SSE 逐行解析
      重试加退避
    资源
      预留容量
      限制缓冲区
      监控内存
    安全
      密钥用环境变量
      .env 入 .gitignore
      不打印敏感信息

五、总结

这两周踩的坑看似很多,但每一个其实都是 Rust 乃至系统编程的"基础课"——超时、编码、内存管理、异步模型、密钥安全,这些东西在任何语言里都是必学的,Rust 只是让问题暴露得更早(也因此更痛苦)。

如果你也在用 Rust 写工具,希望这篇文章能帮你少走我走过的弯路。特别是自学的朋友,别怕踩坑,每踩一个坑都是一次真正理解"为什么"的机会。下次分享我打算整理 Rust 生命周期标注的心法,期待和大家交流。


欢迎在评论区分享你写 CLI 工具时踩过的坑,一起进步。

<数据集>咖啡叶片病害识别<目标检测> YOLO与VOC格式的咖啡叶片病害识别数据集,适用于YOLO系列、Faster Rcnn、SSD等模型训练,类别:Cercospora、Miner、Phoma、Rust,图片数量1744。文件中包含图片、txt标签、指定类别信息的yaml文件、xml标签,已将图片和txt标签划分为训练集、验证集和测试集,可直接用于YOLOv5、YOLOv6、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLOv11、YOLOv12、YOLOv13、YOLOv26等YOLO系列算法的训练。 阅读详情

相关推荐

B站李沐讲论文笔记AlexNet

B站李沐讲论文笔记AlexNet

夏知识不进脑子薇的博客 789

老赵点滴:国内深度.NET技术博客的认知建模实践

技术博客本质是知识传播载体,其核心价值在于构建可迁移的技术认知模型。从基础概念出发,.NET开发者需理解运行时机制(如JIT编译、GC代际)、API设计约束(如Minimal APIs的HTTP头优化)与工程权衡逻辑,而非仅掌握语法用法。这种基于生产问题反推原理的‘问题源流图’方法,赋予技术内容真实场景锚点和可验证性,显著提升学习效率与系统设计能力。结合‘代码即文档’三阶呈现、非线性知识架构与监控驱动闭环,该实践为.NET开发者提供了超越教程的深度成长路径——尤其适合追求可维护性、性能可控性及长期技术判断力

世范水晶 455

RAG在医药行业为什么80%都翻车了?

这篇文章不是要劝你别做RAG,而是把坑说清楚。医药行业有它的特殊性——监管压力、专业术语密度、多语言混排、文档结构复杂——这些因素叠加在一起,让标准RAG教程里的那套方法论几乎直接失效

qq_31808385的博客 635

组网第63课:安全域划分——Trust/Untrust/DMZ、区域模型、与 VLAN 的关系

一组接口 + 一组策略 + 一条默认拒绝。一组接口:这个域包含哪些物理口、VLAN、子接口;一组策略:这个域到别的域放行什么、拒绝什么;一条默认拒绝:没写进策略的,一律不通。第三条最关键。没有默认拒绝,前面划的域全是装饰。

qq_36765170的博客 510

UE4 - 河流流体插件Fluid Flux

Fluid Flux可以模拟一定区域内的流体效果,由于使用顶视图所Capture的Heightfield信息,所以只支持室外开阔场景,不支持洞穴等室内场景;

GT的虚幻引擎文档 6667

YOLOv8实战:用Labelme标注自定义数据集并训练分割模型(附完整代码)

本文详细介绍了使用Labelme标注自定义数据集并训练YOLOv8实例分割模型的完整流程。从环境搭建、数据标注与格式转换,到模型训练、调优、验证及部署,提供了实战代码和调优建议。文章还探讨了如何结合追踪目标技术,实现视频流中的实例分割与目标追踪,为计算机视觉应用提供了一套可复制的解决方案。

552

GitHub 热榜项目:日榜(2026-11-06)

给编码智能体补工程与产品技能是本期最明显的一类,mattpocock/skills 把需求对齐、TDD、缺陷诊断做成小技能。NVIDIA/auto-ontology 从企业元数据生成本体、构建知识图谱,为 LLM 提供带来源追溯的自然语言查询和 SQL 生成,新增 2,679 星;vercel-labs/agent-desktop 通过桌面无障碍树把界面元素转成 JSON 和可操作 ref,让智能体不必截图就能点击输入,新增 2,173 星。

u130130的博客 457

Latex | IEEE双栏排版中长公式的排版

IEEE双栏排版中,有时候公式过于长了,过宽,导致单栏排版不下。

qq_43466146的博客 1万+

GitHub 热榜项目:日榜(2026-11-07)

给编码智能体配技能的项目最热:cloudflare/security-audit-skill 把审计拆成六阶段并输出可验证 JSON,新增 2,548 星;mattpocock/skills 把工程技能放进 .agents 目录,新增 2,070 星。Web 应用框架也有四个项目集中上榜,以 react/react 为代表。

u130130的博客 387

C#量化交易的市场数据采集与分析:从0到1构建你的‘数据雷达’——对比5大神器,代码实战全解析!

数据是量化交易的‘氧气’,而好的数据采集与分析是你的‘呼吸机’!本文5大神器+代码实战,总有一款适合你!(本文代码均经过测试,但请根据实际需求调整!

java专栏 730

光电二极管探测器电流信号处理与指令输出系统

本文提出了一种基于Python的光电二极管电流信号处理系统设计方案。该系统能够实时采集、处理光电二极管输出的电流信号,并根据预设阈值输出控制指令。系统采用模块化架构,包含数据采集、信号处理、阈值检测、指令生成等核心模块。其中数据采集模块提供了模拟器实现和硬件接口框架,支持噪声模拟和脉冲信号生成。系统设计考虑了实时性、可靠性和可扩展性,适用于光强测量、光电检测等多种应用场景。

huanghm88的专栏 1180

GitHub 热榜项目:日榜(2026-11-10)

本期共 11 个热门开源项目,合计新增 13,136 stars。榜单前三分别是 Anthropic 的 Agent Skills 仓库、Meta 开源的 React 设计系统 Astryx(+3,278)和用户名检索工具 sherlock(+4,267,本期涨星最多)。其余项目覆盖 DeepMind 的物理引擎 MuJoCo、Claude 官方示例集、自托管智能体记忆系统 GBrain、用 Rust 写的浏览器自动化 CLI、基于 WiFi 信号的人体感知平台 RuView、多市场股票分析工具,以及 Ho

u130130的博客 370

快速卷积结构FIR数字滤波器的设计与ARM平台实现

本文提出了一种基于快速卷积结构的FIR数字滤波器设计与实现方案。通过Matlab设计了满足特定指标的低通FIR滤波器,采用重叠-保存法和重叠-相加法两种快速卷积算法,将计算复杂度从O(N·M)降低到O(NlogN)。详细介绍了滤波器系数生成、性能验证流程,并提供了ARM平台C语言实现的关键技术,包括定点量化处理。实验结果表明,该方法能有效滤除混合信号中的高频干扰,保留目标1kHz正弦信号,验证了设计方案的有效性。

huanghm88的专栏 123

【Rust 手搓 Win11 灵动岛】① 从零搭骨架:无边框置顶窗口 + 工作区精确停靠

项目“岛仔”(daozai)是基于 Rust 与 eframe/egui 构建的 Win11 灵动岛桌面应用,采用窗口恒定展开、胶囊自绘方案实现无抖动丝滑动画。本篇聚焦 M1 里程碑:通过 Win32 获取工作区避让任务栏,解决创建期 DPI 缩放误传问题——虽首帧补偿逻辑因误解 ViewportCommand 单位而引入双重缩放错误,但实测假阳性掩盖了缺陷;后续将修复为直接下发 logical 尺寸。代码重构提升可测性,三组单元测试覆盖多 DPI 场景。最终验证结果经更正后确认需在下篇修正。

ygn12的博客 348

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

OpenSearch 的索引快照别只留本地:用 repository-s3 存进 RustFS

OpenSearch 跑起来,索引分片默认写在节点自己的本地盘上。单机测试很顺,可一旦索引量上来、节点不止一个,本地盘的故障就成了绕不开的坎:一块盘坏了,上面的分片副本丢了,重建全靠其他副本兜着,集群压力瞬间翻倍。更隐蔽的是快照,很多人把快照也留在本地盘的默认仓库里,等于把备份和原始数据放在了同一块会坏的盘上。OpenSearch 自带 repository-s3 插件,能把索引快照直接写进任意…

分布式存储与RustFS的博客 562

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

Rolldown 把插件改动回传提速 26 倍

Rolldown 下一个版本会把插件改动的回传做细,只把真的改了的字段送回 Rust,大型项目里这一步从 1015 毫秒降到 39 毫秒。什么时候能用上呢,改动已经在 11 月 6 日合并,会跟着下一个版本走,Vite 8 用的就是 Rolldown。更麻烦的是缓存,Rust 替换 chunk 数据之后,之前那份代码缓存就废了,下一个读。这次没有动钩子对象本身,只是把「回传」这件事做细了,先看哪些字段变了,再决定传什么。所以改法很直接,只送变了的字段,没变的 chunk 一个字节都不发。

前端之虎陈随易 193

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

Rust如何实现微服务网关的高并发与限流熔断机制

在现代互联网系统中,微服务架构已成为主流,而微服务网关则是整个系统的重要组成部分。当面对高并发、高可用性需求时,很多开发者对如何用 Rust 实现一个稳定可靠的网关存在疑惑,尤其是处理请求的幂等性、限流和熔断机制。本文将以 Rust 的实际案例为基础,结合生产环境中的具体经验,讲解如何设计和实现一个具备限流与熔断能力的微服务网关。

wuchangjian的专栏 47

我写了一个 Windows 开发环境管理平台:Nexus(Tauri 2 + Rust + React)

文件监听事件很密集。如果节流窗口内的变更直接丢弃,最后一次改动就永远加载不到——所以窗口结束时必须补一轮。只对「失败」重试,不对「启动中」重试。启动慢不代表启动失败,盲目重试只会把健康的服务越搞越糟。Nexus 原本是为了解决自己日常开发里「窗口太多、状态不清、日志找不到」的问题,后来慢慢补成了现在这个样子:项目与服务管理、日志、服务健康、文件与代码查看、文件监听、开机启动、Node 版本管理,以及一个内嵌的 AI 助手。如果你也在 Windows 上同时维护多个服务,欢迎试用。

hi星尘的博客 565

Rust 如何构建高可靠支付回调系统?从《Rust 系统编

在现代支付系统中,支付回调处理和对账逻辑是整个系统的稳定性关键。当一笔支付完成时,第三方支付平台会通过异步回调通知业务系统交易结果。然而,在高并发、高吞吐的场景下,如何保证这些回调消息的可靠消费与数据一致性,成为了一个挑战。作为一位中级开发者,你可能已经经历过类似场景:在处理支付回调时,因网络抖动、服务宕机、重复通知等问题导致的数据不一致或对账失败。这时候,“系统编程”不再是理论层面的概念,而是实际落地时的救命稻草。

wuchangjian的专栏 211

GitHub 热榜项目:周榜(2026-11-08)

本期收录19个热门开源项目,总新增⭐102,473,聚焦Python、TypeScript、Rust。Top项目涵盖AI智能体记忆(claude-mem)、Git工作树管理(worktrunk)、游戏开发协作框架(Claude-Code-Game-Studios)及紫微斗数排盘引擎(ziwei-doushu)。T3code实现多智能体控制界面,Anthropic开源技能库助力Agent开发,Hallmark则专注消除AI设计“机器味”。技术趋势凸显AI工程化与跨会话智能体能力的快速演进。

u130130的博客 714

Airflow 的中间文件别散落各节点:让 DAG 直接读写 RustFS

数据流水线的中间结果往哪放,是个容易被忽略、出事才知道疼的问题。直接写本地盘,任务被调度到别的节点就找不到文件;挂共享 NFS,又多一层要维护的高可用。Airflow 自带的 Amazon S3 provider 其实就是个标准 S3 客户端,只要把连接的 endpoint_url 从 AWS 指到 RustFS,DAG 里的读写就全落到对象存储上了。这篇顺着官方集成指南,用 Apache Ai…

分布式存储与RustFS的博客 470

Worktrunk:用 Rust 写的 CLI,给并行跑的 AI agent 各开一份 Git worktree

Worktrunk 是一个 Rust 写的 Git worktree 管理 CLI,补上了原生 worktree 缺失的创建、命名、切换、清理生命周期,目标是让并行跑的多个 AI agent 各占一份互不干扰的工作副本;截至 2026 年 11 月 3 日仓库 10313 stars、81 位贡献者,但本文仅有单一来源,安装方式与 CLI 命令未获证实。

老孙正经胡说 165

Rust重写开源版Photoshop来了,支持PSD编辑,还能让AI Agent直接操作图像

PhotoCraft 是一个基于 Rust 开发的开源专业图像编辑器,旨在以原生高性能架构重构 Photoshop 级工作流。它提供图层、蒙版、调整图层、矢量绘图、文字排版、智能滤镜以及高兼容性的 PSD 支持,同时通过 CLI、JSON 控制接口和 MCP Server 具备面向 AI Agent 的自动化能力。

Tony.Dong的专栏 248

用Rust重写IntelliJ IDEA:169MB替代2GB,0.9秒打开项目的开源IDE

IntelliJ IDEA虽强大却臃肿,启动慢、占内存。开源项目OpenIDEA用Rust重写,保留熟悉快捷键与工作流,体积仅169MB,0.9秒启动。基于LSP支持60+语言,结合Tauri实现轻量跨平台界面。虽暂不替代IntelliJ的Java深度与插件生态,但为多语言开发者提供高效轻量选择,展现Rust+Web技术构建桌面应用的新范式。

weixin_41007138的博客 194

target 目录越来越大,Rust 项目该怎么清理

Rust 项目一多,磁盘空间常常先被target目录吃掉。每个工作区的target里,既有最后要运行的程序,也有依赖编译结果、增量编译数据和构建脚本产生的中间文件。它们都在增长,但生命周期并不一样。整目录删除当然最快,代价是最终产物也会一起消失。只想清掉缓存,又得挨个项目判断哪些目录能删。这个两难,是我写的原因。cargoc 把 Cargo 的中间构建文件放到专用目录,按工作区记录和清理空闲缓存;最终产物仍留在 Cargo 原来的target-dir。它解决的是“哪些中间文件可以清、什么时候清”。

fuwei2020的博客 354

我用 RustyPaste 做 AI Agent 云端存储

RustyPaste 是一个用 Rust 编写的极简文件上传与 Pastebin 服务。可以用来存储和访问 AI Agent 生成的文件

藏云阁技术社区 222
上一篇: WASM 在边缘计算中的应用:轻量沙箱 + 快速启动的组合优势
下一篇: 学习路径可视化:用 AI 给自己生成 Rust 技能树和薄弱点热力图
第一程序员
博客等级 码龄1年 1万+粉丝 1812原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值