迭代器项的生命周期的“冲突要求”作为参数传递给方法

时间:2015-01-25 22:26:39

标签: rust lifetime

我试图让这段代码编译:

use std::collections::HashMap;

#[derive(PartialEq, Eq, Hash, Clone)]
struct Key<'a> {
    v: &'a str
}

fn make_key_iter(s: &str) -> Box<Iterator<Item = Key>> {
    Box::new(s.split('.').map(|e| Key { v: e }))
}

struct Node<'a> {
    children: HashMap<Key<'a>, Box<Node<'a>>>
}

impl<'a> Node<'a> {
    fn lookup<'b>(&self, mut iter: Box<Iterator<Item = Key<'b>>>) -> bool {
        match iter.next() {
            Some(key) => match self.children.get(&key) {
                             Some(node) => node.lookup(iter),
                             None => false
                         },
            None => true
        }
    }
}

fn main() {
    let s = "a.b.c.d".to_string();
    let iter = make_key_iter(s.as_slice());
    let node = Node { children: HashMap::new() };
    node.lookup(iter);
}

Playpen link

编译会出现以下错误:

<anon>:18:20: 18:26 error: cannot infer an appropriate lifetime due to conflicting requirements
<anon>:18         match iter.next() {
                         ^~~~~~
<anon>:17:5: 25:6 help: consider using an explicit lifetime parameter as shown: fn lookup(&self, mut iter: Box<Iterator<Item = Key<'b>>>) -> bool

令人困惑的是,编译器建议的签名完全无效,因为它使用了未定义的生命周期。

2 个答案:

答案 0 :(得分:7)

首先,一个建议:由于盒装迭代器也是迭代器,您可以将查找函数更改为

fn lookup<'b, I: Iterator<Item = Key<'b>>>(&self, mut iter: I) -> bool {
    match iter.next() {
        Some(key) => match self.children.get(&key) {
                         Some(node) => node.lookup(iter),
                         None => false
                     },
        None => true
    }
}

这更通用一点。但问题仍然存在。您正试图将&Key<'b>中的self.children.get(&key)传递给HashMap,而HashMap实际上需要&Q Q实现BorrowFrom<Key<'a>>。编译器的建议现在用这样的'b替换'a

fn lookup<I: Iterator<Item = Key<'a>>>(&self, mut iter: I) -> bool { //'
    match iter.next() {
        Some(key) => match self.children.get(&key) {
                         Some(node) => node.lookup(iter),
                         None => false
                     },
        None => true
    }
}

肯定会让编译器开心。但这不是你想要的!它会不必要地限制您可以用作查找参数的字符串切片集。这样,您只能使用引用内存的字符串切片,该切片至少与'a引用的范围一样长。但是对于查找,实际上并不需要这种限制。

解决方案是完全删除HashMap的Q函数的类型参数get中的任何生命周期参数。我们实际上可以使用Q=Key<'something>而不是Q=str。我们只需要添加以下BorrowFrom实现

impl<'a> BorrowFrom<Key<'a>> for str {
    fn borrow_from<'s>(owned: &'s Key<'a>) -> &'s str {
        owned.v
    }
}

并将Key类型设为public(因为它在公共特征中用作参数)。对我有用的查找函数如下所示:

fn lookup_iter<'b, I: Iterator<Item = Key<'b>>>(&self, mut i: I) -> bool {
    if let Some(key) = i.next() {
        match self.children.get(key.v) {
            Some(node_box_ref) => node_box_ref.lookup_iter(i),
            None => false
        }
    } else {
        true
    }
}

如果我们将所有内容拼凑在一起,我们就会得到

#![feature(core)]
#![feature(hash)]
#![feature(std_misc)]
#![feature(collections)]

use std::collections::HashMap;
use std::collections::hash_map::Entry::{ Occupied, Vacant };
use std::borrow::BorrowFrom;

#[derive(PartialEq, Eq, Hash, Clone)]
pub struct Key<'a> {
    v: &'a str
}

impl<'a> BorrowFrom<Key<'a>> for str {
    fn borrow_from<'s>(owned: &'s Key<'a>) -> &'s str {
        owned.v
    }
}

fn str_to_key(s: &str) -> Key { 
    Key { v: s }
}

struct Node<'a> {
    children: HashMap<Key<'a>, Box<Node<'a>>>
}

impl<'a> Node<'a> {
    fn add_str(&mut self, s: &'a str) {
        self.add_iter(s.split('.').map(str_to_key))
    }

    fn add_iter<I>(&mut self, mut i: I) where I: Iterator<Item = Key<'a>> { //'
        if let Some(key) = i.next() {
            let noderef =
                match self.children.entry(key) {
                    Vacant(e) => {
                        let n = Node { children: HashMap::new() };
                        e.insert(Box::new(n))
                    }
                    Occupied(e) => {
                        e.into_mut()
                    }
                };
            noderef.add_iter(i);
        }
    }

    fn lookup_str(&self, s: &str) -> bool {
        self.lookup_iter(s.split('.').map(str_to_key))
    }

    fn lookup_iter<'b, I>(&self, mut i: I) -> bool where I: Iterator<Item = Key<'b>> {
        if let Some(key) = i.next() {
            match self.children.get(key.v) {
                Some(node_box_ref) => node_box_ref.lookup_iter(i),
                None => false
            }
        } else {
            true
        }
    }
}

fn main() {
    let mut node: Node<'static> = Node { children: HashMap::new() }; //'
    node.add_str("one.two.three");
    { // <-- "inner scope"
        let s = String::from_str("one.two.three");
        println!("lookup: {:?}", node.lookup_str(&*s));
    }
    println!("The End");
}

如您所见,我故意将node设为Node<'static>,因此节点的生命周期参数'a实际上是指整个程序的生命周期。在这个例子中没关系,因为它将存储的唯一字符串切片是字符串文字。请注意,对于查找,我创建了一个短暂的String对象。因此,'b中的生命周期参数node.lookup_str将引用明显比'a='static短的“内部范围”。一切顺利! :)

哦,我也摆脱了迭代拳击。

答案 1 :(得分:1)

我同意诊断不太理想。我会建议提交一个bug;也许这个建议者还不了解相关类型的生命期。

要解决您的问题,我建议您使用与您已经拥有的相同的生命周期:

impl<'a> Node<'a> {
    fn lookup(&self, mut iter: Box<Iterator<Item = Key<'a>>>) -> bool { //'
        match iter.next() {
            Some(key) => match self.children.get(&key) {
                             Some(node) => node.lookup(iter),
                             None => false
                         },
            None => true
        }
    }
}

我实际上还不清楚你的原始代码试图做什么。您定义了一个新的生命周期参数'b。对于每次调用,该生命周期将由呼叫者确定。这是个坏消息,因为这个生命周期可能比Node本身更长,导致对不再有效的内存的引用。好极了! Rust拯救了我们!

另一个解决方案是拥有一个明确的生命周期'b,但告知Rust它的短于或等于'a(表示&#34; a超过b&# 34;)长于或等于'a(表示&#34; b超过&#34;):

fn lookup<'b : 'a>(&self, mut iter: Box<Iterator<Item = Key<'b>>>) -> bool

进一步阐述

这是一个显示相同问题的小例子:

use std::collections::HashSet;

fn example<'a, 'b>(set: HashSet<&'a u8>, key: &'b u8) -> bool {
    set.contains(&key)
}

fn main() {}

同样,如果您在生命周期(<'a, 'b : 'a>)之间设置关系或使两个参数的生命周期相同,那么此代码将被编译。

HashSet::containsHashMap::get都通过引用密钥类型(或者lend out a reference到密钥类型的内容)来查找密钥。但是,您正在查找的密钥必须与您存储的密钥类型(或子类型)相同。在这种情况下,类型还包括生命周期。这就是为什么使用相同的生命周期(或者超过密钥的生命周期)允许它进行编译。