如何用新格式化的字符串替换字符串切片的Vec中的一个元素?

时间:2019-04-05 18:28:31

标签: rust

我正在尝试替换字符串切片列表中的一行,并且无法使用生命周期正确地替换它。

这是我的代码:

pub struct SomeDataType<'a> {
    pub lines: Vec<&'a str>,
    // other fields omitted
}

impl<'a> SomeDataType<'a> {
    pub fn parse(text: &str) -> Result<SomeDataType, String> {
        let lines: Vec<&str> = text.lines().collect();
        Ok(SomeDataType { lines })
    }

    // replace first occurrence, and return original value
    pub fn replace_placeholder(&mut self, real_value: &str) -> Option<String> {
        let newstr = format!("## {}", real_value);
        for line in self.lines.iter_mut() {
            if line.starts_with("## PLACEHOLDER") {
                let original: String = String::from(*line);
                *line = newstr.as_str();
                return Some(original);
            }
        }
        None
    }
}

fn main() {
    let text = r##"
Lorem ipsum
## PLACEHOLDER 1
dolor sit amet,
## PLACEHOLDER 2
consectetur adipiscing elit,
"##;

    let mut x = SomeDataType::parse(text).unwrap();
    let original = x.replace_placeholder("The Real Value");
    println!("ORIGINAL VALUE: {:?}", original); //prints: ORIGINAL VALUE: Some("## PLACEHOLDER 1")
    println!("{}", x.lines.join("\n")) //prints the text with first occurrence replaced
}
error[E0597]: `newstr` does not live long enough
  --> src/main.rs:18:25
   |
6  | impl<'a> SomeDataType<'a> {
   |      -- lifetime `'a` defined here
...
18 |                 *line = newstr.as_str();
   |                 --------^^^^^^---------
   |                 |       |
   |                 |       borrowed value does not live long enough
   |                 assignment requires that `newstr` is borrowed for `'a`
...
23 |     }
   |     - `newstr` dropped here while still borrowed

这一定是借来的,而且寿命很长,但是我不知道是什么。

2 个答案:

答案 0 :(得分:5)

您的数据结构

pub struct SomeDataType<'a> {
    lines: Vec<&'a str>,
}

存储引用到字符串切片。由于在Rust中引用必须始终有效,因此这些字符串切片的生存期必须比SomeDataType的实例更长,并且每个字符串切片的生存期必须至少为'a

您的函数replace_placeholder()在此行中创建一个新的本地String实例:

let newstr = format!("## {}", real_value);

String实例仅在函数运行时有效,因为它是局部变量。为了能够在self.lines中存储对此字符串的引用,它必须至少生存'a的生存期SomeDataType,而不是生存。这就是编译器抱怨的原因。

使用当前的数据结构,您实际上无法做到这一点。您在replace_placeholder()中创建的任何字符串仅在函数运行时有效,除非您可以将字符串的所有权传递给寿命更长的数据结构。 SomeDataType不能拥有所有权-它仅存储引用。

最简单的解决方案是将数据类型定义更改为

pub struct SomeDataType {
    lines: Vec<String>,
}

因此它拥有所有字符串。这将需要您从解析的行中创建新的String对象,因此您将复制所有行。这不太可能成为问题,但是如果出于某种原因需要避免这种开销,则也可以使用向量Cow<'a, str>。这种数据结构能够存储引用或拥有的字符串。

答案 1 :(得分:2)

这是我如何按照Sven Marnach和Stargateur建议使用Cow对其进行更新来使代码工作的方式:

use std::borrow::Cow;

pub struct SomeDataType<'a> {
    pub lines: Vec<Cow<'a, str>>,
    // other fields omitted
}

impl<'a> SomeDataType<'a> {
    pub fn parse(text: &str) -> Result<SomeDataType, String> {
        let lines = text.lines().map(Cow::Borrowed).collect();
        Ok(SomeDataType { lines })
    }

    // replace first occurrence, and return original
    pub fn replace_placeholder(&mut self, real_value: &str) -> Option<String> {
        let newstr = Cow::Owned(format!("## {}", real_value));
        for line in self.lines.iter_mut() {
            if line.starts_with("## PLACEHOLDER") {
                let original: String = String::from(line.clone());
                *line = newstr;
                return Some(original);
            }
        }
        None
    }
}

或者,使用String看起来更简单,而且可能也更优雅:

pub struct SomeDataType {
    pub lines: Vec<String>,
    // other fields omitted
}

impl SomeDataType {
    pub fn parse(text: &str) -> Result<SomeDataType, String> {
        let lines = text.lines().map(String::from).collect();
        Ok(SomeDataType { lines })
    }

    // replace first occurrence, and return original
    pub fn replace_placeholder(&mut self, real_value: &str) -> Option<String> {
        let newstr = format!("## {}", real_value);
        for line in self.lines.iter_mut() {
            if line.starts_with("## PLACEHOLDER") {
                let original = line.clone();
                *line = newstr;
                return Some(original);
            }
        }
        None
    }
}