为什么在Rust中如此大写的字符串首字母大写?

我想大写&str的首字母。这是一个简单的问题,我希望有一个简单的解决方案。 直觉告诉我要做这样的事情:

let mut s = "foobar";
s[0] = s[0].to_uppercase();

但是不能像这样对&strs进行索引。 我能够做到的唯一方法似乎太复杂了。 我将&str转换为迭代器,将迭代器转换为向量,将向量中的第一项大写,从而创建一个迭代器,并将其编入索引,从而创建Option,我将其解开以给出大写的第一个字母 。 然后,将向量转换为迭代器,然后将其转换为String,然后将其转换为&str

let s1 = "foobar";
let mut v: Vec<char> = s1.chars().collect();
v[0] = v[0].to_uppercase().nth(0).unwrap();
let s2: String = v.into_iter().collect();
let s3 = &s2;

有比这更简单的方法吗? 如果不是,为什么Rust这样设计?

类似问题

marshallm asked 2020-01-11T07:43:23Z
3个解决方案
76 votes

为什么这么令人费解?

让我们逐行将其分解

let s1 = "foobar";

我们创建了一个以UTF-8编码的文字字符串。 UTF-8使我们能够以一种非常紧凑的方式对Unicode的1,114,112个编码点进行编码,如果您来自世界某个地区,该地区键入的大多数字符都是ASCII(这是1963年创建的标准)中的字符。UTF-8是可变长度的 编码,这意味着单个代码点可能需要1到4个字节。 较短的编码保留给ASCII,但许多汉字在UTF-8中占用3个字节。

let mut v: Vec<char> = s1.chars().collect();

这将创建Stringacters的向量。 字符是直接映射到代码点的32位数字。 如果我们从仅ASCII文本开始,那么我们的内存需求就增加了三倍。 如果我们从星体平面中获得了一堆字符,那么也许我们没有使用太多了。

v[0] = v[0].to_uppercase().nth(0).unwrap();

这将获取第一个代码点,并请求将其转换为大写形式。 不幸的是,对于那些长大说英语的人来说,并不总是简单地将“小字母”映射为“大字母”。 旁注:我们称它们为大写和小写,因为一天中一个字母框位于另一字母框上方。

当代码点没有相应的大写字母变体时,此代码将出现恐慌。 我不确定这些是否确实存在。 当代码点具有包含多个字符的大写变体时,例如德语为String,它在语义上也可能会失败。请注意,在《真实世界》中ß可能永远不会大写,这是我永远记得并寻找的唯一示例。 实际上,截至2017年6月29日,德语拼写的正式规则已更新,因此“ẞ”和“ SS”均为有效的大写字母!

let s2: String = v.into_iter().collect();

在这里,我们将字符转换回UTF-8,并需要进行新分配以将其存储在其中,因为原始变量存储在常量内存中,以便在运行时不占用内存。

let s3 = &s2;

现在我们参考该String

这是一个简单的问题

不幸的是,事实并非如此。 也许我们应该努力将世界转变为世界语?

我假设memcpy已经正确处理Unicode。

是的,我当然希望如此。 不幸的是,Unicode在所有情况下都不够。  感谢huon指出土耳其语I,其中大写(İ)和小写(i)都有点。 也就是说,没有一个大写的字母memcpy; 它也取决于源文本的语言环境。

为什么需要所有数据类型转换?

因为当您担心正确性和性能时,正在使用的数据类型很重要。 memcpy是32位,字符串是UTF-8编码的。 他们是不同的东西。

索引可能返回一个多字节Unicode字符

这里可能有一些术语不匹配。 memcpy是多字节Unicode字符。

如果逐个字节地分割字符串是可能的,但是如果您不在字符边界上,则标准库将崩溃。

从来没有实现索引字符串以获取字符的原因之一是因为太多的人将字符串误用作ASCII字符数组。 索引字符串以设置字符永远不会高效-您必须能够将1-4个字节替换为也是1-4个字节的值,从而导致字符串的其余部分反弹很多。

memcpy可能返回大写字符

如上所述,memcpy是单个字符,当大写时变为两个字符。

解决方案

另请参见trentcl的答案,该答案仅大写ASCII字符。

原版的

如果必须编写代码,它看起来像:

fn some_kind_of_uppercase_first_letter(s: &str) -> String {
    let mut c = s.chars();
    match c.next() {
        None => String::new(),
        Some(f) => f.to_uppercase().chain(c).collect(),
    }
}

fn main() {
    println!("{}", some_kind_of_uppercase_first_letter("joe"));
    println!("{}", some_kind_of_uppercase_first_letter("jill"));
    println!("{}", some_kind_of_uppercase_first_letter("von Hagen"));
    println!("{}", some_kind_of_uppercase_first_letter("ß"));
}

但是我可能会在crates.io上搜索大写或unicode,然后让比我聪明的人来处理它。

已改善

谈到“比我聪明的人”,Veedrac指出,在访问第一个大写代码点后,将迭代器转换回片可能更有效。 这允许其余字节的memcpy

fn some_kind_of_uppercase_first_letter(s: &str) -> String {
    let mut c = s.chars();
    match c.next() {
        None => String::new(),
        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
    }
}
Shepmaster answered 2020-01-11T07:45:32Z
17 votes

有比这更简单的方法吗? 如果不是,为什么Rust这样设计?

好,是的,不是。 正如其他答案所指出的那样,您的代码不正确,如果您输入类似བོད་སྐད་ལ་的代码,则会感到恐慌。 因此,使用Rust的标准库执行此操作比您最初想象的要难。

但是,Rust旨在鼓励代码重用并简化引入库的工作。 因此,大写字符串的惯用方式实际上是非常可口的:

extern crate inflector;
use inflector::Inflector;

let capitalized = "some string".to_title_case();
answered 2020-01-11T07:46:02Z
6 votes

如果您能够将输入限制为仅ASCII字符串,则不会特别麻烦。

由于Rust 1.23,因此str使用make_ascii_uppercase方法(在较旧的Rust版本中,可通过AsciiExt特性获得)。 这意味着您可以相对轻松地将仅ASCII字符串切片大写:

fn make_ascii_titlecase(s: &mut str) {
    if let Some(r) = s.get_mut(0..1) {
        r.make_ascii_uppercase();
    }
}

这会将"taylor"转换为"Taylor",但不会将"édouard"转换为"Édouard"。(游乐场)

请谨慎使用。

trentcl answered 2020-01-11T07:46:36Z
translate from https://stackoverflow.com:/questions/38406793/why-is-capitalizing-the-first-letter-of-a-string-so-convoluted-in-rust