9

I'm working on my first Rust crate and I wanted to make my API a bit more user friendly by allowing both foo(vec!["bar", "baz"]) and foo(vec![String::from("foo"), String::from("baz")]).

So far I've managed to accept both String and &str but I'm struggling to do the same for Vec<T>.

fn foo<S: Into<String>>(string: S) -> String {
    string.into()
}

fn foo_many<S: Into<String>>(strings: Vec<S>) -> Vec<String> {
    strings.iter().map(|s| s.into()).collect()
}

fn main() {
    println!("{}", foo(String::from("bar")));
    println!("{}", foo("baz"));

    for string in foo_many(vec!["foo", "bar"]) {
        println!("{}", string);
    }
}

The compiler error I get is:

error[E0277]: the trait bound `std::string::String: std::convert::From<&S>` is not satisfied
 --> src/main.rs:6:30
  |
6 |     strings.iter().map(|s| s.into()).collect()
  |                              ^^^^ the trait `std::convert::From<&S>` is not implemented for `std::string::String`
  |
  = help: consider adding a `where std::string::String: std::convert::From<&S>` bound
  = note: required because of the requirements on the impl of `std::convert::Into<std::string::String>` for `&S`
Stargateur
  • 24,473
  • 8
  • 65
  • 91
Martin Sotirov
  • 351
  • 2
  • 11
  • Possible duplicate of [How to write a trait method taking an iterator of strings, avoiding monomorphization (static dispatch)?](https://stackoverflow.com/questions/54225766/how-to-write-a-trait-method-taking-an-iterator-of-strings-avoiding-monomorphiza) – Stargateur Jul 26 '19 at 16:10
  • 2
    I have retract my vote, the other question is probably too confusing for newbie, sorry for that. – Stargateur Jul 26 '19 at 16:25
  • Using `AsRef` might be more useful than `Into`. Also note that by converting into String, you're probably unnecessarily allocating strings into the heap. – SOFe Jul 27 '19 at 06:30

2 Answers2

10

You can go for full generic, you don't need to force user to use a Vec, better you can take a generic type that implement IntoIterator that you just have to write that Item implement Into<String>, the syntax is somehow strange and logic. You need a second generic type to do it. I call I the type that will be the iterator and T the Item type.

fn foo<S: Into<String>>(string: S) -> String {
    string.into()
}

fn foo_many<I, T>(iter: I) -> Vec<String>
where
    I: IntoIterator<Item = T>,
    T: Into<String>,
{
    iter.into_iter().map(Into::into).collect()
}

fn main() {
    println!("{}", foo(String::from("bar")));
    println!("{}", foo("baz"));

    for string in foo_many(vec!["foo", "bar"]) {
        println!("{}", string);
    }

    for string in foo_many(vec![foo("foo"), foo("baz")]) {
        println!("{}", string);
    }
}

This solve your problem and make your function even more generic.

Stargateur
  • 24,473
  • 8
  • 65
  • 91
  • I haven't even thought about allowing other iterable structures but that's definitely an even more flexible solution! Thanks. – Martin Sotirov Jul 26 '19 at 16:39
5

This doesn't work because your iteration doesn't give you S but &S.

If you want to move the strings out of the vector, you must make it mutable and drain it:

fn foo_many<S: Into<String>>(mut strings: Vec<S>) -> Vec<String> {
    strings.drain(..).map(|s| s.into()).collect()
}

playground

Denys Séguret
  • 372,613
  • 87
  • 782
  • 758