Why is capitalizing the first letter of a string so convoluted in Rust?

StringRustUppercase

String Problem Overview


I'd like to capitalize the first letter of a &str. It's a simple problem and I hope for a simple solution. Intuition tells me to do something like this:

let mut s = "foobar";
s[0] = s[0].to_uppercase();

But &strs can't be indexed like this. The only way I've been able to do it seems overly convoluted. I convert the &str to an iterator, convert the iterator to a vector, upper case the first item in the vector, which creates an iterator, which I index into, creating an Option, which I unwrap to give me the upper-cased first letter. Then I convert the vector into an iterator, which I convert into a String, which I convert to a &str.

let s1 = "foobar";
let mut v: Vec<char> = s1.chars().collect();
v[0] = v[0].to_uppercase().nth(0).unwrap();
let s2: String = v.into_iter().collect();
let s3 = &s2;

Is there an easier way than this, and if so, what? If not, why is Rust designed this way?

Similar question

String Solutions


Solution 1 - String

Why is it so convoluted?

Let's break it down, line-by-line

let s1 = "foobar";

We've created a literal string that is encoded in UTF-8. UTF-8 allows us to encode the 1,114,112 code points of Unicode in a manner that's pretty compact if you come from a region of the world that types in mostly characters found in ASCII, a standard created in 1963. UTF-8 is a variable length encoding, which means that a single code point might take from 1 to 4 bytes. The shorter encodings are reserved for ASCII, but many Kanji take 3 bytes in UTF-8.

let mut v: Vec<char> = s1.chars().collect();

This creates a vector of characters. A character is a 32-bit number that directly maps to a code point. If we started with ASCII-only text, we've quadrupled our memory requirements. If we had a bunch of characters from the astral plane, then maybe we haven't used that much more.

v[0] = v[0].to_uppercase().nth(0).unwrap();

This grabs the first code point and requests that it be converted to an uppercase variant. Unfortunately for those of us who grew up speaking English, there's not always a simple one-to-one mapping of a "small letter" to a "big letter". Side note: we call them upper- and lower-case because one box of letters was above the other box of letters back in the day.

This code will panic when a code point has no corresponding uppercase variant. I'm not sure if those exist, actually. It could also semantically fail when a code point has an uppercase variant that has multiple characters, such as the German ß. Note that ß may never actually be capitalized in The Real World, this is the just example I can always remember and search for. As of 2017-06-29, in fact, the official rules of German spelling have been updated so that both "ẞ" and "SS" are valid capitalizations!

let s2: String = v.into_iter().collect();

Here we convert the characters back into UTF-8 and require a new allocation to store them in, as the original variable was stored in constant memory so as to not take up memory at run time.

let s3 = &s2;

And now we take a reference to that String.

> It's a simple problem

Unfortunately, this is not true. Perhaps we should endeavor to convert the world to Esperanto?

> I presume char::to_uppercase already properly handles Unicode.

Yes, I certainly hope so. Unfortunately, Unicode isn't enough in all cases. Thanks to huon for pointing out the Turkish I, where both the upper (İ) and lower case (i) versions have a dot. That is, there is no one proper capitalization of the letter i; it depends on the locale of the the source text as well.

> why the need for all data type conversions?

Because the data types you are working with are important when you are worried about correctness and performance. A char is 32-bits and a string is UTF-8 encoded. They are different things.

> indexing could return a multi-byte, Unicode character

There may be some mismatched terminology here. A char is a multi-byte Unicode character.

Slicing a string is possible if you go byte-by-byte, but the standard library will panic if you are not on a character boundary.

One of the reasons that indexing a string to get a character was never implemented is because so many people misuse strings as arrays of ASCII characters. Indexing a string to set a character could never be efficient - you'd have to be able to replace 1-4 bytes with a value that is also 1-4 bytes, causing the rest of the string to bounce around quite a lot.

> to_uppercase could return an upper case character

As mentioned above, ß is a single character that, when capitalized, becomes two characters.

Solutions

See also trentcl's answer which only uppercases ASCII characters.

Original

If I had to write the code, it'd look like:

fn some_kind_of_uppercase_first_letter(s: &str) -> String {
    let mut c = s.chars();
    match c.next() {
        None => String::new(),
        Some(f) => f.to_uppercase().chain(c).collect(),
    }
}

fn main() {
    println!("{}", some_kind_of_uppercase_first_letter("joe"));
    println!("{}", some_kind_of_uppercase_first_letter("jill"));
    println!("{}", some_kind_of_uppercase_first_letter("von Hagen"));
    println!("{}", some_kind_of_uppercase_first_letter("ß"));
}

But I'd probably search for uppercase or unicode on crates.io and let someone smarter than me handle it.

Improved

Speaking of "someone smarter than me", Veedrac points out that it's probably more efficient to convert the iterator back into a slice after the first capital codepoints are accessed. This allows for a memcpy of the rest of the bytes.

fn some_kind_of_uppercase_first_letter(s: &str) -> String {
    let mut c = s.chars();
    match c.next() {
        None => String::new(),
        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
    }
}

Solution 2 - String

>Is there an easier way than this, and if so, what? If not, why is Rust designed this way?

Well, yes and no. Your code is, as the other answer pointed out, not correct, and will panic if you give it something like བོད་སྐད་ལ་. So doing this with Rust's standard library is even harder than you initially thought.

However, Rust is designed to encourage code reuse and make bringing in libraries easy. So the idiomatic way to capitalize a string is actually quite palatable:

extern crate inflector;
use inflector::Inflector;

let capitalized = "some string".to_title_case();

Solution 3 - String

It's not especially convoluted if you are able to limit your input to ASCII-only strings.

Since Rust 1.23, str has a make_ascii_uppercase method (in older Rust versions, it was available through the AsciiExt trait). This means you can uppercase ASCII-only string slices with relative ease:

fn make_ascii_titlecase(s: &mut str) {
    if let Some(r) = s.get_mut(0..1) {
        r.make_ascii_uppercase();
    }
}

This will turn "taylor" into "Taylor", but it won't turn "édouard" into "Édouard". (playground)

Use with caution.

Solution 4 - String

I did it this way:

fn str_cap(s: &str) -> String {
  format!("{}{}", (&s[..1].to_string()).to_uppercase(), &s[1..])
}

If it is not an ASCII string:

fn str_cap(s: &str) -> String {
  format!("{}{}", s.chars().next().unwrap().to_uppercase(), 
  s.chars().skip(1).collect::<String>())
}

Solution 5 - String

Since the method to_uppercase() returns a new string, you should be able to just add the remainder of the string like so.

this was tested in rust version 1.57+ but is likely to work in any version that supports slice.

fn uppercase_first_letter(s: &str) -> String {
        s[0..1].to_uppercase() + &s[1..]
    }

Solution 6 - String

Here's a version that is a bit slower than @Shepmaster's improved version, but also more idiomatic:

fn capitalize_first(s: &str) -> String {
    let mut chars = s.chars();
    chars
        .next()
        .map(|first_letter| first_letter.to_uppercase())
        .into_iter()
        .flatten()
        .chain(chars)
        .collect()
}

Solution 7 - String

This is how I solved this problem, notice I had to check if self is not ascii before transforming to uppercase.

trait TitleCase {
    fn title(&self) -> String;
}

impl TitleCase for &str {
    fn title(&self) -> String {
        if !self.is_ascii() || self.is_empty() {
        	return String::from(*self);
        }
        let (head, tail) = self.split_at(1);
        head.to_uppercase() + tail
    }
}

pub fn main() {
    println!("{}", "bruno".title());
    println!("{}", "b".title());
    println!("{}", "🦀".title());
    println!("{}", "ß".title());
    println!("{}", "".title());
    println!("{}", "བོད་སྐད་ལ".title());
}

Output

Bruno
B
🦀
ß

བོད་སྐད་ལ 

Solution 8 - String

The OP's approach taken further:
replace the first character with its uppercase representation

let mut s = "foobar".to_string();
let r = s.remove(0).to_uppercase().to_string() + &s;
println!("{}", r);

works with Unicode characters as well eg. "😎foobar"

Solution 9 - String

Inspired by get_mut examples I code something like this:

fn make_capital(in_str : &str) -> String {
    let mut v = String::from(in_str);
    v.get_mut(0..1).map(|s| { s.make_ascii_uppercase(); &*s });

    v
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionmarshallmView Question on Stackoverflow
Solution 1 - StringShepmasterView Answer on Stackoverflow
Solution 2 - Stringuser8174234View Answer on Stackoverflow
Solution 3 - StringtrentView Answer on Stackoverflow
Solution 4 - StringNikolai LasunovView Answer on Stackoverflow
Solution 5 - StringMartin SmithView Answer on Stackoverflow
Solution 6 - StringyuyoyuppeView Answer on Stackoverflow
Solution 7 - StringBruno Rocha - rochacbrunoView Answer on Stackoverflow
Solution 8 - StringKaplanView Answer on Stackoverflow
Solution 9 - StringValentin RomanovView Answer on Stackoverflow