通过索引修改String中的字符

时间:2014-10-24 08:52:32

标签: rust

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

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

不安全的阻塞特别不合需要。有没有更好的方法来通过索引修改String内容?

1 个答案:

答案 0 :(得分:15)

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


您的代码不正确,因为它访问单个字节以执行基于字符的操作,但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;
}

(试试here

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

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
}

(试试here

同样String来自Extend特征的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
}

(试试here

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

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

(试试here

但是,如果您事先知道您正在使用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()
}

(试试here

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

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

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