在Rust字符串中使用索引比较字符

5
我想要从"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] == "#",而我无法检查字符串中的第二个字符。

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

1个回答

14

Rust字符串以UTF-8编码的字节序列形式存储。由于UTF-8是一种可变宽度编码,因此字节索引可能会导致位于字符内部,这显然是不安全的。但通过索引获取代码点是O(n)操作。此外,索引代码点并不是您真正想要做的事情,因为有一些代码点甚至没有与之相关联的字符,比如变音符号或其他修饰符。索引字形集群更接近于正确的方法,但通常需要用于文本呈现或语言处理。

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

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

首先,您始终可以获得基础字节序列的视图。 &str 有一个as_bytes()方法,该方法返回&[u8],字符串由此组成。然后,您可以使用常规的索引操作:

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

请注意特殊符号的表示方法:b'#' 表示“类型为 u8 的 ASCII 字符 #”,即它是一个字节字符字面量(也请注意,您不需要编写 "#".chars().next() 来获取字符 #,您可以直接编写 '#' - 一个普通的字符字面量)。然而,这样做是不安全的,因为 &str 是 UTF-8 编码的字符串,第一个字符可能由多个字节组成。

在 Rust 中处理 ASCII 数据的正确方式是使用 ascii crate。您可以通过 as_ascii_str() 方法将 &str 转换为 &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());
}

2
x[].as_bytes()[0] != b'#' 在任何有意义的情况下都不是不安全的。它不会威胁内存安全,也不涉及无效的char值,不会对类型进行奇怪的操作,甚至不太可能做出无意义的事情。在UTF-8中,多字节码点仅由字节> 127(即非ASCII)组成,因此搜索具有值为35的字节是查找U + 0023代码点出现的完全合适的方法。但是可以承认:这是一种糟糕的风格,并且转换为字节是其他文本处理任务的一种不良习惯。 - user395760
1
@delnan,好的,在这种特殊情况下你是正确的。不安全(在Rust通常上下文中使用的意义)可能是错误的词。然而,对于除0以外的任何索引编写相同的内容都是毫无意义和不正确的,而且问题作者明确要求检查第二个字符和可能的其他字符。 - Vladimir Matveev
2
使用索引1尝试并检查第二个代码点或图形簇是不正确的。但是,字节索引并不一定是不正确的。UTF-8的属性允许将子字符串搜索等操作写成字节的形式。通常这样做是没有意义的(char迭代器通常更好,并且许多算法已经由libstd提供了),但是让我们不要误解信息的本意。 - user395760

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接