12

I want to join a list of numbers into a String. I have the following code:

let ids: Vec<i32> = Vec::new();
ids.push(1);
ids.push(2);
ids.push(3);
ids.push(4);
let joined = ids.join(",");
print!("{}", joined);

However, I get the following compilation error:

error[E0599]: no method named `join` found for struct `std::vec::Vec<i32>` in the current scope
  --> src\data\words.rs:95:22
   |
95 |     let joined = ids.join(",");
   |                      ^^^^ method not found in `std::vec::Vec<i32>`
   |
   = note: the method `join` exists but the following trait bounds were not satisfied:
           `<[i32] as std::slice::Join<_>>::Output = _`

I'm a bit unclear as to what to do. I understand the implementation of traits, but whatever trait it's expecting, I would expect to be natively implemented for i32. I would expect joining integers into a string to be more trivial than this. Should I cast all of them to Strings first?

EDIT: It's not the same as the linked question, because here I am specifically asking about numbers not being directly "joinable", and the reason for the trait to not be implemented by the number type. I looked fairly hard for something in this direction and found nothing, which is why I asked this question.

Also, it's more likely that someone will search specifically for a question phrased like this instead of the more general "idiomatic printing of iterator values".

Daniel Gray
  • 1,697
  • 1
  • 21
  • 41
  • 2
    I also think the linked question is not quite a duplicate. I believe there is a substantial difference between creating a string by joining `Vec` and *printing* something like that. – at54321 Dec 21 '21 at 12:51

4 Answers4

7

If you don't want to explicitly convert into string, then you can use Itertools::join method (this is an external crate though)

PlayGround

Relevant code:

use itertools::Itertools;

let mut ids: Vec<i32> = ...;
let joined = Itertools::join(&mut ids.iter(), ",");
print!("{}", joined);

Frxstrem suggestion:

let joined = ids.iter().join(".");
timthelion
  • 2,636
  • 2
  • 21
  • 30
apatniv
  • 1,771
  • 10
  • 13
  • 3
    Since `Itertools` is a trait, you can also write `ids.iter().join(",")`. Seems a bit cleaner to me. ([Modified playground example](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f5b66c9a3ac4abe7483c2f37d5ad0b57)) – Frxstrem Apr 04 '20 at 19:11
  • 1
    Generally, I prefer the functional style. – apatniv Apr 04 '20 at 22:49
  • 1
    Please don't edit answers to change them. Create your own answer and earn jucy jucy karma! Editing is to remove errors/typos, not for changing answers. Thank you – timthelion Dec 12 '21 at 18:28
6

I would do

let ids = vec!(1,2,3,4);
let joined: String = ids.iter().map( |&id| id.to_string() + ",").collect(); 
print!("{}", joined);

Generally when you have a collection of one type in Rust, and want to turn it to another type, you call .iter().map(...) on it. The advantage of this method is you keep your ids as integers which is nice, have no mutable state, and don't need an extra library. Also if you want a more complex transformation than just a casting, this is a very good method. The disadvantage is you have a trailing comma in joined. playground link

David Sullivan
  • 462
  • 7
  • 16
  • 2
    I'd probably map a `.to_string()` and then call `.join()`, but thanks for your input! – Daniel Gray Apr 06 '20 at 08:39
  • 1
    You can use `std::write()!` to avoid that temporary heap allocation (id.to_string()). [Playground link](https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=b357054d9b340fdb7504e4e5f192be81) – Steve Lau Sep 25 '22 at 11:16
  • 1
    This won't give you the answer you expect as it will have a trailing separator. e.g. `vec![1, 2, 3, 4]` will become `1,2,3,4,` – Michael Hall Jun 13 '23 at 06:13
4

Using the [T]::join() method requires that [T] implements the Join trait. The Join trait is only implemented for [T] where T implements Borrow<str> (like String or &str) or Borrow<[U]> (like &[U] or Vec<U>). In other words, you can only join a vector of strings or a vector of slices/vectors.

In general, Rust requires you to be very explicit about type conversion, so in many cases you shouldn't expect the language to e.g. automatically convert an integer to a string for you.

To solve your problem, you need to explicitly convert your integers into strings before pushing them into your vector:

let mut ids: Vec<String> = Vec::new();
ids.push(1.to_string());
ids.push(2.to_string());
ids.push(3.to_string());
ids.push(4.to_string());
let joined = ids.join(",");
print!("{}", joined);

Playground example

Frxstrem
  • 38,761
  • 9
  • 79
  • 119
1

If you want a generic solution:

fn join<I, T>(it: I, sep: &str) -> String
where
    I: IntoIterator<Item = T>,
    T: std::fmt::Display,
{
    use std::fmt::Write;

    let mut it = it.into_iter();
    let first = it.next().map(|f| f.to_string()).unwrap_or_default();

    it.fold(first, |mut acc, s| {
        write!(acc, "{}{}", sep, s).expect("Writing in a String shouldn't fail");
        acc
    })
}

fn main() {
    assert_eq!(join(Vec::<i32>::new(), ", "), "");
    assert_eq!(join(vec![1], ", "), "1");
    assert_eq!(join(vec![1, 2, 3, 4], ", "), "1, 2, 3, 4");
}

Maybe this implement

If you prefer that style, you can use an extension method:

trait JoinIterator {
    fn join(self, sep: &str) -> String;
}

impl<I, T> JoinIterator for I
where
    I: IntoIterator<Item = T>,
    T: std::fmt::Display,
{
    fn join(self, sep: &str) -> String {
        use std::fmt::Write;

        let mut it = self.into_iter();
        let first = it.next().map(|f| f.to_string()).unwrap_or_default();

        it.fold(first, |mut acc, s| {
            write!(acc, "{}{}", sep, s).expect("Writing in a String shouldn't fail");
            acc
        })

    }
}

fn main() {
    assert_eq!(Vec::<i32>::new().join(", "), "");
    assert_eq!(vec![1].join(", "), "1");
    assert_eq!(vec![1, 2, 3, 4].join(", "), "1, 2, 3, 4");
}
Boiethios
  • 38,438
  • 19
  • 134
  • 183
  • Thank you for the idea! I think I am not a fan of the repeated string allocations and copying. I would hope that the `String::join` method only does one big allocation, but I haven't checked. In any case, it's good to keep this alternative in mind. Thanks a lot! – Daniel Gray Apr 07 '20 at 11:04
  • @DanielGray You're right, I've updated my code. – Boiethios Apr 07 '20 at 13:25