【问题标题】:Comparing a character in a Rust string using indexing使用索引比较 Rust 字符串中的字符
【发布时间】:2014-12-08 09:09:53
【问题描述】:

我想从“input.txt”中读取字符串,并且只在行首留下没有#(注释)符号的字符串。我写了这段代码:

use std::io::{BufRead, BufReader};
use std::fs::File;

fn main() {
    let file = BufReader::new(File::open("input.txt").unwrap());
    let lines: Vec<String> = file.lines().map(|x| x.unwrap()).collect();
    let mut iter = lines.iter().filter(|&x| x.chars().next() != "#".chars().next());
    println!("{}", iter.next().unwrap());
}

但是这一行

|&x| x.chars().next() != "#".chars().next()

对我来说闻起来很糟糕,因为它可能看起来像 |x| x[0] == "#",而且我无法检查字符串中的第二个字符。

那么我该如何重构这段代码呢?

【问题讨论】:

    标签: string iterator rust


    【解决方案1】:

    Rust 字符串存储为表示 UTF-8 编码字符的字节序列。 UTF-8 是一种可变宽度编码,因此字节索引可能会将您留在一个字符内,这显然是不安全的。但是通过索引获取代码点是一个 O(n) 操作。此外,索引代码点并不是您真正想要做的,因为有些代码点甚至没有关联的字符,例如变音符号或其他修饰符。索引字素簇更接近正确的方法,但通常在文本渲染或语言处理中需要。

    我的意思是索引字符串很难正确定义,而且大多数人通常想要的都是错误的。因此 Rust 不提供对字符串的通用索引操作。

    但是,有时您确实需要为字符串编制索引。例如,如果您事先知道您的字符串仅包含 ASCII 字符,或者您正在处理二进制数据。当然,在这种情况下,Rust 提供了所有必要的手段。

    首先,您始终可以获得底层字节序列的视图。 &amp;str 具有 as_bytes() 方法,该方法返回 &amp;[u8],字符串包含的字节切片。然后就可以使用通常的索引操作了:

    x.as_bytes()[0] != b'#'
    

    注意特殊符号:b'#' 表示“u8 类型的 ASCII 字符 #”,即它是字节字符文字(另请注意,您不需要写 "#".chars().next() 来获取字符 @ 987654330@,你可以只写'#' - 一个纯字符文字)。但是,这是不安全的,因为&amp;str 是 UTF-8 编码的字符串,并且第一个字符可以包含多个字节。

    在 Rust 中处理 ASCII 数据的正确方法是使用 ascii crate。您可以使用as_ascii_str() 方法从&amp;str 转到&amp;AsciiStr。然后你可以这样使用它:

    extern crate ascii;
    use ascii::{AsAsciiStr, AsciiChar};
    
    // ...
    
    x.as_ascii_str().unwrap()[0] != AsciiChar::Hash
    

    这样您将需要更多的输入,但您会获得更多的安全性作为回报,因为as_ascii_str() 会检查您是否只使用 ASCII 数据。

    然而,有时您只想处理二进制数据,而不是将其真正解释为字符,即使源包含一些 ASCII 字符。例如,当您为某些标记语言(如 Markdown)编写解析器时,可能会发生这种情况。在这种情况下,您可以将整个输入视为一个字节序列:

    use std::io::{Read, BufReader};
    use std::fs::File;
    
    fn main() {
        let mut file = BufReader::new(File::open("/etc/hosts").unwrap());
        let mut buf = Vec::new();
        file.read_to_end(&mut buf).unwrap();
        let mut iter = buf.split(|&c| c == b'\n').filter(|line| line[0] != b'#');
        println!("{:?}", iter.next().unwrap());
    }
    

    【讨论】:

    • x[].as_bytes()[0] != b'#' 在任何意义上都不是不安全。它不会威胁内存安全,它不涉及无效的char 值,它不会对类型做一些时髦的事情,它甚至不太可能做一些无意义的事情。在 UTF-8 中,多字节代码点仅由大于 127 的字节组成(即不是 ASCII),因此搜索值为 35 的字节是查找 U+0023 代码点的完美方法。但是当然:这是不好的风格,对于其他文本处理任务来说,下降到字节是一个坏习惯。
    • @delnan,好的,在这种特殊情况下你是对的。不安全(考虑到通常在 Rust 上下文中使用的含义)可能是错误的词。但是,为除 0 之外的任何索引编写相同的内容是没有意义且不正确的,并且问题作者明确要求检查第二个字符以及可能的其他字符。
    • 使用索引 1 来尝试检查第二个代码点或字素簇是不正确的。但是字节索引不一定是不正确的。 UTF-8 的属性允许以字节为单位编写诸如子字符串搜索之类的内容。这通常是没有意义的,是的(char 迭代器通常更好,并且 libstd 已经提供了许多算法),但我们不要发布消息。
    猜你喜欢
    • 1970-01-01
    • 2015-03-18
    • 2023-04-02
    • 2022-11-15
    • 2019-12-22
    • 1970-01-01
    • 1970-01-01
    • 2020-02-04
    • 1970-01-01
    相关资源
    最近更新 更多