How to convert an Iterator on a tuple of (String,

2019-07-02 12:37发布

I'm having trouble converting from an Iterator of (String, String) to an Iterator of (&str, &str). I'm using an external library, so can't change the signature of that, and not sure that I need to. Basically I have this function def:

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) } ));

}

But I'm getting the dreaded: error: 'x.0' does not live long enough

I think the ref keyword in the let should have been the right thing here, i.e. keep the ownership with the Iterator, and just do a borrow. I get a similar issue if I get rid of ref in the let changing the let to this:

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

Then k and v don't live long enough. Does anyone have a recommendation for fixing this?

标签: rust lifetime
2条回答
甜甜的少女心
2楼-- · 2019-07-02 13:11

Since your params argument is created from a Vec<(String, String)> you can change your where clause to where I: Iterator<Item=(&str, &str)> and get the iterator by calling

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

A simplified example:

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[..]));
}
查看更多
做自己的国王
3楼-- · 2019-07-02 13:12

You can't have an iterator that (safely) yields references to any internal or owned state; the Iterator trait is just not designed to allow it. These sorts of constructs are usually known as "streaming iterators", and they're something of a hole in the language/stdlib at the moment.

Consider what happens to a (String, String) value as it flows through your map call. Each tuple is returned from I::next, which causes ownership to pass into the closure you gave to map. Thus, when you use ref in the closure, you're taking a reference to variables which are local to the closure. You now construct a new tuple, return it and... because the closure owns the Strings (they're being stored in k and v), they are destroyed, thus invalidating the references you tried to return.

The problem is that there is no way to avoid taking ownership of the (String, String) items.

Now, that having been said, you can cheat here. All you need to do is guarantee that the (String, String) values continue to exist beyond each individual step in the iterator. Thus:

let params: Vec<_> = params.collect();
url.set_query_from_pairs(params.iter().map(|&(ref x, ref y)| (&x[..], &y[..])))

This works because Vec::iter gives us Iterator<Item=&(String, String)>, from which we can borrow without taking ownership (which is retained by params).

查看更多
登录 后发表回答