3

I'm doing the rustlings exercises and I tried this to make a capitalize function. But the join part does not work. It says:

"the method join exists for struct Vec<char>, but its trait bounds were not satisfied the following trait bounds were not satisfied: <[char] as Join<_>>::Output = _"

which I don't know what means. What would be the right way to join a char vector?

pub fn capitalize_first(input: &str) -> String {
    let mut c = input.chars();
    match c.next() {
        None => String::new(),
        Some(first) => {
            let upper = first.to_ascii_uppercase();
            let mut v = c.collect::<Vec<char>>();
            v[0] = upper;
            v.join("")
        },
    }
}
Chayim Friedman
  • 47,971
  • 5
  • 48
  • 77
Gabriel Machado
  • 401
  • 3
  • 14
  • Note that this code has also a logic error, since it drops the second character of the original string - https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=5a816255eb701742bb9d2d8f5c30cdbc. – Cerberus Nov 21 '21 at 05:22
  • There is also an option to use [without](https://stackoverflow.com/questions/38406793/why-is-capitalizing-the-first-letter-of-a-string-so-convoluted-in-rust/69996191#69996191) a vector, but you need the solution [with](https://stackoverflow.com/questions/38406793/why-is-capitalizing-the-first-letter-of-a-string-so-convoluted-in-rust) a vector. – Kaplan Nov 21 '21 at 09:53
  • That's true. I did not notice that collecting the iterator would colect the rest of it, the part that was not iterated yet. I thought it would collect it all from the beggining. Thank you – Gabriel Machado Nov 21 '21 at 14:41

3 Answers3

6

To answer your question, the best way to get a string from a vec of chars is to iter and collect:

 let my_string = my_char_vec.iter().collect();

But there are other problems in your code:

  1. you're taking the first char to check the string isn't empty, then you build a string from the rest of the iteration, and you make the first char replace the first char of that string... losing this char which was the second one of the initial str
  2. you're building a useless vec and iterating again. Those are expensive steps that you don't need

You can fix those problems by adapting the code to directly write from the iteration into the string:

pub fn capitalize_first(input: &str) -> String {
    let mut chars = input.chars();
    let mut string = String::new();
    if let Some(first) = chars.next() {
        string.push(first.to_ascii_uppercase());
        for c in chars {
            string.push(c);
        }
    }
    string
}

Note that you're using a function dedicated to ASCII characters. This is fine when you're sure you're only dealing with ASCII but if you want something which works in an international context, you want to use the more general to_uppercase. As an unicode uppercased character may be several characters, the code is a little more complex:

pub fn capitalize_first(input: &str) -> String {
    let mut chars = input.chars();
    let mut string = String::new();
    if let Some(first) = chars.next() {
        let first = first.to_uppercase();
        for c in first {
            string.push(c);
        }
        for c in chars {
            string.push(c);
        }
    }
    string
}

If you're sure you can use to_ascii_upercase, then there's another solution. Because ASCII chars are just one byte in lowercase and uppercase, you can change them in place in the UTF8 string:

pub fn capitalize_first(input: &str) -> String {
    let mut string = input.to_string();
    if !string.is_empty() {
        string[..1].make_ascii_uppercase();
    }
    string
}

This second approach could be used on a mutable string with zero allocation. But it would panic if the first char weren't one byte long.

Denys Séguret
  • 372,613
  • 87
  • 782
  • 758
2

You can just use as_str()

pub fn capitalize_first(input: &str) -> String {
    let mut c = input.chars();
    match c.next() {
        None => String::new(),
        Some(first) => first.to_uppercase().to_string() + c.as_str(),
    }
}
devgony
  • 21
  • 1
  • Neat, I didn't know that the `Chars` iterator has an [`as_str` method](https://doc.rust-lang.org/stable/std/str/struct.Chars.html#method.as_str). – Caesar Dec 07 '22 at 00:21
0

There's a constraint in the return type of the join method that the char type does not meet, since it doesn't have a static lifetime:

pub fn join<Separator>(&self, sep: Separator) -> <Self as Join<Separator>>::Output
    where
        Self: Join<Separator>,
    {
        Join::join(self, sep)
    }

You should use String::from_iter instead:

pub fn capitalize_first(input: &str) -> String {
  let mut c = input.chars();
  match c.next() {
      None => String::new(),
      Some(first) => {
          let upper = first.to_ascii_uppercase();
          let mut v = c.collect::<Vec<char>>();
          v[0] = upper;
          String::from_iter(v)
      },
  }
}
moy2010
  • 854
  • 12
  • 18
  • 2
    The reason the `.join` doesn't work is because joining into a `String` [requires](https://doc.rust-lang.org/std/slice/trait.Join.html#impl-Join%3C%26%27_%20str%3E) elements to implement `Borrow`, which `char` does not. And not because of some static lifetime bound. – kmdreko Nov 21 '21 at 00:31
  • @kmdreko, oh, thanks for the clarification! I misread the error message then. – moy2010 Nov 21 '21 at 00:35