[控场AI]
· 9 分钟阅读· 4,789 字

一个Rust Unicode Bug,三家编码模型实测对比

一个Rust Unicode Bug,三家编码模型实测对比

用同一个 Rust Unicode 切片 Bug 和固定提示词,对比三家编码模型的补丁质量与可复现评估方法。

文章以一个按字节切片的 Rust 函数为测试题:它在处理 `café` 等合法 UTF-8 输入时会于字符中间切断而 panic。作者用完全相同的提示词、温度 0 和回归测试,比较 DeepSeek V4 Pro、Claude Fable 5.1 与 Grok Build 0.1 的首次响应。评估不看解释的自信程度,而看 `cargo test`、编译输出、token 用量、耗时和模型版本,并要求 API 标识符对照在线目录核实。正确修复需用 `char_indices` 找到第 n 个标量值后的字节边界并保留 `&str` 返回类型。文章强调单个 Bug 不能给模型排名,最好的编码 LLM 取决于具体任务。

一个Rust Unicode Bug,三家编码模型实测对比

Rust Unicode Bug 编码模型对比

编码模型的排行榜很难说明它处理真实 Bug 的能力。一个在 ASCII 上运行良好的 Rust 函数,可能因为切错了 UTF-8 字节边界,而在合法的 Unicode 输入上直接 panic。本文用一个可复现的 Bug、一条固定提示词和同一套回归测试,比较 DeepSeek广告 V4 Pro、Claude Fable 5.1 与 Grok Build 0.1 三个模型给出的补丁质量——看代码,而不是看解释的自信程度。

这个 Bug 为什么值得拿来测模型

被测函数的目标是返回字符串的前 n 个字符:

pub fn prefix(input: &str, n: usize) -> &str {
    if n >= input.len() {
        return input;
    }
    &input[..n]
}

问题在于它按字节计数,而不是按字符计数。UTF-8 中 café 末尾的 é 占两个字节,当 n=4 时,切片恰好从该字符中间切开,触发 panic。作为对照的测试用例如下:

#[test]
fn ascii() { assert_eq!(prefix("rustacean", 4), "rust"); }

#[test]
fn unicode() { assert_eq!(prefix("café", 4), "café"); }

#[test]
fn beyond_length() { assert_eq!(prefix("é", 10), "é"); }

将代码保存为新建 Cargo library 项目的 src/lib.rs,执行 cargo test 即可复现 Unicode 用例的失败。这个 Bug 的好处是边界清晰:修复必须保留 &str 返回类型、尊重字符边界、并通过回归测试,任何走捷径的改动都会在编译器或测试里暴露出来。

UTF-8 是一种变长编码:ASCII 字符占 1 字节,拉丁扩展字符(如 é)占 2 字节,CJK 字符通常占 3 字节,emoji 多占 4 字节。Rust 的字符串切片 &input[..n] 按字节索引,若 n 落在某个多字节字符的中间,就会切出一个非法的 UTF-8 序列。Rust 在运行时检测到这一点会直接 panic,而不是返回乱码,这正是该 Bug 如此显眼的原因。理解这一点需要区分三个层次:字节(byte)、Unicode 标量值(scalar value,即一个 char)和字素簇(grapheme cluster,用户感知的一个字符)。例如 é 可以是单个标量值 U+00E9,也可以由 e 加组合重音 U+0301 两个标量值组成,但在字素簇层面它们都显示为一个字符。本题要求按标量值计数,因此用 char_indices 而非按字素簇切分也是正确的。

三家模型,同一道题

本次比较的三个候选是 DeepSeek V4 Pro、Claude Fable 5.1 和 Grok Build 0.1。原文特别强调,模型可用性、确切的 API 标识符和受支持的参数,在真正执行前必须对照在线目录核实,不能凭记忆填写。

模型API 标识符温度输入版本
DeepSeek V4 Pro从目录确认0同一 Rust 文件与提示词记录 API 版本
Claude Fable 5.1从目录确认0同一 Rust 文件与提示词记录 API 版本
Grok Build 0.1从目录确认0同一 Rust 文件与提示词记录 API 版本

所有模型使用相同的请求体、系统指令、最大输出额度和测试环境,并记录 API 实际返回的模型 ID。温度设为 0 能降低随机性,但原文也提醒:它并不保证重复调用得到完全一致的答案。这一点对“可复现对比”的严谨性很关键。

提示词:一字不改

每个模型收到的提示词完全相同:

Fix the Rust function below. It must return the first n Unicode scalar values without panicking. Keep the return type &str. Explain the bug briefly, return a complete src/lib.rs, and add a regression test for an emoji and a combining character. Do not add external dependencies.

提示词之后附上完整的原始 src/lib.rs。规则同样明确:不要把某个模型的失败告诉另一个模型,也不要悄悄修好某个回答再去测试。这保证了比较的公平性——模型拿到的是同一道题,而不是被“喂”过答案的题。

每个模型的补丁该看什么

DeepSeek V4 Pro

需要捕获原始响应、报告的输入输出 token 数、请求耗时,以及第一次 cargo test 的输出。正确的补丁应当遍历 char 边界,而不是在任意字节索引处切片。如果返回的代码无法编译,保留错误信息,并把下一次提示算作另一次尝试。

Claude Fable 5.1

使用完全相同的源码和提示词。重点记录它是否保留了借用的 &str 返回类型,是否处理了请求长度超过字符数的情况。组合字符用例要特别留意:Unicode 标量值不等于字素簇,对一个 e 加组合重音请求一个标量值,可能只返回 e——按本题定义这是正确的。

Unicode 标量值(Unicode scalar value)指除代理对范围 U+D800–U+DFFF 之外的所有码点,Rust 的 char 类型恰好对应一个标量值。字素簇(grapheme cluster)则是人类感知的最小书写单位,可能由多个标量值组合而成,例如 e 加组合尖音符、家庭 emoji 由多个码点通过零宽连接符拼接。题目明确要求返回前 n 个标量值,因此像 e 加组合重音的输入,请求 1 个标量值时返回 e 是符合题意的;若模型返回整个字素簇反而偏离了要求。这一区分是判断模型是否真正理解题意、而非笼统地“处理 Unicode”的关键。

Grok Build 0.1

重复相同流程,不修改提示词。审查模型是否改动了 API、引入了依赖,或返回 String 而非 &str。即使可见输出看起来正确,这些改动同样重要。首次尝试和后续尝试要分开记录。

在评估补丁时,cargo test 只验证行为,cargo fmt --check 和 cargo clippy 则分别检查格式一致性与常见缺陷。clippy 是 Rust 官方提供的静态分析工具,能捕获如不必要的分配、可疑的类型转换、低效的迭代等模式,对编码模型而言,它常能暴露“能跑但不够地道”的写法。而 cargo fmt --check 不修改文件,只报告格式差异,适合在自动化流程中作为门禁。将三者组合使用,可以区分“编译通过且测试通过”“风格不达标”“存在 lint 警告”等不同质量层次,避免仅凭一次 cargo test 绿色就高估模型输出。

正确的修复长什么样

一种有效做法是用 char_indices 找到第 n 个标量值之后的字节边界:

pub fn prefix(input: &str, n: usize) -> &str {
    let end = input
        .char_indices()
        .nth(n)
        .map(|(index, _)| index)
        .unwrap_or(input.len());
    &input[..end]
}

需要说明的是,这段参考实现是判断正确性的“标准答案”,并非任何模型的输出,而且它必须被排除在模型提示词之外。测试用例应覆盖 ASCII、多字节字符、空字符串、n 等于 0、n 大于字符数、emoji 以及分解重音等情形。

记分卡:先填表,再谈排名

模型首次通过尝试次数耗时输入 token输出 token备注
DeepSeek V4 Pro来自 cargo test来自日志来自日志来自 API来自 API编译与行为备注
Claude Fable 5.1来自 cargo test来自日志来自日志来自 API来自 API编译与行为备注
Grok Build 0.1来自 cargo test来自日志来自日志来自 API来自 API编译与行为备注

原文对此说得很直白:只有当每一行都由首次响应、Cargo 输出和 API 日志填充后,记分卡才有意义;在那之前,它只是一张可重复执行的评估工作表,而不是模型排名。

不同编码任务该怎么选模型

短补丁场景下,首次编译通过和边界用例正确比解释是否漂亮更重要。大型重构时,应观察模型改动无关代码的频率。代码审查场景,可以测试它能否在无提示的情况下识别“字节 vs 字符”的区别。终端自动化场景,则比较各模型对所需输出格式的遵循程度是否稳定。

原文的结论是:最好的编码 LLM 取决于任务。一个 Rust Bug 无法断定哪个模型在整仓库、整语言或整工具链上最强,但它能揭示在受控条件下,模型是否处理了某个具体的失败模式。

如何复现这次比较

用 cargo new unicode-prefix --lib 创建 Cargo library,把 src/lib.rs 替换为有 Bug 的版本并运行 cargo test。将相同的提示词和文件发送给每个已确认的模型标识符,保存原始 JSON 响应、请求时间戳、token 用量字段和模型版本。每次尝试都把候选代码提取到干净的工作树中,运行 cargo fmt --check、cargo test 和 cargo clippy,并记录失败而不修改首次回答。

仓库建议按如下结构组织:

baseline/src/lib.rs
oracle/src/lib.rs
prompts/task.txt
runs/<model>/response.json
runs/<model>/cargo-test.txt

可复现的产物是代码和日志,而不是一张排名图。一份可分享的对比应当包含仓库、未经编辑的首次响应和 Cargo 日志,让读者既能检查候选修复,也能看到失败。

#Rust Unicode Bug#编码模型对比#DeepSeek V4 Pro#Claude Fable 5.1#Grok Build 0.1#AI编程评测#UTF-8 字符边界#cargo test 回归测试
分享:

相关推荐