规则很简单:每天用 AI 生成至少一段非平凡的 Rust
代码(不是 hello world,是真正的系统代码),然后
cargo check。编译通过就算赢,编译失败就记录错误类型。
30 天,87 次编译失败。以下是按失败模式分类的完整记录。
失败模式分布
| 模式 | 次数 | 占比 | 典型错误 |
|---|---|---|---|
| 生命周期错误 | 28 | 32% | missing lifetime specifier |
| 所有权 move 后使用 | 19 | 22% | value used after move |
| 借用冲突 | 15 | 17% | cannot borrow as mutable, already borrowed as immutable |
| trait bound 不满足 | 11 | 13% | the trait X is not implemented for Y |
| 类型不匹配 | 8 | 9% | expected &str, found String |
| async/Pin/Future 相关 | 6 | 7% | future is not Send |
一、生命周期错误(32%)
AI 生成的 Rust 代码中最常见的编译错误。根本原因:AI 不理解引用的生命周期必须被显式或隐式地满足。
高频场景:返回内部引用
// AI 生成(编译失败)
fn first_word(s: &str) -> &str {
let words: Vec<&str> = s.split_whitespace().collect();
words[0] // error: 返回了局部变量 words 里的引用
}这段代码的 words 是局部 Vec,函数返回后被
drop。words[0] 指向 words
内部的切片引用,会悬垂。
正确版本不需要 collect:
fn first_word(s: &str) -> &str {
s.split_whitespace().next().unwrap_or("")
}AI 之所以犯这个错误,是因为 Python/Java/Go 的习惯——先收集到列表再取元素。在有 GC 的语言里这没问题,在 Rust 里这是生命周期错误。
另一个高频场景:结构体存引用
// AI 生成(编译失败)
struct Parser {
input: &str, // missing lifetime specifier
}AI 经常忘记给结构体里的引用标注生命周期。正确写法:
struct Parser<'a> {
input: &'a str,
}在 30 天的实验中,AI 在 75% 的情况下忘记了结构体的生命周期标注。当我在 prompt 里显式提醒”注意 Rust 的生命周期”时,正确率提高到 60%——但仍有 40% 遗漏。
二、所有权 move 后使用(22%)
Rust 的 move 语义是 AI 最不适应的概念。
// AI 生成(编译失败)
let data = vec![1, 2, 3];
send_to_thread(data); // data 被 move 进线程
println!("{:?}", data); // error: value used after moveAI 在 C++/Java 的训练数据里形成了”变量赋值后仍然可用”的惯性。Rust 的 move 语义打破了这个假设。
更隐蔽的变体:
// AI 生成(编译失败)
fn process(items: Vec<String>) {
for item in items { // items 被 move 进 for 循环
println!("{}", item);
}
println!("total: {}", items.len()); // error: items 已经被 move
}for item in items 会 move
items。AI 不理解 for
循环会消费所有权。正确写法是
for item in &items 或
for item in items.iter()。
三、借用冲突(17%)
// AI 生成(编译失败)
let mut map = HashMap::new();
map.insert("key", vec![1, 2, 3]);
let values = map.get("key").unwrap(); // 不可变借用
map.insert("key2", vec![4, 5, 6]); // error: 可变借用冲突
println!("{:?}", values);AI 不理解 map.get() 返回的引用持有
map
的不可变借用,在这个引用存活期间不能再可变借用
map。
这个模式在用 Rust 重写 C 网络服务器那篇文章里详细分析过——echo server 里处理 io_uring CQE 时的同一个问题。AI 在生成类似的 ring buffer 操作代码时,100% 会犯这个错误。
四、trait bound 不满足(13%)
// AI 生成(编译失败)
fn spawn_task<T: Debug>(value: T) {
tokio::spawn(async move {
println!("{:?}", value);
});
// error: T: Send is required by tokio::spawn
}tokio::spawn 要求 Future 是
Send 的,因此 Future capture 的所有值也必须是
Send。AI 经常忘记添加 Send
bound。
更微妙的:
// AI 生成(编译失败)
fn largest<T>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in &list[1..] {
if item > largest { // error: binary operation `>` cannot be applied to type `&T`
largest = item;
}
}
largest
}
// 需要 T: PartialOrdAI 从 Python 的习惯里直接用 >
比较泛型,忘了 Rust 的泛型不自带比较运算符。
五、async 相关错误(7%)
// AI 生成(编译失败)
async fn fetch_data(url: &str) -> String {
let client = reqwest::Client::new();
let resp = client.get(url).send().await?;
// error: cannot use the `?` operator in an async function
// that returns `String` (需要返回 Result)
resp.text().await?
}AI 在 async 代码里经常混淆返回类型——用了 ?
但返回类型不是 Result。
更有趣的错误:
// AI 生成(编译失败)
let mutex = std::sync::Mutex::new(vec![]);
tokio::spawn(async move {
let mut guard = mutex.lock().unwrap();
some_async_fn().await; // error: MutexGuard 不是 Send
guard.push(1);
});std::sync::MutexGuard 不能跨
.await 持有,因为它不是 Send。AI
不理解 async 代码在 .await
点可能被调度到另一个线程。正确做法是用
tokio::sync::Mutex 或在 await 之前 drop
guard。
六、教训
| 教训 | 数据 |
|---|---|
| AI 不理解 Rust 的所有权模型 | 71% 的失败和 lifetime/move/borrow 相关 |
| 在 prompt 里提醒有限帮助 | “注意生命周期”只提高 15% 正确率 |
| 简单函数正确率远高于复杂函数 | 无泛型无引用的函数正确率 90%+,有的 50% |
| async 是 AI 的另一个盲区 | 7% 的失败,但每次都很难修 |
结论:AI 可以帮你写 Rust,但它写的是”Java 风格的 Rust”——正确的逻辑,错误的所有权模型。你需要做的不是让 AI 重试,而是理解编译器在说什么,然后自己修。
编译器是你的真正助手。AI 只是快速打草稿的工具。
延伸阅读:
- Rust 所有权:C++ RAII 本来想成为的样子 – AI 不理解的那五个约束
- unsafe Rust:当编译器不再替你扛枪 – 如果 AI 写 unsafe 代码,后果更严重
- 让 LLM 帮你写系统代码:哪些能信,哪些会死 – 更广泛的 AI 系统编程评测