Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Concisely initializing a vector of Strings

Tags:

rust

I'm trying to create a vector of Strings to test arg parsing (since this is what std::env::args() returns) but struggling with how to do this concisely.

What I want:

let test_args = vec!["-w", "60", "arg"]; // should array of Strings
let expected_results = my_arg_parser(test_args);

This obviously doesn't work because the vectors contents are all &strs.

Using String::from but works but doesn't scale well and is ugly :)

let args = vec![String::from("-w"), String::from("60"), String::from("args")];

I could map over the references and return string objects, but this seems very verbose:

let args = vec!["-w", "60", "args"].iter().map(|x| x.to_string()).collect::<Vec<String>>();

Should I just create a helper function to do the conversion, or is there an easier way?

like image 280
Nick Tomlin Avatar asked Jul 04 '16 11:07

Nick Tomlin


People also ask

How do you initialize a vector in Rust?

Using Vec::new() Method: let v : Vec<i64> = Vec::new(); Here v is the initialized vector that will contain the 64-bit integer datatype. It is initialized with help of the Vec::new() method.

Is it possible to initialize any vector with an array in C++?

C++11 also support std::begin and std::end for array, so a vector can also be initialized like static const int arr[] = {10,20,30}; vector<int> vec(begin(arr), end(arr)); .


3 Answers

You can use the to_string() method directly on the literals:

let test_args = vec!["-w".to_string(), "60".to_string(), "arg".to_string()];

Otherwise a macro to do this would be as simple as:

macro_rules! vec_of_strings {
    ($($x:expr),*) => (vec![$($x.to_string()),*]);
}

See play.rust.org example

like image 108
JDemler Avatar answered Oct 24 '22 04:10

JDemler


JDemler already provided a nice answer. I have two additional things to say:

First, you can also use into() instead of to_string() for all elements but the first. This is slightly shorter and also equivalent to to_string()/String::from(). Looks like this:

vec!["a".to_string(), "b".into(), "c".into()];

Second, you might want to redesign your arg parsing. I will assume here that you won't mutate the Strings you get from env::args(). I imagine your current function to look like:

fn parse_args(args: &[String]) -> SomeResult { ... }

But you can make that function more generic by not accepting Strings but AsRef<str>. It would look like this:

fn parse_args<T: AsRef<str>>(args: &[T]) -> SomeResult { ... }

In the documentation you can see that String as well as str itself implement that trait. Therefore you can pass a &[String] and a &[&str] into your function. Awesome, eh?


In similar fashion, if you want to accept anything that can be converted into an owned String, you can accept <T: Into<String>> and if you want to return either a String or an &str, you can use Cow. You can read more about that here and here.

Apart from all that: there are plenty of good CLI-Arg parsers out there (clap-rs, docopt-rs, ...), so you might not need to write your own.

like image 35
Lukas Kalbertodt Avatar answered Oct 24 '22 03:10

Lukas Kalbertodt


I agree that Lukas Kalbertodt's answer is the best — use generics to accept anything that can look like a slice of strings.

However, you can clean up the map version a little bit:

  1. There's no need to allocate a vector for the initial set of strings.
  2. There's no need to use the complete type (Vec<String>); you could specify just the collection (Vec<_>). If you pass the result to a function that only accepts a Vec<String>, then you don't need any explicit types at all; it can be completely inferred.
  3. You can use a slightly shorter s.into() in the map.

fn do_stuff_with_args(args: Vec<String>) { println!("{}", args.len()) }

fn main() {
    let args = ["-w", "60", "args"].iter().map(|&s| s.into()).collect();
    do_stuff_with_args(args);
}
like image 11
Shepmaster Avatar answered Oct 24 '22 03:10

Shepmaster