Collect string references

Consider this playground:

fn main() {
    let a:Vec<String> = vec!["hello".to_string(), "world".to_string()];
    
    //totally fine
    let _b:Vec<&String> = a.iter().collect();
    
    //this isn't allowed
    //let _c:Vec<&str> = a.iter().collect();
    
    //neither is this
    //let _d:Vec<&str> = a.iter().map(|x| &x).collect();
    
    //also fine
    let _e:Vec<&str> = a.iter().map(|x| x.as_ref()).collect();
}

Is there a way to collect as <&str> without going through as_ref() ?

Also, why isn't & equivilent to calling as_ref() here?

I think as_ref() is the right way to do this. The alternative would be to simply cast x to &str within the map(), forcing a deref coercion.

The reason why the ways you tried don't work is that iter() returns an iterator over &String. A deref coercion can't be performed through the iterator, because the compiler can't make assumptions about how an associated type might be used. A similar principle holds for the generic parameter of Vec and, since neither of those hold, a coercion to Vec<&str> can't be done in the first case. Attempting to take a reference again results in &&String, which also doesn't work. The as_ref() method takes a &String as input so the method resolution doesn't take another reference and can just pass it in directly.

1 Like
let _c:Vec<&&String> = a.iter().map(|x| &x).collect();

And &&String doesn't coerce to &str.

But the code below should work, because &String can coerce to &str

let _c:Vec<&str> = a.iter().map(|x| x).collect();
1 Like

I'm gonna need to meditate on that one :slight_smile:

How is this any different than just skipping the map altogether?

It's different because the map(|x: &String| -> &str {x}) (to spell out the map in full with type annotations) gives the compiler a place to insert any code it needs to change the type.

1 Like

Interesting... and there's no "place" in iter() or collect() to do that?

Yes, because you can't insert coercion points into generic code (except in a few constrained cases)

Correct, because implicit deref coercions in the Rust compiler means that the x expands out to x.deref() to make the types fit, and it's that deref call that causes the type conversion to take place.

Neither collect nor iter have type issues without deref coercion, so there's nowhere in there to put the code; the map provides somewhere to do the implicit deref coercion and make the types fit.

1 Like

Sorry - not following... why is iter() and collect() generic code, but map() is not? They're all methods over the same impl's (that use generics) - e.g. Iterator, IntoIterator, etc.

I guess I'm not quite clear on what you mean by generic code here...

So it's literally the fact that map() accepts an expression which allows room for replacing that expression with x.deref() (plus whatever else is there)... ?

Yes - it's literally that, because the implicit deref generates a call to x.deref() to make the type of the closure fit, and that closure type combined with map changes the type of the iterator from impl Iterator<Item = &String> to impl Iterator<Item = &str>; collect on the latter type can then generate a Vec<&str>, whereas there is no implementation of FromIterator<&String> for Vec<&str>.

1 Like

Yes, I should be more clear on that. The big difference is that map takes a closure argument. This closure is not generic, so Rust can insert some deref coercions.

You won't see coercions inside the implementations of It Iterator::map, Iterator::collect or <[_]>::iter, because coercions require more information about the concrete types than the implementation provides.

For example, to perform a deref coercion you need to know that you have a type &P or &mut P, where P: Deref. But in the generic implications of the above methods, you can't know that. The generic implantations also require that types match up exactly.

The difference with the closure is that all other types are known, so Rust knows that a deref coercion is valid, so it applies it.

3 Likes