如何将元组(String,String)的迭代器转换为(& str,& str)的迭代器?

3

我遇到了将一个 (String, String) 的迭代器转换为 (&str, &str) 的迭代器的问题。我正在使用一个外部库,所以不能更改其签名,也不确定是否需要更改。基本上,我有这个函数定义:

use hyper;

fn build_url<'a, I>(host: &'a str, port: u16, path: &'a str, params: I) -> 
   hyper::Url where I: Iterator<Item=(String, String)> {

       let mut url = hyper::Url::parse(&format!("http://{h}:{p}/{pt}",
                                            h = self.etcd_host,
                                            p = self.etcd_port,
                                            pt = path));
       if let Err(e) = url {
           panic!("error parsing url: {}", e);
       }

       let mut url = url.unwrap();

       // fn set_query_from_pairs<'a, I>(&mut self, pairs: I)
       //  where I: Iterator<Item=(&'a str, &'a str)>
       url.set_query_from_pairs(
            params.map(|x: (String, String)| -> 
                       (&str, &str) { let (ref k, ref v) = x; (k, v) } ));

}

但是我遇到了可怕的错误:error: 'x.0' does not live long enough

我认为在let中使用ref关键字应该是正确的做法,即将所有权保留在迭代器中,只进行借用。如果我将let更改为以下内容,则会遇到类似的问题:

let (k, v) = x; (&k, &v)

然后kv的寿命不够长。有人有修复建议吗?


1
你需要的是一个流式迭代器。目前还没有这样的迭代器,但可能有替代方案。你从哪里获取params迭代器?它是一个映射还是一个向量?如果是这样,你可以从中创建一个非拥有迭代器,然后将其映射到一个(&str, &str)迭代器。 - oli_obk
是的,实际上这是从 vec![] 的创建中来的,因此我可以更改该参数类型... - bluejekyll
2个回答

7
您无法安全地拥有迭代器,以产生对任何内部或拥有状态的引用;Iterator特征只是不设计允许它。这些构造通常被称为“流迭代器”,它们目前在语言/stdlib中存在一些漏洞。
考虑一个(String, String)值在您的map调用中的流动情况。每个元组从I::next返回,这将导致所有权传递到您提供给map的闭包中。因此,在闭包中使用ref时,您正在引用局部变量。现在,您构造了一个新的元组,将其返回,因为闭包拥有String(它们存储在kv中),所以它们被销毁,从而使您尝试返回的引用无效。
问题在于没有办法避免拥有(String, String)项。
现在,这样做可能会有欺骗行为。您需要做的就是保证(String, String)值在迭代器的每个单独步骤之后继续存在。
let params: Vec<_> = params.collect();
url.set_query_from_pairs(params.iter().map(|&(ref x, ref y)| (&x[..], &y[..])))

这是因为Vec::iter给了我们Iterator<Item=&(String, String)>,我们可以借用其中的内容而不需要拥有它们(所有权仍由params保留)。

我对 &x[..] 不太熟悉,有没有相关的文档可以参考?我记不清在书籍或其他资源中有提到过这个。 - bluejekyll
这个对我有用:url.set_query_from_pairs(params.iter().map(|&(ref k,ref v)| -> (&'a str, &'a str) { (k,v) })); - bluejekyll
为了更清晰,我将我的方法签名改为使用'&a Vec,然后使映射中的&str绑定到该生命周期。 - bluejekyll
@bluejekyll,&x[..]是我们所谓的切片语法。x[..]是一个索引操作,它接受一个FullRange对象(..) - 对于一个String,它意味着返回一个对应于整个字符串的str。然后使用&来获取一个&str。你也可以写成&x[1..],从第二个字节开始切割字符串到结尾。这种语法在这里有描述(http://doc.rust-lang.org/1.0.0-beta/book/arrays-vectors-and-slices.html)。 - Vladimir Matveev
你的方法有效是因为存在解引用强制转换 - kv&String 类型,而在某些上下文中,当目标类型(在本例中为 &str)已知时,&String 可以自动转换为 &str - Vladimir Matveev
@bluejekyll,它是在最近一周左右添加到http://doc.rust-lang.org/nightly/book/primitive-types.html#slices中的。 - Steve Klabnik

4

由于您的params参数是从Vec<(String,String)>创建的,因此您可以将where子句更改为where I: Iterator<Item=(&str, &str)>并通过调用来获取迭代器。

your_vector.iter().map(|(a, b)|, (&a[..], &b[..])))

一个简化的例子:
fn test<'a, I>(it: I)
    where I: Iterator<Item=&'a str>
{
    for s in it {
        dump(s);
    }
}

fn dump(s: &str) {
    println!("{}", s);
}

fn main() {
    let v = vec!["a".to_string(), "42".to_string()];
    test(v.iter().map(|s| &s[..]));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接