j_s
j_s

Reputation: 103

How to convert IntoIterator<Item=AsRef<str>> to Iterator<Item=&str> in Rust?

I'm running Rust nightly (rustc 1.50.0-nightly (f0f68778f 2020-12-09)).

I learned about a way to accept (as a function parameter) arbitrary objects that can be converted to an iterator. I also learned about a way to do that to allow accepting both String and &str (and maybe other types that can be converted to &str) using AsRef<str>.

So far so good, I have a following test code that demonstrates that:

use std::env;

fn main() {
    let v1: Vec<String> = vec!["a".to_string(), "b".to_string()];
    let v2: Vec<&str> = vec!["a", "b"];
    let args: env::Args = env::args();

    fun1(&v1);
    fun1(&v2);

    fun1(v1);
    fun1(v2);
    fun1(args);
}

fn fun1<I, T>(args: I)
where
    I: IntoIterator<Item = T>,
    T: AsRef<str>,
{
    println!("=================");
    for item in args {
        println!("{}", item.as_ref());
    }
}

It compiles nicely and runs fine. Now, my desire is to convert args from IntoIterator<Item=AsRef<str>> into Iterator<Item=&str> so I don't have to deal with those as_ref() calls throughout the function. In this example it's a simple loop that calls as_ref() in one place but my code that I'm not sharing here is more convoluted – I want to avoid calling as_ref() every time I get a next element out of that iterator.

Unfortunately my naive attempt to do so doesn't work:

use std::env;

fn main() {
    let v1: Vec<String> = vec!["a".to_string(), "b".to_string()];
    let v2: Vec<&str> = vec!["a", "b"];
    let args: env::Args = env::args();

    fun2(&v1);
    fun2(&v2);

    fun2(v1);
    fun2(v2);
    fun2(args);
}

fn fun2<I, T>(args: I)
where
    I: IntoIterator<Item = T>,
    T: AsRef<str>,
{
    println!("=================");
    let iter = args.into_iter();
    let mapped = iter.map(|item| item.as_ref());
    for item in mapped {
        println!("{}", item);
    }
}
% rustc test1.rs 
error[E0515]: cannot return value referencing function parameter `item`
  --> test1.rs:23:34
   |
23 |     let mapped = iter.map(|item| item.as_ref());
   |                                  ----^^^^^^^^^
   |                                  |
   |                                  returns a value referencing data owned by the current function
   |                                  `item` is borrowed here

error: aborting due to previous error

For more information about this error, try `rustc --explain E0515`.

and for the life of me I can't get around this (I played with various approaches and all I got was errors like this), I looked through some previous questions and no luck.

Is there even a way to achieve this in Rust?

Upvotes: 0

Views: 1606

Answers (1)

Alice Ryhl
Alice Ryhl

Reputation: 4229

This is not possible without collecting all of the data in the iterator into a Vec or similar. This is because an Iterator<Item = &str> requires that the string data behind the slices is not owned by the iterator, but an Iterator<Item = String> does own the string data by virtue of using an owned type.

This also explains why collecting into a vector works. In that case, the string data would be owned by the vector, and not the iterator, which satisfies the requirement that the Iterator<Item = &str> does not own the string data.

fn fun2<I, T>(args: I)
where
    I: IntoIterator<Item = T>,
    T: AsRef<str>,
{
    println!("=================");
    
    let vec: Vec<T> = args.into_iter().collect();
    
    let mapped = vec.iter().map(|item| item.as_ref());
    for item in mapped {
        println!("{}", item);
    }
}

Note that it is not always the case that an T: AsRef<str> type owns the string data. For example, if T = &str, then it doesn't. Unfortunately generic code must handle all cases allowed by the generic parameters, so if the code does not handle the T = String case, it wont compile.

Note that in the case of T = &str, then the vector in the above example does not own the string data. Still, this is ok, because in that case, the string data is owned by some other collection entirely outside of fun2, and that also satisfies the requirement that Iterator<Item = &str> must not own the string data.

Upvotes: 1

Related Questions