Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to accept both Vec<String> and Vec<str> as function arg in Rust

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`
like image 349
Martin Sotirov Avatar asked Jul 26 '19 16:07

Martin Sotirov


2 Answers

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.

like image 189
Stargateur Avatar answered Nov 11 '22 05:11

Stargateur


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

like image 38
Denys Séguret Avatar answered Nov 11 '22 05:11

Denys Séguret