22

I'm trying to convert a HashSet<String> into a sorted vector that can then be joined with commas:

use std::collections::HashSet;

fn main() {
    let mut hs = HashSet::<String>::new();
    hs.insert(String::from("fee"));
    hs.insert(String::from("fie"));
    hs.insert(String::from("foo"));
    hs.insert(String::from("fum"));

    let mut v: Vec<&String> = hs.iter().collect();
    v.sort();

    println!("{}", v.join(", "));
}

This will not compile:

error[E0599]: no method named `join` found for struct `std::vec::Vec<&std::string::String>` in the current scope
  --> src/main.rs:13:22
   |
13 |     println!("{}", v.join(", "));
   |                      ^^^^ method not found in `std::vec::Vec<&std::string::String>`

I understand why I can't join the Vec<&String>, but how can I convert the HashSet to a Vec<String> instead, so it can be joined?

The examples given in What's an idiomatic way to print an iterator separated by spaces in Rust? do not seem to apply because the iterator for Args returns String values, unlike the iterator for HashSet which returns &String.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Ralph
  • 31,584
  • 38
  • 145
  • 282
  • 1
    Does this answer your question? [What's an idiomatic way to print an iterator separated by spaces in Rust?](https://stackoverflow.com/questions/36941851/whats-an-idiomatic-way-to-print-an-iterator-separated-by-spaces-in-rust) – E_net4 Mar 27 '20 at 19:45
  • 1
    You could call `into_iter` to take ownership of the values. – squiguy Mar 27 '20 at 19:50
  • *the iterator for `HashSet` which returns `&String`* — one of the many iterators of `HashSet` does that, yes. Others do not. You also can format a `&String` just fine. – Shepmaster Mar 27 '20 at 20:17

2 Answers2

36

I encourage you to re-read The Rust Programming Language, specifically the chapter on iterators. Next, become familiar with the methods of Iterator.

The normal way I'd expect to see this implemented is to convert the HashSet to an iterator and then collect the iterator to a Vec:

let mut v: Vec<_> = hs.into_iter().collect();

In this case, I'd prefer to use FromIterator directly (the same trait that powers collect):

let mut v = Vec::from_iter(hs);

Focusing on your larger problem, use a BTreeSet instead, coupled with What's an idiomatic way to print an iterator separated by spaces in Rust?

use itertools::Itertools; // 0.10.1
use std::collections::BTreeSet;

fn main() {
    // Create the set somehow
    let hs: BTreeSet<_> = ["fee", "fie", "foo", "fum"]
        .into_iter()
        .map(String::from)
        .collect();

    println!("{}", hs.iter().format(", "));
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
  • 1
    I'm part-way through my first reading of _The Rust Programming Language_. I figured out how to make it work by changing to `HashSet<&'static str>`. After that change, the `join` worked. In my case, using `&'static str` is fine since the HashSet contains only string literals. – Ralph Mar 27 '20 at 21:21
  • How come this `Vec::from_iter(hs)` works, when `hs` is a HashSet (and not an iterator)? – MEMark Aug 05 '22 at 21:25
  • 1
    @MEMark review the definition of `FromItetator` and you’ll see it accepts any value that can be *turned into* an iterator, not that is only already an iterator. – Shepmaster Aug 05 '22 at 22:20
1

There's a straightforward way to convert a HashSet of Strings into a Vec of Strings using the turbofish (::<>):

let result_vec = result_set.into_iter().collect::<Vec<_>>();
dimo414
  • 47,227
  • 18
  • 148
  • 244
beaknit
  • 11
  • 1
  • 2
    Specifying the type on both the variable _and_ via the turbofish is redundant. Specifying the type inside the collection is also redundant. I'd write it as `let result_vec = result_set.into_iter().collect::>()`. – Shepmaster Nov 24 '21 at 16:37