I know I could use iter-map-to_string-collect to get a Vec of Strings, but that's annoying and also creating all these temporary Strings is inefficient.
But all numbers implement Display, so we know they can be written into a String.
Why can't join work for any Vec<T> where T: Display? Thanks!
Alternatively, use itertools::join. It will create a String, but you were already doing that with the joins you had that worked.
The stdjoin works on slices generally, resulting in a String or a Vec,[1] and it's designed in such a way that you can only get a String if you have a slice of something that can be Borrowed as a &str. It's not based on Display like the itertools method and my approach are.
You can't borrow a &str from an i32, so it's the wrong tool for this job.
Thanks for the reply. Yes, I can see that join doesn't work for Display types in general, so I'm wondering why not and if there is hope it will some day.
Joining numbers into a single string seems a very common problem, and I believe most programming languages have a standard function for it.
On the other hand, based on your reply, I'm starting to think the better solution would be one that can avoid allocation, for example, if println!, print!, writeln!, write!, and format! could be extended to accept Vecs and Iters of Display types and some separator. Something like
println!("{sep=","}", vec![1,2,3])
And thanks for the code example. I didn't know slices can be destructured this way, and it is very useful in this case.
Here's a slightly less dirty version. The join_display method can be called on collections as long as a reference to the collection can be turned into an iterator. If you already have an iterator that's cheap to clone (most reference iterators are), then you can use new. If you actually want it to consume the iterator, then you'll need to use interior mutability since fmt takes &self.
use core::fmt::{self, Display};
fn main() {
println!("{}", vec![1, 2, 3].join_display(","));
}
pub struct JoinDisplay<I, D> {
iter: I,
delimiter: D,
}
impl<I, D> JoinDisplay<I, D> {
pub fn new(iter: I, delimiter: D) -> Self {
Self { iter, delimiter }
}
}
impl<I, D> Display for JoinDisplay<I, D>
where
I: IntoIterator<Item: Display> + Clone,
D: Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut iter = self.iter.clone().into_iter();
let Some(first) = iter.next() else {
return Ok(());
};
write!(f, "{first}")?;
for item in iter {
write!(f, "{}{item}", self.delimiter)?;
}
Ok(())
}
}
pub trait JoinDisplayExt: Sized {
fn join_display<D>(self, delimiter: D) -> JoinDisplay<Self, D>;
}
impl<'a, I> JoinDisplayExt for &'a I
where
&'a I: IntoIterator<Item: Display>,
{
fn join_display<D>(self, delimiter: D) -> JoinDisplay<Self, D> {
JoinDisplay::new(self, delimiter)
}
}
The implementation of Join that allows calling join(","), which produces a String, looks like:
impl<S> Join<&str> for [S]
where
S: Borrow<str>,
type Output = String
Now say I have a type Foo that implements Borrow<str> but does not implement Display. .join(",") on a Vec<Foo> will work and produce a String. But if they changed the bounds on this implementation to S: Display, it would stop working -- a breaking change.
It would also change the behavior of types that implement both Borrow<str> and Display, but display differently than they borrow.
So it's probably not possible to make .join(",") work for all display types without specialization.
They could make an implementation for Join<&dyn Display> and then you could call vec.join(&"," as &dyn Display), but I doubt that would be considered an acceptable API.
So if std gets a join more like itertools has, it will probably be named something else, if it's a method. (There could be a standalone std::str::join function I suppose.)
If the goal isn't to get a string but to write it out somewhere else, it's much better to do this, yes. Making types that implementDebug/Display/whatever, like @drewtato's example, is a great pattern -- you'll see it in the standard library for various types too, where they don't directly implement Display but there are methods that return proxy types for particular ways of showing them.
And you can always .to_string() something that implements Display if you do need exactly a String for some reason.