为什么把字符串的第一个字母大写在 Rust 中如此令人费解?

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

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

But &strs can't be indexed like this. The only way I've been able to do it seems overly convoluted. I convert the &str to an iterator, convert the iterator to a vector, upper case the first item in the vector, which creates an iterator, which I index into, creating an Option, which I unwrap to give me the upper-cased first letter. Then I convert the vector into an iterator, which I convert into a String, which I convert to a &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 被设计成这样?

Similar question

27171 次浏览

为什么这么复杂?

我们一行一行地分析一下

let s1 = "foobar";

我们已经创建了一个用 UTF-8编码的字符串。UTF-8允许我们以一种非常紧凑的方式编码 Unicode的1,114,112个 密码点,如果你来自世界上的一个地区,键入的大多是 ASCII中的字符,ASCII是1963年创建的一个标准。UTF-8是 可变长度编码,这意味着单个编码点可能是 从1到4个字节。较短的编码保留给 ASCII,但是 许多汉字在 UTF-8中占用3个字节

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

这将创建 char字符的向量。字符是直接映射到代码点的32位数字。如果我们从只使用 ASCII 文本开始,我们的内存需求已经增加了四倍。如果我们有一些字符从 星界,那么也许我们没有使用更多。

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

This grabs the first code point and requests that it be converted to an uppercase variant. Unfortunately for those of us who grew up speaking English, there's not always a simple one-to-one mapping of a "small letter" to a "big letter". Side note: we call them upper- and lower-case because one box of letters was above the other box of letters back in the day.

当一个代码点没有相应的大写变量时,这个代码将会恐慌。事实上,我不确定是否存在这种情况。当一个代码点具有包含多个字符的大写变体(如德语 ß)时,它也可能在语义上失败。请注意,在真实世界中,可能从来没有真正大写的 ß,这只是一个例子,我可以一直记住并搜索它。截至2017年6月29日,实际上,官方的德语拼写规则已经更新,使 “ something”和“ SS”都是有效的大写

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

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

let s3 = &s2;

现在我们引用 String

问题很简单

Unfortunately, this is not true. Perhaps we should endeavor to convert the world to 世界语?

我假设 char::to_uppercase已经正确地处理了 Unicode。

Yes, I certainly hope so. Unfortunately, Unicode isn't enough in all cases. 由于 谢谢你指出来土耳其语 I,其中都大写(İ)和小写()版本有一个点。也就是说,字母 i没有适当的 one大写; 它也取决于源文本的 地点

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

Because the data types you are working with are important when you are worried about correctness and performance. A char is 32-bits and a string is UTF-8 encoded. They are different things.

indexing could return a multi-byte, Unicode character

这里可能有一些不匹配的术语。 a char a multi-byte Unicode字符。

如果逐字节切片 字符串是可能的,但是如果不在字符边界上,标准库就会恐慌。

One of the reasons that indexing a string to get a character was never implemented is because so many people misuse strings as arrays of ASCII characters. Indexing a string to 准备好了 a character could never be efficient - you'd have to be able to replace 1-4 bytes with a value that is also 1-4 bytes, causing the rest of the string to bounce around quite a lot.

to_uppercase可以返回大写字母

如上所述,ß是一个单独的字符,当大写时,就变成了 两个角色

解决方案

参见 trentcl's answer,它只有大写的 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 points out认为在访问第一个大写代码点之后将迭代器转换回一个片可能更有效。这允许其余字节的 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(),
}
}

还有比这更简单的方法吗? 如果有,那又是什么? 如果没有,为什么 Rust 被设计成这样?

是也不是。正如另一个回答所指出的那样,您的代码是不正确的,如果您给它类似于 something something something something something something something 的东西,它会感到恐慌。因此,使用 Rust 的标准库做这件事比您最初想象的还要困难。

但是,Rust 的设计目的是鼓励代码重用,并使引入库变得容易。因此,将字符串大写的惯用方法实际上是相当可取的:

extern crate inflector;
use inflector::Inflector;


let capitalized = "some string".to_title_case();

如果您能够将输入限制为仅使用 ASCII 的字符串,那么就不会特别复杂了。

自 Rust 1.23以来,str有一个 make_ascii_uppercase方法(在 Rust 的旧版本中,它可以通过 AsciiExt trait 获得)。这意味着您可以相对容易地将仅用于 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"

小心使用。

这个版本比@Shepmaster 的改进版稍慢一些,但也更像 成语:

fn capitalize_first(s: &str) -> String {
let mut chars = s.chars();
chars
.next()
.map(|first_letter| first_letter.to_uppercase())
.into_iter()
.flatten()
.chain(chars)
.collect()
}

我是这样做的:

fn str_cap(s: &str) -> String {
format!("{}{}", (&s[..1].to_string()).to_uppercase(), &s[1..])
}

如果不是 ASCII 字符串:

fn str_cap(s: &str) -> String {
format!("{}{}", s.chars().next().unwrap().to_uppercase(),
s.chars().skip(1).collect::<String>())
}

这就是我如何解决这个问题,注意,我必须检查自己是否在转换为大写之前。

trait TitleCase {
fn title(&self) -> String;
}


impl TitleCase for &str {
fn title(&self) -> String {
if !self.is_ascii() || self.is_empty() {
return String::from(*self);
}
let (head, tail) = self.split_at(1);
head.to_uppercase() + tail
}
}


pub fn main() {
println!("{}", "bruno".title());
println!("{}", "b".title());
println!("{}", "🦀".title());
println!("{}", "ß".title());
println!("{}", "".title());
println!("{}", "བོད་སྐད་ལ".title());
}


Output

Bruno
B
🦀
ß


བོད་སྐད་ལ

业务处的做法更进一步:
将第一个字符替换为其大写表示形式

let mut s = "foobar".to_string();
let r = s.remove(0).to_uppercase().to_string() + &s;

或者

let r = format!("{}{s}", s.remove(0).to_uppercase());
println!("{r}");

也可以使用 Unicode 字符,例如 "😎foobar"

Inspired by 举例说明 I code something like this:

fn make_capital(in_str : &str) -> String {
let mut v = String::from(in_str);
v.get_mut(0..1).map(|s| { s.make_ascii_uppercase(); &*s });


v
}

因为方法 to_uppercase()返回一个新字符串,所以您应该能够像这样添加字符串的其余部分。

this was tested in rust version 1.57+ but is likely to work in any version that supports slice.

fn uppercase_first_letter(s: &str) -> String {
s[0..1].to_uppercase() + &s[1..]
}