为什么在Rust中将字符串的第一个字母大写如此复杂?

时间:2016-07-16 01:07:22

标签: string rust uppercase

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

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

&str s不能像这样编入索引。我能够做到这一点的唯一方法似乎过于复杂。我将&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会这样设计?

Similar question

6 个答案:

答案 0 :(得分:69)

为什么这么复杂?

让我们逐行分解

let s1 = "foobar";

我们创建了一个用UTF-8编码的文字字符串。 UTF-8允许我们以一种相当紧凑的方式对1,114,112 code points Unicode进行编码,如果你来自世界上一个主要输入ASCII中的字符的区域,这是一个标准创建于1963年.UTF-8是可变长度编码,这意味着单个代码点可能take from 1 to 4 bytes。较短的编码是为ASCII保留的,但many Kanji take 3 bytes in UTF-8

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

这会创建一个char个向量的向量。字符是直接映射到代码点的32位数字。如果我们从仅使用ASCII的文本开始,我们的内存需求将翻两番。如果我们有来自the astral plane的一堆字符,那么我们可能还没有那么多使用过。

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

这会抓取第一个代码点并请求将其转换为大写变体。对于我们这些从小就说英语的人来说,不幸的是not always a simple one-to-one mapping of a "small letter" to a "big letter"。旁注:我们将其称为大写和小写because one box of letters was above the other box of letters back in the day

当代码点没有相应的大写变体时,此代码将会出现混乱。实际上,我不确定这些是否存在。当代码点具有包含多个字符的大写变体(例如德语ß)时,它也可能在语义上失败。请注意,ß可能永远不会在真实世界中被大写,这是我可以永远记住并搜索的唯一例子。事实上,截至2017年6月29日,德语拼写的官方规则已经更新,以便both "ẞ" and "SS" are valid capitalizations

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

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

let s3 = &s2;

现在我们引用String

  

这是一个简单的问题

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

  

我认为char::to_uppercase已正确处理Unicode。

是的,我当然希望如此。不幸的是,在所有情况下,Unicode都不够。  感谢huon for pointing out Turkish I,其中上部( )和小写( i )版本都有一个点。也就是说,字母i没有一个正确大写;它还取决于源文本的locale

  

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

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

  

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

这里可能存在一些不匹配的术语。 char 多字节Unicode字符。

如果你逐字节地切换字符串是可能的,但如果你不在字符边界上,标准库就会发生混乱。

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

  

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上搜索uppercaseunicode,让比我聪明的人处理它。

改进

说到“比我聪明的人”,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(),
    }
}

答案 1 :(得分:15)

  

有没有比这更简单的方法,如果有,那又怎样?如果没有,为什么Rust会这样设计?

嗯,是的,不是。正如另一个答案指出的那样,你的代码是不正确的,如果你给它类似བོད་སྐད་will的话会引起恐慌。因此,使用Rust的标准库进行此操作比您最初的想法更难。

但是,Rust旨在鼓励代码重用,并使库容易。因此,将字符串大写的惯用方法实际上非常适合:

extern crate inflector;
use inflector::Inflector;

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

答案 2 :(得分:0)

我同意这个问题的家伙。所以,我用自己的方式做到了:

fn capitalize(word: &str) -> String {
    let mut output = String::with_capacity(word.len());
    let (first, last) = word.split_at(1);
    let first_letter = format!("{}", first.to_uppercase());
    output.push_str(first_letter.as_str());
    output.push_str(last);
    output
}

fn main() {
    let input = "end";
    let ret = capitalize(input);
    println!("{} -> {}", input, ret);
}

答案 3 :(得分:0)

这就是我解决此问题的方法,请注意,在转换为大写字母之前,我必须检查self是否为ascii。

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());
}

输出

Bruno
B
?
ß

བོད་སྐད་ལ 

答案 4 :(得分:-1)

这里的版本比@Shepmaster的改进版本要慢一些,但也有很多惯用

public class myInterceptor : IDbConnectionInterceptor
{
        public void Opened(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
        {
        //do my SP here
        }
}

答案 5 :(得分:-1)

我是这样做的:

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>())
}