Как я могу заменить один элемент в Vec строковых фрагментов недавно отформатированной строкой? - PullRequest
2 голосов
/ 05 апреля 2019

Я пытаюсь заменить одну строку в списке фрагментов строки и не могу сделать это правильно со временем жизни.

Вот мой код:

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 ]

5 голосов
/ 06 апреля 2019

Ваша структура данных

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>. Эта структура данных может хранить либо ссылку, либо собственную строку.

2 голосов
/ 08 апреля 2019

Вот как я заставил код работать, обновив его для использования Cow, как рекомендовали Свен Марнах и Stargateur:

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
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...