如何传递 &mut str 并更改原始 mut str 而不返回?

rdx*_*dkr 7 string reference mutable lifetime rust

我正在从书中学习 Rust,并且正在处理第 8 章末尾的练习,但是在将单词转换为 Pig Latin 的问题上我遇到了困难。我想具体看看我是否可以将 a 传递&mut String给一个接受 a (也接受切片)的函数&mut str并修改其中引用的字符串,以便将更改反射回外部而不需要 a return,就像在 C 中使用 a 一样char **

我不太确定我只是搞乱了语法,还是由于 Rust 的严格规则而导致它比听起来更复杂,而我还没有完全掌握这些规则。对于里面的终身错误,to_pig_latin()我记得读过一些解释如何正确处理这种情况的东西,但现在我找不到它,所以如果你也能为我指出它,我将非常感激。

另外,您对我处理字符串内的字符和索引的方式有何看法?

use std::io::{self, Write};

fn main() {
    let v = vec![
        String::from("kaka"),
        String::from("Apple"),
        String::from("everett"),
        String::from("Robin"),
    ];

    for s in &v {
        // cannot borrow `s` as mutable, as it is not declared as mutable
        // cannot borrow data in a `&` reference as mutable
        to_pig_latin(&mut s);
    }

    for (i, s) in v.iter().enumerate() {
        print!("{}", s);

        if i < v.len() - 1 {
            print!(", ");
        }
    }

    io::stdout().flush().unwrap();
}

fn to_pig_latin(mut s: &mut str) {
    let first = s.chars().nth(0).unwrap();
    let mut pig;

    if "aeiouAEIOU".contains(first) {
        pig = format!("{}-{}", s, "hay");
        s = &mut pig[..]; // `pig` does not live long enough
    } else {
        let mut word = String::new();

        for (i, c) in s.char_indices() {
            if i != 0 {
                word.push(c);
            }
        }

        pig = format!("{}-{}{}", word, first.to_lowercase(), "ay");
        s = &mut pig[..]; // `pig` does not live long enough
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:这是固定代码以及下面的建议。

fn main() {
    // added mut
    let mut v = vec![
        String::from("kaka"),
        String::from("Apple"),
        String::from("everett"),
        String::from("Robin"),
    ];

    // added mut
    for mut s in &mut v {
        to_pig_latin(&mut s);
    }

    for (i, s) in v.iter().enumerate() {
        print!("{}", s);

        if i < v.len() - 1 {
            print!(", ");
        }
    }

    println!();
}

// converted into &mut String
fn to_pig_latin(s: &mut String) {
    let first = s.chars().nth(0).unwrap();

    if "aeiouAEIOU".contains(first) {
        s.push_str("-hay");
    } else {
        // added code to make the new first letter uppercase
        let second = s.chars().nth(1).unwrap();

        *s = format!(
            "{}{}-{}ay",
            second.to_uppercase(),
            // the slice starts at the third char of the string, as if &s[2..]
            &s[first.len_utf8() * 2..],
            first.to_lowercase()
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

Mas*_*inn 16

我不太确定我只是搞乱了语法,还是由于 Rust 的严格规则而导致它比听起来更复杂,而我还没有完全掌握这些规则。对于 to_pig_latin() 内的生命周期错误,我记得读过一些解释如何正确处理这种情况的内容,但现在我找不到它,所以如果您也能为我指出它,我将非常感激。

您尝试做的事情是行不通的:使用可变引用,您可以就地更新裁判,但这在这里极其有限:

  • a&mut str不能改变长度或任何类似的事情
  • a&mut str仍然只是一个引用,内存必须驻留在某个地方,在这里您在函数内创建新的字符串,然后尝试将它们用作引用的新后备缓冲区,正如编译器告诉您的那样,这是行不通的:字符串将在函数结束时被释放

您可以做的是采用一个&mut String,它可以让您就地修改拥有的字符串本身,这更加灵活。事实上,它完全符合您的请求: an&mut str对应于 a char*,它是指向内存中某个位置的指针。

AString也是一个指针,因此 an&mut String是指向内存中某个区域的双指针。

所以像这样:

fn to_pig_latin(s: &mut String) {
    let first = s.chars().nth(0).unwrap();
    if "aeiouAEIOU".contains(first) {
        *s = format!("{}-{}", s, "hay");
    } else {
        let mut word = String::new();

        for (i, c) in s.char_indices() {
            if i != 0 {
                word.push(c);
            }
        }

        *s = format!("{}-{}{}", word, first.to_lowercase(), "ay");
    }
}
Run Code Online (Sandbox Code Playgroud)

您还可以通过使用更精细的方法来避免一些完整的字符串分配,例如

fn to_pig_latin(s: &mut String) {
    let first = s.chars().nth(0).unwrap();
    if "aeiouAEIOU".contains(first) {
        s.push_str("-hay")
    } else {
        s.replace_range(first.len_utf8().., "");
        write!(s, "-{}ay", first.to_lowercase()).unwrap();
    }
}
Run Code Online (Sandbox Code Playgroud)

尽管replace_range+ 的write!可读性不太好,也不太可能带来太大的好处,所以这也可能是 a format!,类似于:

fn to_pig_latin(s: &mut String) {
    let first = s.chars().nth(0).unwrap();
    if "aeiouAEIOU".contains(first) {
        s.push_str("-hay")
    } else {
        *s = format!("{}-{}ay", &s[first.len_utf8()..], first.to_lowercase());
    }
}
Run Code Online (Sandbox Code Playgroud)