Складывание ссылок внутри совпадения приводит к пожизненной ошибке
Я хочу построить строку s
перебирая вектор простых структур, добавляя различные строки к acc
в зависимости от структуры.
#[derive(Clone, Debug)]
struct Point(Option<i32>, Option<i32>);
impl Point {
fn get_first(&self) -> Option<i32> {
self.0
}
}
fn main() {
let mut vec = vec![Point(None, None); 10];
vec[5] = Point(Some(1), Some(1));
let s: String = vec.iter().fold(
String::new(),
|acc, &ref e| acc + match e.get_first() {
None => "",
Some(ref content) => &content.to_string()
}
);
println!("{}", s);
}
Запуск этого кода приводит к следующей ошибке:
error: borrowed value does not live long enough
Some(ref content) => &content.to_string()
^~~~~~~~~~~~~~~~~~~
note: reference must be valid for the expression at 21:22...
|acc, &ref e| acc + match e.get_first() {
^
note: ...but borrowed value is only valid for the expression at 23:33
Some(ref content) => &content.to_string()
^~~~~~~~~~~~~~~~~~~~
Проблема в том, что время жизни &str
Я создаю, кажется, заканчивается немедленно. Однако если to_string()
вернул бы &str
во-первых, компилятор не жаловался бы. Тогда какая разница?
Как я могу заставить компилятор понять, что я хочу, чтобы ссылки на строки жили так долго, как я создаю s
?
2 ответа
Есть разница между результатом ваших веток:
""
имеет тип&'static str
content
имеет типi32
так что вы конвертируете его вString
а затем от этого к&str
... но это&str
имеет то же время жизни, что иString
вернулсяto_string
, который умирает слишком рано
Быстрый обходной путь, как упомянуто @Dogbert, состоит в том, чтобы переместиться acc +
внутри ветвей:
let s: String = vec.iter().fold(
String::new(),
|acc, &ref e| match e.get_first() {
None => acc,
Some(ref content) => acc + &content.to_string(),
}
);
Однако это немного расточительно, потому что каждый раз, когда у нас есть целое число, мы выделяем String
(с помощью to_string
) просто чтобы сразу отказаться от него.
Лучшее решение - использовать write!
вместо этого макрос, который просто добавляет к исходному строковому буферу. Это означает, что нет потраченных впустую распределений.
use std::fmt::Write;
let s = vec.iter().fold(
String::new(),
|mut acc, &ref e| {
if let Some(ref content) = e.get_first() {
write!(&mut acc, "{}", content).expect("Should have been able to format!");
}
acc
}
);
Возможно, это немного сложнее, особенно потому, что форматирование добавляет обработку ошибок, но более эффективно, поскольку использует только один буфер.
Есть несколько решений вашей проблемы. Но сначала несколько объяснений:
Если
to_string()
вернул бы&str
во-первых, компилятор не жаловался бы. Тогда какая разница?
Предположим, есть метод to_str()
который возвращает &str
, Как будет выглядеть подпись?
fn to_str(&self) -> &str {}
Чтобы лучше понять проблему, давайте добавим явные времена жизни (которые не нужны благодаря жизненному выбору):
fn to_str<'a>(&'a self) -> &'a str {}
Становится ясно, что возвращенный &str
живет до тех пор, пока получатель метода (self
). Это было бы хорошо, так как приемник живет достаточно долго для вашего acc + ...
операция. В вашем случае, однако, .to_string()
Call создает новый объект, который живет только во второй руке. После того, как тело руки останется, оно будет уничтожено. Поэтому вы не можете передать ссылку на него во внешнюю область (в которой acc + ...
происходит).
Итак, одно из возможных решений выглядит следующим образом:
let s = vec.iter().fold(
String::new(),
|acc, e| {
acc + &e.get_first()
.map(|f| f.to_string())
.unwrap_or(String::new())
}
);
Это не оптимально, но, к счастью, вашим значением по умолчанию является пустая строка и собственная версия пустой строки (String::new()
) не требует каких-либо распределений кучи, поэтому нет снижения производительности.
Тем не менее, мы по-прежнему выделяем один раз на целое число. Для более эффективного решения см . Ответ Матье М.