40

I just want a space separated String of the argument variables obtained from std::env::args(), which I've been creating using the fold function like this:

std::env::args()
    .fold("".to_string(), |accum, s| accum + &s + " ")

However this creates an extra space at the end which is unwanted. I tried using the truncate function, but truncate doesn't return a String, just modifies the existing String, and also this would require the creation of an intermediate binding in order to use the String's len() call to define how long the truncated String should be (which itself would require an intermediate binding due to Rust's current lexical borrowing rules I believe!)

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Harvey Adcock
  • 929
  • 1
  • 7
  • 16
  • I believe if you change the lambda expression to `accum + " " + &s`, the trailing space will disappear. – Ferruccio Apr 29 '16 at 15:13
  • 1
    @Ferruccio This just results in a leading space, I only did it the way around I did because a trailing space is often invisible whereas a leading one can be seen in the formatting. I should have specified leading and trailing spaces are unwanted though. – Harvey Adcock Apr 29 '16 at 15:14
  • 3
    There's an [`intersperse`](https://bluss.github.io/rust-itertools/doc/itertools/trait.Itertools.html#method.intersperse) method in `itertools` if you're willing to add a dependency. I don't think there's a straightforward way to do this with `std`. – Dogbert Apr 29 '16 at 15:18
  • @Dogbert So with `itertools` it looks like you would call `intersperse` on the iterator and then `fold` that iterator into the final `String` by just having `accum + &s` in the fold closure? Seems simple enough, would be nice if something similar to this existed in std though. – Harvey Adcock Apr 29 '16 at 15:29
  • 1
    Yes, of course, I should have realized that would happen. I've run into this problem before (using C#). You need to check if `accum` is empty. I'm not sure if this is valid Rust syntax, but I normally do something like: `accum + (if accum == "" {""} else {" "}) + &s` – Ferruccio Apr 29 '16 at 15:32
  • 2
    @Ferruccio Yes that would work, slightly messier looking closure but nothing too dreadful. Just a note for anyone choosing the `itertools` approach though - there's a `join` function in `itertools` which joins all elements into a single `String` separated by a specified separator. – Harvey Adcock Apr 29 '16 at 15:46
  • @HarveyAdcock you can actually just call `.collect()` on an iterator of `String` and get back a single `String`. But yeah, `join` is even better for you that I completely missed. – Dogbert Apr 29 '16 at 16:06
  • 5
    Why all this talk of intersperse in itertools (I see it often)? Itertools has actual methods for formatting, use [`.join(" ")` or `.format_default(" ")`](http://bluss.github.io/rust-itertools/doc/itertools/trait.Itertools.html#method.join) – bluss Apr 29 '16 at 23:39

4 Answers4

29

What's an idiomatic way to print all command line arguments in Rust?

fn main() {
    let mut args = std::env::args();

    if let Some(arg) = args.next() {
        print!("{}", arg);

        for arg in args {
            print!(" {}", arg);
        }
    }
}

Or better with Itertools' format or format_with:

use itertools::Itertools; // 0.8.0

fn main() {
    println!("{}", std::env::args().format(" "));
}

I just want a space separated String

fn args() -> String {
    let mut result = String::new();
    let mut args = std::env::args();

    if let Some(arg) = args.next() {
        result.push_str(&arg);

        for arg in args {
            result.push(' ');
            result.push_str(&arg);
        }
    }

    result
}

fn main() {
    println!("{}", args());
}

Or

fn args() -> String {
    let mut result = std::env::args().fold(String::new(), |s, arg| s + &arg + " ");
    result.pop();
    result
}

fn main() {
    println!("{}", args());
}

If you use Itertools, you can use the format / format_with examples above with the format! macro.

join is also useful:

use itertools::Itertools; // 0.8.0

fn args() -> String {
    std::env::args().join(" ")
}

fn main() {
    println!("{}", args());
}

In other cases, you may want to use intersperse:

use itertools::Itertools; // 0.8.0

fn args() -> String {
    std::env::args().intersperse(" ".to_string()).collect()
}

fn main() {
    println!("{}", args());
}

Note this isn't as efficient as other choices as a String is cloned for each iteration.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 3
    If you put the `for` loop inside the `if let`, you do not need to use `fuse`. – malbarbo Apr 29 '16 at 19:59
  • 3
    With `itertools`, you can just do `std::env::args().join(" ")` (thanks to OP and @bluss for the tip in the question comments). – Dogbert Apr 30 '16 at 07:57
  • For the `intersperse` example, couldn't you just call `.collect::()` instead of folding? – Addison May 30 '19 at 19:03
  • @Addison yep! I'm guessing I copy-pasted from the earlier example with `fold` and didn't apply critical thought. – Shepmaster May 30 '19 at 19:17
5

Here's a solution with iterators, and zero non-stdlib dependencies

(performance wise, this shines in release mode, with optimizations turned on [playground])

use std::iter::once;

fn join_iter<T>(
    mut iter: impl Iterator<Item = T>,
    sep: impl Fn(&T) -> T,
) -> impl Iterator<Item = T> {
    iter.next()
        .into_iter()
        .chain(iter.flat_map(move |s| once(sep(&s)).chain(once(s))))
}

fn examples() {
    let iter = [1, 3, 5, 7, 9].iter().cloned();
    println!("{:?}", join_iter(iter, |v| v - 1).collect::<Vec<_>>());
    // [1, 2, 3, 4, 5, 6, 7, 8, 9]

    let iter = ["Hello", "World"].iter().cloned();
    let sep = ", ";
    println!("{:?}", join_iter(iter, |_| sep).collect::<String>());
    // "Hello, World"
}

fn args() -> String {
    join_iter(std::env::args(), |_| " ".to_string()).collect()
}

fn main() {
    examples();

    println!("{}", args());
}
Miraclx
  • 371
  • 3
  • 8
3

Here is an alternative for

I just want a space separated String of the argument variables obtained from std::env::args()

fn args_string() -> String {
    let mut out = String::new();
    let mut sep = "";
    for arg in std::env::args() {
        out.push_str(sep);
        out.push_str(&*arg);
        sep = " ";
    }
    out
}

pub fn main() {
    println!("{}", args_string());
}
malbarbo
  • 10,717
  • 1
  • 42
  • 57
2

Here is a version using fold

fn join<I, T, D>(iter: I, separator: D) -> String
where
    T: Display,
    D: Display,
    I: Iterator<Item = T>,
{
    match iter.fold(None, |a:Option<String>, b| {
        Some(match a {
            None => format!("{}", &b),
            Some(mut a) => {
                write!(a, "{}{}", separator, b).unwrap();
                a
            }
        })
    }) {
        None => String::new(),
        Some(rval) => rval,
    }
}
Mutant Bob
  • 3,121
  • 2
  • 27
  • 52