土法炼钢兴趣小组的算法知识备份

我用 AI 写了一个月的 Rust,以下是所有编译不过的瞬间

目录

规则很简单:每天用 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 move

AI 在 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 &itemsfor 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: PartialOrd

AI 从 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 只是快速打草稿的工具。


延伸阅读:


By .