Reduce vs Fold for String Concatenation in Rust

Viewed 1212

Since 1.51, Rust has included reduce, which I'm used to from Scala. fold works like foldLeft in Scala, but reduce is different. What am I getting wrong?

This works beautifully:

let ss = vec!["a", "b", "c"].iter()
.fold("".to_string(), |cur, nxt| cur + nxt);

println!("{}", ss);

This does not:

let ss = vec!["a", "b", "c"].iter()
.reduce(|cur, nxt| cur + nxt);

println!("{}", ss);

Errors:

error[E0369]: cannot add `&&str` to `&&str`
 --> src/main.rs:3:28
  |
3 |     .reduce(|cur, nxt| cur + nxt);
  |                        --- ^ --- &&str
  |                        |
  |                        &&str

error[E0277]: `Option<&&str>` doesn't implement `std::fmt::Display`
 --> src/main.rs:5:20
  |
5 |     println!("{}", ss);
  |                    ^^ `Option<&&str>` cannot be formatted with the default formatter
  |
  = help: the trait `std::fmt::Display` is not implemented for `Option<&&str>`
  = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
  = note: required by `std::fmt::Display::fmt`
  = note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info)

(Playground)

What am I doing wrong?

1 Answers

In the first one you're starting with "".to_string() which is an owned String. String implements Add<&'_ str> which lets you concatenate other borrowed &str strings with the + operator.

Writing the parameter types out lets you see the difference between cur and nxt clearly:

let ss = vec!["a", "b", "c"].iter()
    .fold("".to_string(), |cur: String, nxt: &&str| cur + nxt);

The reduce() call, on the other hand, works purely with &strs, but &str doesn't support concatenation with +. There's no Add impl.

let ss = vec!["a", "b", "c"].iter()
    .reduce(|cur: &str, nxt: &str| cur + nxt); // &str + &str not defined

While &str doesn't support concatenation, String does, because it can do it efficiently. a + b consumes a, re-using its buffer for the result String. It doesn't have to allocate O(a.len()) space nor spend O(a.len()) time copying the existing contents of a. b is simply appended in-place to a's buffer.

Therefore, if you convert the borrowed &strs to owned Strings it compiles:

let ss = vec!["a", "b", "c"].iter()
    .map(|s| s.to_string())
    .reduce(|cur: String, nxt: String| cur + &nxt)
    .unwrap();

Notice also that reduce() returns an Option<String> which needs to be unwrapped.

Related