Impl From or AsRef for a collection/slice

Hi folks,

I'd like to be able to:

let x = vec![Foo{ ... } ... ];
let y: Vec<String> = x.into()

or

let x = &[Foo{ ... } ... ];
let y: &[&str] = x.as_ref()

but I can't:

pub struct Foo{
    bar: String
}
impl From<Foo> for String {
    fn from(value: Foo) -> String {
        value.bar
    }
}
impl AsRef<str> for Foo {
    fn as_ref(&self) -> &str {
        self.bar.as_ref()
    }
}
// error: this is not defined in the current crate
impl From<Vec<Foo>> for Vec<String> {
    fn from(value: Vec<Foo>) -> Self {
        value.into_iter().map(|x| x.bar).collect()
    }
}

Could you please advise what is the best practice for this?

This fundamentally can't work, because the layouts aren't compatible.

To get a &[&str], a [&str] has to exist somewhere so the reference can point to it, but there isn't one. You only have a [Foo]. (Note that a [String] doesn't work either, because String and &str have different layouts.)

What you can do (you may already know this):

/// Converts `Vec<Foo>` into a new `Vec<String>`.
/// Consumes `Vec<Foo>`, so the `String`s are moved, not reallocated.
fn vec_of_foo_into_vec_of_string(vec: Vec<Foo>) -> Vec<String> {
    vec.into_iter().map(|x| x.bar).collect()
}

/// Converts `&[Foo]` into a new `Vec<&str>`.
/// The `[Foo]` slice (which could be in a `Vec`) is left intact.
/// The returned `&str`s cannot live for longer than the input slice,
/// since they refer to the `String`s in the `Foo`s.
fn slice_of_foo_to_vec_of_str(slice: &[Foo]) -> Vec<&str> {
    slice.iter().map(|x| x.bar.as_ref()).collect()
}

This could also be done generically:

fn into_vec_of_string<T>(vec: Vec<T>) -> Vec<String>
where
    T: Into<String>,
{
    vec.into_iter().map(|x| x.into()).collect()
}

fn to_vec_of_str<T>(slice: &[T]) -> Vec<&str>
where
    T: AsRef<str>,
{
    slice.iter().map(|x| x.as_ref()).collect()
}

You can further simplify by doing the mapping without explicit closures:

    vec.into_iter().map(Into::into).collect()

    slice.iter().map(AsRef::as_ref).collect()

In addition to the limitations you encountered with the orphan rule ("not defined in the current crate"), it is generally a good idea to be careful not to overuse the From/Into traits. The doc has guidelines for using these traits:
https://doc.rust-lang.org/std/convert/trait.From.html#when-to-implement-from

3 Likes

Depending on how the resulting slices are used, it may be workable to return the iterators directly, without collect. For cases where you only visit them sequentially, you use the iterator. For cases where you need random access, or visiting multiple times then you call your helper function then collect to a Vec.

1 Like

Thank you. I create functions as @jumpnbrownweasel .

@danjl1100 that's an interesting idea. Thank you. Do you know about a project / crate that does that so I can look at the code?

I think they just mean you can have functions that return an iterator, instead of collecting it into a Vec:

fn into_string_iter<T>(vec: Vec<T>) -> impl Iterator<Item = String>
where
    T: Into<String>,
{
    vec.into_iter().map(Into::into)
}

fn to_str_iter<T>(slice: &[T]) -> impl Iterator<Item = &str>
where
    T: AsRef<str>,
{
    slice.iter().map(AsRef::as_ref)
}

That way you can either use the iterator directly:

    for s in to_str_iter(&v1) {/*...*/}
    for s in into_string_iter(v1) {/*...*/}

Or you can collect from the iterator:

    let v: Vec<&str> = to_str_iter(&v1).collect();
    let v: Vec<String> = into_string_iter(v1).collect();
1 Like