原文:https://dev.to/lovestaco/one-rust-unicode-bug-three-coding-models-a-reproducible-comparison-48ae(作者 @lovestaco)
编码模型排行榜很少能说明一个模型在真实 bug 上的表现。一个 Rust 函数可能在 ASCII 上正常工作,却因为把 UTF-8 切在错误的字节边界上而在合法 Unicode 输入上 panic。修复必须尊重字符边界,并保留 &str 返回类型。本文用一个可复现的 bug、一段提示词和同一套回归测试,对比三个模型的补丁质量,而不是比较谁的解释更有信心。
一行 Rust 切片代码就可能在合法 UTF-8 上 panic,这让它成为一个很好的编码模型测试题。修复必须保留函数签名、正确处理字符边界,并通过回归测试。本次对比对三个模型使用同一个 bug,并以编译器输出而非排行榜分数来评判它们的补丁。
这个 bug
下面这个函数本应返回字符串的前 n 个字符。它在纯 ASCII 输入上正常工作,但当 n 恰好切到一个多字节 UTF-8 字符中间时就会 panic。
pub fn prefix(input: &str, n: usize) -> &str {
if n >= input.len() {
return input;
}
&input[..n]
}
#[cfg(test)]
mod tests {
use super::prefix;
#[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), "é");
}
}
原始实现统计的是字节,而不是字符。在 UTF-8 中,café 末尾的 é 占两个字节。在字节索引 4 处切片会切穿这个字符,导致 panic。把代码保存为新建 Cargo library 项目中的 src/lib.rs,运行 cargo test 即可复现这个失败的 Unicode 用例。
三个模型,同一个任务
三个候选模型分别是 DeepSeek V4 Pro、Claude Fable 5.1 和 Grok Build 0.1。这些是本次对比的目标模型页面。执行前需要对照实时目录核对模型可用性、确切的 API 标识符和支持的参数。
三个模型的测试条件统一如下:
- DeepSeek V4 Pro:API 标识符以实时目录为准;temperature 设为 0;输入相同的 Rust 文件与提示词;记录 API 版本号。
- Claude Fable 5.1:API 标识符以实时目录为准;temperature 设为 0;输入相同的 Rust 文件与提示词;记录 API 版本号。
- Grok Build 0.1:API 标识符以实时目录为准;temperature 设为 0;输入相同的 Rust 文件与提示词;记录 API 版本号。
每个模型都使用相同的请求体、系统指令、最大输出额度和测试环境。记录 API 返回的确切模型 ID。temperature 设为 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 标量值与字素簇(grapheme cluster)并不相同。对一个 e 加组合重音符号的输入请求取一个标量值,可能只返回那个 e。在本任务的定义下这是正确结果。
Grok Build 0.1
不改动提示词,重复同样的流程。检查模型是否更换了 API、引入了外部依赖,或者返回 String 而不是 &str。即使表面输出看起来正确,这些改动也很重要。把第一次尝试和后续每一次尝试分别保留为独立记录。
正确的修复长什么样
一种可行的写法是用 char_indices 找到指定数量标量值之后的字节边界。
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 以及分解形式的重音符号。参考代码不应出现在任何模型的提示词中。
对比记分卡
- DeepSeek V4 Pro
- 首次尝试通过情况:从 cargo test 填入
- 尝试次数:从日志填入
- 耗时:从日志填入
- 输入 token:从 API 填入
- 输出 token:从 API 填入
- 备注:编译器与行为说明
- Claude Fable 5.1
- 首次尝试通过情况:从 cargo test 填入
- 尝试次数:从日志填入
- 耗时:从日志填入
- 输入 token:从 API 填入
- 输出 token:从 API 填入
- 备注:编译器与行为说明
- Grok Build 0.1
- 首次尝试通过情况:从 cargo test 填入
- 尝试次数:从日志填入
- 耗时:从日志填入
- 输入 token:从 API 填入
- 输出 token:从 API 填入
- 备注:编译器与行为说明
只有当每一行都用首次响应、Cargo 输出和 API 日志填满之后,这张记分卡才真正有用。在那之前,它只是一份可重复执行的评测工作表,而不是模型排名。
不同编码任务该怎么选
写短补丁时,首次编译通过和边界情况正确,比一份漂亮的解释更重要。做大型重构时,要看模型有多频繁地改动无关代码。做代码审查时,测试它能否在不被提示的情况下识别出字节与字符的区别。做终端自动化时,比较每个模型遵循指定输出格式的稳定程度。
最适合编码的 LLM 取决于任务。一个 Rust bug 无法证明哪个模型在整个代码库、整门语言或整条工具链上表现最好,它能揭示的是:在受控条件下,某个模型能否处理一种具体的失败模式。
如何复现这次对比
用 cargo new unicode-prefix --lib 创建一个 Cargo 库,把 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 日志。读者既能检查建议的修复方案,也能查看失败记录。
原文:https://dev.to/lovestaco/one-rust-unicode-bug-three-coding-models-a-reproducible-comparison-48ae(作者 @lovestaco)



