Using .join() on a Vec<&MyStruct>

I have a Vec of my custom type, which I will call Name. The type can be represented compactly as a String, and I would like to be able to call .join() on a Vec of this type. This actually works when I implement Borrow<str> for my type.

But during tests, I also have Vecs containing references to my type so that I can concisely re-use variables. While .join(", ") works with Vec<Name>, trying to use Vec<&Name> instead gives me an error:

   Compiling playground v0.0.1 (/playground)
error[E0599]: the method `join` exists for struct `Vec<&Name>`, but its trait bounds were not satisfied
  --> src/main.rs:43:39
   |
45 |     println!("Expected: {}", expected.join(", "));
   |                                       ^^^^ method cannot be called on `Vec<&Name>` due to unsatisfied trait bounds
   |
   = note: the following trait bounds were not satisfied:
           `[&Name]: Join<_>`

For more information about this error, try `rustc --explain E0599`.
error: could not compile `playground` (bin "playground") due to 1 previous error

Here's a Rust Playground.

If there's a better way to handle re-using the instances in the tests so that I can have Vecs of owned types, then I'm all ears. But otherwise, is there a succinct way to get join() working on a Vec of references? ChatGPT wants me to map the items to a new Vec<String> using to_string() on each and then join() that, which does work… but feels much clunkier than the concise .join() I'm hoping for.

Thanks in advance!

1 Like

See implementors of the Join trait:

When you join() with &str, it picks this implementation:

impl<S> Join<&str> for [S]
where
    S: Borrow<str>,
type Output = String

the type in your slice (Vec<S> is Derefed as [S]) needs to implement Borrow<str>.

In std there's a blanket impl: impl<T> Borrow<T> for &T, but that's not actually applicable for supporting &Name from Name, because the T here is str, not Name.

So you need separate impl Borrow<str> for &Name too.

use std::borrow::Borrow;

struct Name;
impl Borrow<str> for Name {
    fn borrow(&self) -> &str { "name" }
}
impl Borrow<str> for &Name {
    fn borrow(&self) -> &str { "name" }
}

fn main() {
    let x = [Name, Name];
    x.join(",");
    
    let xr = x.each_ref();
    xr.join(";");
}
2 Likes

That's great, I had no idea that you could use the reference operator in the for part of an impl definition like that. TYVM!

Yeah, impl for in Rust isn't like class implementing one type, but more like an SQL query that selects which types support what. The right side of for can be almost anything you want:

impl<T, U, Z> MyTrait 
    for Result<T, U> 
    where T: AsRef<str> + Clone, U: std::ops::Add<i32, Output=Vec<Z>> {

it implements MyTrait for any Result type that has some Ok type parameter that is string-like and can be cloned, and an Err type that when added to an integer, must produce a Vec of something. Such types may or may not exist.

1 Like