2016-08-30 3 views
1

Я хочу построить строку 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?

+1

(FYI: Перемещение 'acc +' в каждую ветку работает: https://play.rust-lang.org/?gist=95adc6ff8736d21b941a8a89ef67f582&version=stable&backtrace=0.) – Dogbert

+0

Да, это так. Должно быть, это последнее, что я не пробовал, прежде чем делать это сообщение. но почему? – lsund

+0

Это потому, что время жизни acc больше, чем утверждение соответствия. Новая строка, построенная в матче, живет только внутри своего блока. – ljedrz

ответ

6

Существует разница между результатом ваших филиалов:

  • "" имеет тип &'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 
    } 
); 

Это может быть немного более сложным, в частности потому, что форматирование добавляет обработку ошибок, но является более эффективным, поскольку он использует только один буфер.

+2

Вы можете использовать 'String' с помощью макроса' write!() ', Используя вместо этого' std :: fmt :: Write'. Это делает код короче. Я также предпочел бы: «если пусть Some (ref content) = e.get_first() {write! (...); } acc', чтобы избежать «возвращения» 'acc' дважды. –

+0

@LukasKalbertodt: Намного лучше! –

3

Существует несколько вариантов решения вашей проблемы. Но первые несколько объяснений:

If to_string() would have returned a &str in the first place, the compiler would not have complained. Then, what is the difference?

Предположим, что существует метод to_str(), который возвращает &str. Как будет выглядеть подпись?

fn to_str(&self) -> &str {} 

Чтобы лучше понять эту проблему, позволяет добавлять явные времена жизни (которые не являются необходимыми благодаря пожизненной элизии):

fn to_str<'a>(&'a self) -> &'a str {} 

становится ясно, что возвращаемые &str жизнь до тех пор, как приемник из метод (self). Это было бы нормально, так как приемник живет достаточно долго для вашей операции acc + .... Однако в вашем случае вызов .to_string() создает новый объект, который живет только во втором матче. После того как тело руки останется, оно будет уничтожено. Поэтому вы не можете передать ссылку на него во внешнюю область (в которой происходит 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()) не требует никаких распределений кучи, поэтому нет штрафа за производительность.

Однако мы по-прежнему выделяем один раз за целое число. Для более эффективного решения см. Matthieu M.'s answer.