You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rust中遍历字符串的最优方式:chars()与as_bytes()对比

两种first_word实现的差异与优劣分析

作为刚学Rust的新手,你能注意到这两种实现的区别真的很棒!这两个版本看起来相似,但其实存在关键差异,咱们来仔细拆解:

先把两个实现贴出来方便对比:

《Rust Book》中的实现

// Returns the number of characters in the first
// word of the given string

fn first_word(s: &String) -> usize {
    let bytes = s.as_bytes();

    for (i, &item) in bytes.iter().enumerate() {
        if item == b' ' {
            return i;
        }
    }

    s.len()
}

你的简化实现

fn first_word(s: &String) -> usize {
    for (i, item) in s.chars().enumerate() {
        if item == ' ' {
            return i;
        }
    }
    s.len()
}

核心差异

1. 返回索引的语义完全不同

  • Book的实现返回的是字节索引:as_bytes()把字符串转成UTF-8字节切片,遍历的是每个字节的位置。返回的i是空格所在的字节位置,和String::len()(返回字符串总字节数)的语义完全一致。
  • 你的实现返回的是字符索引:chars()遍历的是字符串中的Unicode标量值(每个“字符”),enumerate()的索引是字符的顺序位置,和字节数没有直接对应关系——比如像é、中文这类多字节Unicode字符,一个字符会占用2-4个字节,这时候字符索引和字节索引就会错位。

2. 安全性天差地别

这是最关键的一点:Rust的字符串切片(比如&s[0..i])要求必须落在UTF-8字符的边界上,否则会直接触发运行时panic。

  • Book的实现:因为它判断的是ASCII空格(b' ',单字节),所以返回的字节索引一定是合法的字符边界(ASCII字符都是单字节,空格的位置就是一个完整字符的结尾),用这个索引切片绝对安全。
  • 你的实现:如果字符串里有任何多字节字符,返回的字符索引就不能直接用来切片。举个例子:
    fn main() {
        let s = String::from("café world");
        // 你的实现返回4(字符索引:c、a、f、é、空格,空格是第4个字符)
        // 尝试用这个索引切片:&s[0..4]
        // 这会直接panic!因为é是2个字节,前4字节只包含é的第一个字节,不是合法的UTF-8边界
    }
    

哪种实现更优?

毫无疑问,《Rust Book》里的实现更优,原因有两个:

  1. 安全性更高:返回的字节索引可以直接用于字符串切片,不会触发panic,完全符合Rust的内存安全原则。
  2. 语义一致:返回值和String::len()的语义统一,都是基于字节的计数,符合Rust字符串UTF-8编码的设计逻辑。

另外补充一个小技巧:如果想写更简洁且安全的first_word,可以直接用标准库的str::find方法,它会返回空格的字节索引(或者None),然后直接返回切片:

fn first_word(s: &str) -> &str {
    match s.find(' ') {
        Some(end) => &s[0..end],
        None => s,
    }
}

这个版本不仅简洁,还直接返回第一个单词的切片,避免了手动处理索引的麻烦——不过Book里用循环实现,主要是为了演示迭代器、字节遍历这些基础概念,所以用了更底层的写法。

内容的提问来源于stack exchange,提问作者rustafari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 10:40:39