2016-08-30 14 views
1

構造体に応じてaccに異なる文字列を追加して、単純な構造体のベクトルを反復して文字列sを作成します。一致内の参照を折りたたむと生涯エラーが発生する

#[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の有効期間がmatch文よりも大きいためです。マッチに組み込まれた新しい文字列は、ブロック内にのみ存在します。 – ljedrz

答えて

6

あなたの枝の結果の違いがあります:

  • ""はタイプ&'static str
  • contentのですがタイプi32であるので、あなたはそれから&strに、その後Stringに変換され、 ...しかし、この&strはあまりにも早く死ぬto_stringで返さ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(), 
    } 
); 

しかし、整数があるたびにすぐに破棄するためにStringto_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

代わりに 'std :: fmt :: Write'を使うことで' write!() 'マクロで' String'を使うことができます。これにより、コードが短くなります。私はまた 'を好むだろう' Some(ref content)= e.get_first(){write!(...); } 'acc'を2回返すのを避けるために' 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?

&strを返すメソッドto_str()があるとします。署名はどのように見えますか?

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

よりよく問題を理解するためには、(生涯エリジオンに必要なおかげではありません)、明示的な寿命を追加することができます:

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

それはのレシーバ限り&str生活を返されたことが明らかになり、方法(self)。これは、受信者があなたのacc + ...操作のために十分長く生きているのでOKです。しかし、あなたの場合、.to_string()コールは、2番目のマッチアームにのみ存在する新しいオブジェクトを作成します。腕の体が残った後、それは破壊されます。したがって、外側のスコープ(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())の所有するバージョンですヒープ割り当てを必要としないため、パフォーマンス上のペナルティはありません。

ただし、まだ整数ごとに1つずつ割り当てています。より効率的なソリューションについては、Matthieu M.'s answerを参照してください。

関連する問題