I often see the need to handle "items with separators". Some are so common that we have specific functions like join() and intersperse, but there is no "generic" functional pattern. I even asked about it on stackoverflow, but no good solutions were given, so I would like to propose a new function:
let items = vec!["Hello", "World", "!"];
items.foreach_with_separator(
|elem| print!("{elem}"), // This is called on each element
|| print!(", "), // This is called "between" each element
);
This approach allows arbitrary complex element handling, as well as any kind of separation logic, without duplicating element handling -- I often see it duplicated for the first item and for subsequent ones (or all_items + last item). This will also avoid the need to maintain the boolean state of "is this the first element or not".
The separator callback might benefit from a few additional params:
- index parameter counting separator callback invocations -
|_idx: usize| print!(", "),
- neighboring elements, e.g.
|_elem_before: &T, _elem_after: &T| print!(", "),
- all 3 params
|_idx, _elem_before, _elem_after| print!(", "),
Even more generically, we may want to introduce an accumulator version (this example re-implements the join()
function):
let items = vec!["Hello", "World", "!"];
let result = items.fold_with_separator(
String::new(),
|accumulator, elem| accumulator.append(elem),
|accumulator| accumulator.append(", ")
);
Alternatives
intersperse allows a similar but more convoluted/verbose way to do this, and only work for Clone-able items. In this example, it converts values to Options, with None
being the separator, thus it might bloat the binary if T::clone()
is not used anywhere else, and compiler doesn't realize it will never get called in this code. Note that this function has also been implemented as unstable in the stdlib.
let items = vec!["foo", "bar", "baz"];
for v in items.iter().map(|v| Some(v)).intersperse(None) {
if let Some(v) = v {
print!("{v}")
} else {
print!(", ")
}
}
enumerate()
offers a more classical way to do this:
let items = vec!["foo", "bar", "baz"];
for (idx, v) in items.iter().enumerate() {
if idx > 0 {
print!(", ")
}
print!("{v}")
}