通过索引修改String中的字符

use*_*708 5 rust

我写了一个函数来titlecase(首字母大写,所有其他小写)一个借来的字符串,但它最终变得比它应该的更麻烦.

fn titlecase_word(word: &mut String) {

    unsafe {
        let buffer = word.as_mut_vec().as_mut_slice();
        buffer[0] = std::char::to_uppercase(buffer[0] as char) as u8;

        for i in range(1, buffer.len()) {
            buffer[i] = std::char::to_lowercase(buffer[i] as char) as u8;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

不安全的块是特别不希望的.有没有更好的方法来修改索引的字符串内容?

Vla*_*eev 17

更新:更新了最新的Rust.从Rust 1.0.0-alpha开始,to_lowercase()/ to_uppercase()现在是CharExttrait中的方法,并且Ascii不再有单独的类型:ASCII操作现在收集在两个特征中,AsciiExtOwnedAsciiExt.它们被标记为不稳定,因此它们可能会在整个Rust beta期间发生变化.


您的代码不正确,因为它访问单个字节以执行基于字符的操作,但UTF-8字符不是字节.对于非ASCII的任何内容,它都无法正常工作.

实际上,没有办法正确地就地执行此操作,因为任何字符转换都可能会更改字符占用的字节数,这将需要完整的字符串重新分配.您应该迭代字符并将它们收集到一个新字符串:

fn titlecase_word(word: &mut String) {
    if word.is_empty() { return; }

    let mut result = String::with_capacity(word.len());

    {
        let mut chars = word.chars();
        result.push(chars.next().unwrap().to_uppercase());

        for c in chars {
            result.push(c.to_lowercase());
        }
    }

    *word = result;
}
Run Code Online (Sandbox Code Playgroud)

(在这里试试)

因为无论如何你需要生成一个新的字符串,最好只返回它,而不是替换旧字符串.在这种情况下,最好将切片传递给函数:

fn titlecase_word(word: &str) -> String {
    let mut result = String::with_capacity(word.len());

    if !word.is_empty() {
        let mut chars = word.chars();
        result.push(chars.next().unwrap().to_uppercase());

        for c in chars {
            result.push(c.to_lowercase());
        }
    }

    result
}
Run Code Online (Sandbox Code Playgroud)

(在这里试试)

Stringextend()方法从Extend性状,其提供相对于更惯用的方法for循环:

fn titlecase_word(word: &str) -> String {
    let mut result = String::with_capacity(word.len());

    if !word.is_empty() {
        let mut chars = word.chars();
        result.push(chars.next().unwrap().to_uppercase());
        result.extend(chars.map(|c| c.to_lowercase()));
    }

    result
}
Run Code Online (Sandbox Code Playgroud)

(在这里试试)

实际上,使用迭代器可以进一步缩短它:

fn titlecase_word(word: &str) -> String {
    word.chars().enumerate()
        .map(|(i, c)| if i == 0 { c.to_uppercase() } else { c.to_lowercase() })
        .collect()
}
Run Code Online (Sandbox Code Playgroud)

(在这里试试)

但是,如果您事先知道使用ASCII,则可以使用std::ascii模块提供的特征:

fn titlecase_word(word: String) -> String {
    use std::ascii::{AsciiExt, OwnedAsciiExt};
    assert!(word.is_ascii());

    let mut result = word.into_bytes().into_ascii_lowercase();
    result[0] = result[0].to_ascii_uppercase();

    String::from_utf8(result).unwrap()
}
Run Code Online (Sandbox Code Playgroud)

(在这里试试)

如果输入字符串包含任何非ASCII字符,则此函数将失败.

此函数不会分配任何内容,并将就地修改字符串内容.但是,您不能使用单个&mut String参数编写此类函数而不安全没有额外分配,因为它需要从中移出&mut,这是不允许的.

你可以使用std::mem::swap()带有空字符串的临时变量 - 它不需要不安全但它可能需要分配空字符串.我不记得它是否确实需要分配; 如果没有,那么你可以编写这样的函数,虽然代码会有点麻烦.无论如何,&mut对于Rust来说,参数并不是真正的惯用语.

  • @Vladimir:一个空的`String`由一个空的`Vec`支持,它[不在堆上分配存储](https://github.com/rust-lang/rust/blob/7d0cc44f873ac338b400b20bcb62618aa5d36b70/src/libcollections /vec.rs#L128). (2认同)
  • `result.push(c.to_lowercase());`不用Rust 1.9编译,因为`to_lowercase()`现在返回一个`std :: char :: ToLowercase`. (2认同)