2015-01-06 44 views
0

考慮下面的代碼:需要幫助理解迭代器壽命

#[derive(Clone)] 
pub struct Stride<'a, I: Index<uint> + 'a> { 
    items: I, 
    len: uint, 
    current_idx: uint, 
    stride: uint, 
} 

impl<'a, I> Iterator for Stride<'a, I> where I: Index<uint> { 
    type Item = &'a <I as Index<uint>>::Output; 

    #[inline] 
    fn next(&mut self) -> Option<&'a <I as Index<uint>>::Output> { 
     if (self.current_idx >= self.len) { 
      None 
     } else { 
      let idx = self.current_idx; 
      self.current_idx += self.stride; 
      Some(self.items.index(&idx)) 
     } 
    } 
} 

目前這錯誤,說是編譯器不能推斷爲Some(self.items.index(&idx))行適當的一生。回報價值的壽命應該是多少?我相信它應該與self.items的生命週期相同,因爲Index特徵方法會返回一個與Index實現者具有相同生命週期的參考。

回答

4

Indexdefinition是:

pub trait Index<Index: ?Sized> { 
    type Output: ?Sized; 
    /// The method for the indexing (`Foo[Bar]`) operation 
    fn index<'a>(&'a self, index: &Index) -> &'a Self::Output; 
} 

具體地說,index基準返回到其中參考具有相同的壽命作爲self的元件。也就是說,它借了self

在你的情況下,index呼叫self(可能是&self.items[idx] BTW)是self.items,所以編譯器看到的是,返回值被限制在從self.items借來的,但itemsnext的資self,所以self.items的借款是self本身的借款。

也就是說,編譯器只能保證index返回值是有效的,只要self生命(約突變種種顧慮),所以&mut self和壽命返回&...要拉上。

如果一個編譯它,看到的錯誤,鏈接引用是編譯器的建議是什麼:

<anon>:23:29: 23:40 error: cannot infer an appropriate lifetime for autoref due to conflicting requirements 
<anon>:23    Some(self.items.index(&idx)) 
             ^~~~~~~~~~~ 
<anon>:17:5: 25:6 help: consider using an explicit lifetime parameter as shown: fn next(&'a mut self) -> Option<&'a <I as Index<uint>>::Output> 
<anon>:17  fn next(&mut self) -> Option<&'a <I as Index<uint>>::Output> { 
<anon>:18   if (self.current_idx >= self.len) { 
<anon>:19    None 
<anon>:20   } else { 
<anon>:21    let idx = self.current_idx; 
<anon>:22    self.current_idx += self.stride; 
      ... 

但是,建議簽名fn next(&'a mut self) -> Option<&'a <I as Index<uint>>::Output>Iterator性狀特徵更加嚴格,因此是非法的。 (與壽命的這種佈置迭代器可能是有用的,但他們沒有與許多普通消費者的工作,像.collect。)

編譯器是一種防止被像一個類型表現出的問題:

struct IndexablePair<T> { 
    x: T, y: T 
} 

impl Index<uint> for IndexablePair<T> { 
    type Output = T; 
    fn index(&self, index: &uint) -> &T { 
     match *index { 
      0 => &self.x, 
      1 => &self.y, 
      _ => panic!("out of bounds") 
     } 
    } 
} 

這將兩個T在線存儲(例如直接堆疊)並允許將它們編入索引pair[0]pair[1]index方法將指針直接返回到該存儲器(例如堆棧),所以如果IndexablePair值在存儲器中移動,則這些指針將變爲無效,例如, (假設爲Stride::new(items: I, len: uint, stride: uint)):

let pair = IndexablePair { x: "foo".to_string(), y: "bar".to_string() }; 

let mut stride = Stride::new(pair, 2, 1); 

let value = stride.next(); 

// allocate some memory and move stride into, changing its address 
let mut moved = box stride; 

println!("value is {}", value); 

倒數第二行是壞的!它使value無效,因爲stride和它的字段items(對)在內存中移動,因此value中的引用將指向移動的數據;這是非常不安全和非常糟糕的。

建議的生命週期通過借用stride並禁止此舉來阻止此問題(以及其他一些有問題的問題),但正如我們上面所看到的,我們不能使用它。

到可熔酚醛這方面的一個技術是分離所述存儲器存儲從所述迭代器本身的元件,也就是改變它的Stride的定義:(添加到items的引用)

pub struct Stride<'a, I: Index<uint> + 'a> { 
    items: &'a I, 
    len: uint, 
    current_idx: uint, 
    stride: uint, 
} 

然後,編譯器有保證內存存儲單元是獨立於Stride值(也就是,在內存中移動Stride不會廢止舊的元素),因爲有一個不擁有指針把它們分開。該版本編譯罰款:

use std::ops::Index; 

#[derive(Clone)] 
pub struct Stride<'a, I: Index<uint> + 'a> { 
    items: &'a I, 
    len: uint, 
    current_idx: uint, 
    stride: uint, 
} 

impl<'a, I> Iterator for Stride<'a, I> where I: Index<uint> { 
    type Item = &'a <I as Index<uint>>::Output; 

    #[inline] 
    fn next(&mut self) -> Option<&'a <I as Index<uint>>::Output> { 
     if (self.current_idx >= self.len) { 
      None 
     } else { 
      let idx = self.current_idx; 
      self.current_idx += self.stride; 
      Some(self.items.index(&idx)) 
     } 
    } 
} 

(理論上人們可以通過手動實現Clone而不是derive荷蘭國際集團將其添加在那裏結合的?Sized,很可能使Stride可以直接與&[T]使用,即Stride::new(items: &I, ...)Stride::new(&[1, 2, 3], ...)會工作,而不是必須具有Stride::new(&&[1, 2, 3], ...)雙層作爲結合的默認Sized需要。)

playpen