ExamplesBy LevelBy TopicLearning Paths
497 Fundamental

String Case Conversion

Functional Programming

Tutorial

The Problem

Case conversion appears in: code generation (struct names in camelCase, field names in snake_case), API normalisation (HTTP headers are case-insensitive), URL slugs (lowercase-with-hyphens), and display formatting (title case for headings). str::to_uppercase handles the simple case but does not perform format conversion between naming conventions. These require splitting on boundaries (_, uppercase chars, spaces) and reassembling with different rules.

🎯 Learning Outcomes

  • • Use .to_uppercase() and .to_lowercase() for Unicode-correct case conversion
  • • Implement snake_case conversion by inserting _ before uppercase chars
  • • Implement camelCase by capitalising the first char of each _-delimited word
  • • Implement Title Case by capitalising the first char of each whitespace-delimited word
  • • Apply char_indices, flat_map, and split for case transformation pipelines
  • Code Example

    #![allow(clippy::all)]
    // 497. Case conversion patterns
    fn to_snake_case(s: &str) -> String {
        let mut out = String::new();
        for (i, c) in s.chars().enumerate() {
            if c.is_uppercase() {
                if i > 0 {
                    out.push('_');
                }
                out.extend(c.to_lowercase());
            } else {
                out.push(c);
            }
        }
        out
    }
    
    fn to_camel_case(s: &str) -> String {
        s.split('_')
            .enumerate()
            .flat_map(|(i, word)| {
                let mut chars = word.chars();
                if i == 0 {
                    chars.map(|c| c).collect::<String>()
                } else {
                    let first = chars
                        .next()
                        .map(|c| c.to_uppercase().to_string())
                        .unwrap_or_default();
                    let rest: String = chars.collect();
                    first + &rest
                }
                .chars()
                .collect::<Vec<_>>()
            })
            .collect()
    }
    
    fn to_title_case(s: &str) -> String {
        s.split_whitespace()
            .map(|word| {
                let mut cs = word.chars();
                cs.next()
                    .map(|c| c.to_uppercase().collect::<String>() + cs.as_str())
                    .unwrap_or_default()
            })
            .collect::<Vec<_>>()
            .join(" ")
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_upper() {
            assert_eq!("hello".to_uppercase(), "HELLO");
        }
        #[test]
        fn test_lower() {
            assert_eq!("HELLO".to_lowercase(), "hello");
        }
        #[test]
        fn test_snake() {
            assert_eq!(to_snake_case("MyFunc"), "my_func");
        }
        #[test]
        fn test_camel() {
            assert_eq!(to_camel_case("my_func_name"), "myFuncName");
        }
        #[test]
        fn test_title() {
            assert_eq!(to_title_case("hello world"), "Hello World");
        }
    }

    Key Differences

  • Unicode case: Rust's to_uppercase/to_lowercase are Unicode-correct ('ß'.to_uppercase() == "SS"); OCaml's String.uppercase_ascii is ASCII-only.
  • Iterator combinators: Rust's flat_map + collect for camelCase is idiomatic; OCaml requires Buffer + imperative loops for the same transformation.
  • **char.to_lowercase()**: Returns a ToLowercase iterator (because one char can expand to multiple chars, e.g. 'ß'); OCaml's Char.lowercase_ascii returns a single char.
  • Convention support: Rust's ecosystem has the heck crate for all naming convention conversions; OCaml needs stringext or manual code.
  • OCaml Approach

    (* Simple upper/lower via standard library *)
    String.uppercase_ascii "hello"  (* "HELLO" *)
    String.lowercase_ascii "HELLO"  (* "hello" *)
    
    (* snake_case — manual loop *)
    let to_snake_case s =
      let buf = Buffer.create (String.length s) in
      String.iteri (fun i c ->
        if Char.uppercase_ascii c = c && c <> ' ' && i > 0
        then (Buffer.add_char buf '_'; Buffer.add_char buf (Char.lowercase_ascii c))
        else Buffer.add_char buf c) s;
      Buffer.contents buf
    

    The stringext library provides String.split_on_string and helpers; snake-case and camelCase conversions are common in code generation libraries like ppx_deriving.

    Full Source

    #![allow(clippy::all)]
    // 497. Case conversion patterns
    fn to_snake_case(s: &str) -> String {
        let mut out = String::new();
        for (i, c) in s.chars().enumerate() {
            if c.is_uppercase() {
                if i > 0 {
                    out.push('_');
                }
                out.extend(c.to_lowercase());
            } else {
                out.push(c);
            }
        }
        out
    }
    
    fn to_camel_case(s: &str) -> String {
        s.split('_')
            .enumerate()
            .flat_map(|(i, word)| {
                let mut chars = word.chars();
                if i == 0 {
                    chars.map(|c| c).collect::<String>()
                } else {
                    let first = chars
                        .next()
                        .map(|c| c.to_uppercase().to_string())
                        .unwrap_or_default();
                    let rest: String = chars.collect();
                    first + &rest
                }
                .chars()
                .collect::<Vec<_>>()
            })
            .collect()
    }
    
    fn to_title_case(s: &str) -> String {
        s.split_whitespace()
            .map(|word| {
                let mut cs = word.chars();
                cs.next()
                    .map(|c| c.to_uppercase().collect::<String>() + cs.as_str())
                    .unwrap_or_default()
            })
            .collect::<Vec<_>>()
            .join(" ")
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_upper() {
            assert_eq!("hello".to_uppercase(), "HELLO");
        }
        #[test]
        fn test_lower() {
            assert_eq!("HELLO".to_lowercase(), "hello");
        }
        #[test]
        fn test_snake() {
            assert_eq!(to_snake_case("MyFunc"), "my_func");
        }
        #[test]
        fn test_camel() {
            assert_eq!(to_camel_case("my_func_name"), "myFuncName");
        }
        #[test]
        fn test_title() {
            assert_eq!(to_title_case("hello world"), "Hello World");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
        #[test]
        fn test_upper() {
            assert_eq!("hello".to_uppercase(), "HELLO");
        }
        #[test]
        fn test_lower() {
            assert_eq!("HELLO".to_lowercase(), "hello");
        }
        #[test]
        fn test_snake() {
            assert_eq!(to_snake_case("MyFunc"), "my_func");
        }
        #[test]
        fn test_camel() {
            assert_eq!(to_camel_case("my_func_name"), "myFuncName");
        }
        #[test]
        fn test_title() {
            assert_eq!(to_title_case("hello world"), "Hello World");
        }
    }

    Exercises

  • kebab-case: Implement to_kebab_case(s: &str) -> String that converts CamelCase or snake_case to kebab-case.
  • SCREAMING_SNAKE_CASE: Implement to_screaming_snake(s: &str) -> String for constant naming (MAX_VALUE).
  • Round-trip property test: Write a property test (using proptest) that verifies camelCase → snake_case → camelCase is an identity for ASCII-alphabetic identifiers.
  • Open Source Repos