What is the difference between heck::TitleCase and heck::CamelCase for string transformation?

heck::TitleCase transforms strings into title case where each word is capitalized with spaces preserved (e.g., "hello_world" becomes "Hello World"), while heck::CamelCase transforms strings into camel case where words are concatenated with each word capitalized except the first (e.g., "hello_world" becomes "helloWorld"). The key distinction is that TitleCase produces human-readable titles with spaces between words, suitable for display in UI labels and headings, whereas CamelCase produces programming-language identifiers suitable for variable names, type names, and API conventions. Both use the same word-splitting logic (recognizing underscores, hyphens, and case transitions), but differ in output formatting: TitleCase preserves word boundaries with spaces while CamelCase removes them, and CamelCase lowercases the first word while TitleCase capitalizes all words.

Basic TitleCase Transformation

use heck::ToTitleCase;
 
fn main() {
    // TitleCase: each word capitalized, spaces preserved
    println!("{}", "hello_world".to_title_case());           // "Hello World"
    println!("{}", "hello-world".to_title_case());           // "Hello World"
    println!("{}", "helloWorld".to_title_case());            // "Hello World"
    println!("{}", "HELLO_WORLD".to_title_case());           // "Hello World"
    println!("{}", "hello_world_example".to_title_case());   // "Hello World Example"
}

ToTitleCase capitalizes each word and separates them with spaces.

Basic CamelCase Transformation

use heck::ToLowerCamelCase;
 
fn main() {
    // CamelCase (lower camel case): first word lowercase, rest capitalized
    println!("{}", "hello_world".to_lower_camel_case());        // "helloWorld"
    println!("{}", "hello-world".to_lower_camel_case());        // "helloWorld"
    println!("{}", "HelloWorld".to_lower_camel_case());         // "helloWorld"
    println!("{}", "HELLO_WORLD".to_lower_camel_case());        // "helloWorld"
    println!("{}", "hello_world_example".to_lower_camel_case()); // "helloWorldExample"
}

ToLowerCamelCase lowercases the first word and concatenates all words.

UpperCamelCase (PascalCase) Transformation

use heck::ToUpperCamelCase;
 
fn main() {
    // UpperCamelCase / PascalCase: all words capitalized
    println!("{}", "hello_world".to_upper_camel_case());        // "HelloWorld"
    println!("{}", "hello-world".to_upper_camel_case());        // "HelloWorld"
    println!("{}", "helloWorld".to_upper_camel_case());         // "HelloWorld"
    println!("{}", "HELLO_WORLD".to_upper_camel_case());        // "HelloWorld"
    println!("{}", "hello_world_example".to_upper_camel_case()); // "HelloWorldExample"
}

ToUpperCamelCase capitalizes all words including the first, producing PascalCase.

Word Boundary Detection

use heck::{ToTitleCase, ToLowerCamelCase};
 
fn main() {
    // Both use the same word splitting logic
    
    // Underscore boundaries
    println!("Underscore:");
    println!("  Title: {}", "one_two_three".to_title_case());      // "One Two Three"
    println!("  Camel: {}", "one_two_three".to_lower_camel_case()); // "oneTwoThree"
    
    // Hyphen boundaries
    println!("Hyphen:");
    println!("  Title: {}", "one-two-three".to_title_case());       // "One Two Three"
    println!("  Camel: {}", "one-two-three".to_lower_camel_case());// "oneTwoThree"
    
    // Case transitions (camelCase input)
    println!("Case transitions:");
    println!("  Title: {}", "oneTwoThree".to_title_case());        // "One Two Three"
    println!("  Camel: {}", "oneTwoThree".to_lower_camel_case());  // "oneTwoThree"
    
    // Mixed separators
    println!("Mixed:");
    println!("  Title: {}", "one_two-threeThree".to_title_case()); // "One Two Three Three"
    println!("  Camel: {}", "one_two-threeThree".to_lower_camel_case()); // "oneTwoThreeThree"
}

Both transforms use identical word boundary detection; only output differs.

Case Conversion Rules

use heck::{ToTitleCase, ToLowerCamelCase, ToUpperCamelCase};
 
fn main() {
    // TitleCase rules:
    // 1. Split into words
    // 2. Capitalize first letter of each word
    // 3. Lowercase remaining letters of each word
    // 4. Join with spaces
    
    println!("TitleCase:");
    println!("  {}", "HELLO_WORLD".to_title_case());    // "Hello World"
    println!("  {}", "hELLO_wORLD".to_title_case());    // "Hello World"
    
    // LowerCamelCase rules:
    // 1. Split into words
    // 2. Lowercase entire first word
    // 3. Capitalize first letter, lowercase rest for subsequent words
    // 4. Concatenate (no spaces)
    
    println!("\nLowerCamelCase:");
    println!("  {}", "HELLO_WORLD".to_lower_camel_case()); // "helloWorld"
    println!("  {}", "hELLO_wORLD".to_lower_camel_case()); // "helloWorld"
    
    // UpperCamelCase rules:
    // 1. Split into words
    // 2. Capitalize first letter, lowercase rest for ALL words
    // 3. Concatenate (no spaces)
    
    println!("\nUpperCamelCase:");
    println!("  {}", "HELLO_WORLD".to_upper_camel_case()); // "HelloWorld"
    println!("  {}", "hELLO_wORLD".to_upper_camel_case()); // "HelloWorld"
}

Each transformation applies specific case rules consistently.

Direct Trait Usage

use heck::{TitleCase, CamelCase};
 
fn main() {
    // Use traits directly for custom transformation
    let input = "hello_world_example";
    
    // TitleCase trait
    let title: String = input.to_title_case();
    println!("Title: {}", title);
    
    // CamelCase trait (lower camel case)
    let camel: String = input.to_lower_camel_case();
    println!("Lower Camel: {}", camel);
    
    // Upper camel case via trait
    let pascal: String = input.to_upper_camel_case();
    println!("Upper Camel: {}", pascal);
    
    // The traits are in heck::TitleCase and heck::CamelCase
    // but typically used via ToTitleCase and ToLowerCamelCase aliases
}

Traits can be imported directly for explicit type annotations.

Custom Type Implementation

use heck::{ToTitleCase, ToLowerCamelCase};
 
struct FieldName {
    raw: String,
}
 
impl FieldName {
    fn new(raw: impl Into<String>) -> Self {
        FieldName { raw: raw.into() }
    }
    
    fn to_display(&self) -> String {
        // TitleCase for UI display
        self.raw.to_title_case()
    }
    
    fn to_variable(&self) -> String {
        // LowerCamelCase for code generation
        self.raw.to_lower_camel_case()
    }
    
    fn to_type(&self) -> String {
        // UpperCamelCase for type names
        self.raw.to_upper_camel_case()
    }
}
 
fn main() {
    let field = FieldName::new("user_name");
    
    println!("Display: {}", field.to_display());    // "User Name"
    println!("Variable: {}", field.to_variable());  // "userName"
    println!("Type: {}", field.to_type());          // "UserName"
}

Use different case transforms for different contexts in the same application.

Number Handling

use heck::{ToTitleCase, ToLowerCamelCase};
 
fn main() {
    // Numbers are treated as word boundaries
    println!("Numbers:");
    println!("  Title: {}", "version2_update".to_title_case());      // "Version 2 Update"
    println!("  Camel: {}", "version2_update".to_lower_camel_case()); // "version2Update"
    
    println!("  Title: {}", "html5_parser".to_title_case());         // "Html 5 Parser"
    println!("  Camel: {}", "html5_parser".to_lower_camel_case());   // "html5Parser"
    
    // Adjacent numbers
    println!("  Title: {}", "test123value".to_title_case());         // "Test 123 Value"
    println!("  Camel: {}", "test123value".to_lower_camel_case());   // "test123Value"
}

Numbers create word boundaries, producing separate words in TitleCase.

Acronym Handling

use heck::{ToTitleCase, ToLowerCamelCase};
 
fn main() {
    // Acronyms are detected as word boundaries
    println!("Acronyms:");
    
    // All-caps words
    println!("  Title: {}", "API_VERSION".to_title_case());        // "Api Version"
    println!("  Camel: {}", "API_VERSION".to_lower_camel_case());   // "apiVersion"
    
    // Embedded acronyms
    println!("  Title: {}", "parse_URL_string".to_title_case());   // "Parse Url String"
    println!("  Camel: {}", "parse_URL_string".to_lower_camel_case()); // "parseUrlString"
    
    // Multi-letter acronyms
    println!("  Title: {}", "HTTP_response_code".to_title_case()); // "Http Response Code"
    println!("  Camel: {}", "HTTP_response_code".to_lower_camel_case()); // "httpResponseCode"
    
    // heck doesn't preserve acronym casing - it normalizes
    // If you need "HTTP" preserved, you'd need custom logic
}

heck normalizes acronyms to standard title case; it doesn't preserve all-caps.

Empty and Edge Cases

use heck::{ToTitleCase, ToLowerCamelCase};
 
fn main() {
    // Empty string
    println!("Empty: '{}'", "".to_title_case());         // ""
    println!("Empty: '{}'", "".to_lower_camel_case());   // ""
    
    // Single word
    println!("Single: '{}'", "hello".to_title_case());        // "Hello"
    println!("Single: '{}'", "hello".to_lower_camel_case());  // "hello"
    
    // Single word upper case
    println!("Single upper: '{}'", "HELLO".to_title_case());        // "Hello"
    println!("Single upper: '{}'", "HELLO".to_lower_camel_case());   // "hello"
    
    // Numbers only
    println!("Numbers: '{}'", "123".to_title_case());        // "123"
    println!("Numbers: '{}'", "123".to_lower_camel_case());  // "123"
    
    // Already converted
    println!("Already title: '{}'", "Hello World".to_title_case());      // "Hello World"
    println!("Already camel: '{}'", "helloWorld".to_lower_camel_case());  // "helloWorld"
}

Edge cases produce predictable, normalized output.

Unicode Handling

use heck::{ToTitleCase, ToLowerCamelCase};
 
fn main() {
    // Unicode characters are preserved
    println!("Unicode:");
    println!("  Title: {}", "café_menu".to_title_case());        // "Café Menu"
    println!("  Camel: {}", "café_menu".to_lower_camel_case());  // "caféMenu"
    
    println!("  Title: {}", "über_cool".to_title_case());        // "Über Cool"
    println!("  Camel: {}", "über_cool".to_lower_camel_case());  // "überCool"
    
    // Non-ASCII boundaries
    println!("  Title: {}", "日本語_テスト".to_title_case());     // "日本語 テスト"
    println!("  Camel: {}", "日本語_テスト".to_lower_camel_case()); // "日本語テスト"
}

Unicode characters are preserved; word boundaries are detected correctly.

Performance Characteristics

use heck::{ToTitleCase, ToLowerCamelCase};
 
fn main() {
    // Both operations allocate a new String
    // Complexity is O(n) where n is input length
    
    let input = "this_is_a_long_identifier_name";
    
    // TitleCase must insert spaces, so output may be longer
    let title = input.to_title_case();
    println!("Title length: {} -> {}", input.len(), title.len());
    
    // CamelCase removes separators, so output is shorter or equal
    let camel = input.to_lower_camel_case();
    println!("Camel length: {} -> {}", input.len(), camel.len());
    
    // Both allocate exactly once for the output
    // No intermediate allocations during transformation
}

Both transforms are O(n) with single allocation for output.

SnakeCase and KebabCase Comparison

use heck::{ToTitleCase, ToLowerCamelCase, ToSnakeCase, ToKebabCase};
 
fn main() {
    let input = "HelloWorld";
    
    // Compare all case transformations
    println!("Original: {}", input);
    println!("TitleCase: {}", input.to_title_case());        // "Hello World"
    println!("CamelCase: {}", input.to_lower_camel_case());  // "helloWorld"
    println!("SnakeCase: {}", input.to_snake_case());        // "hello_world"
    println!("KebabCase: {}", input.to_kebab_case());        // "hello-world"
    println!("ShoutySnake: {}", input.to_shouty_snake_case()); // "HELLO_WORLD"
    
    // Round-trip conversions
    let original = "hello_world";
    let camel = original.to_lower_camel_case();
    let back_to_snake = camel.to_snake_case();
    
    println!("\nRound trip: {} -> {} -> {}", original, camel, back_to_snake);
    // hello_world -> helloWorld -> hello_world
}

heck provides multiple case transformations for different conventions.

Code Generation Use Case

use heck::{ToLowerCamelCase, ToUpperCamelCase, ToSnakeCase};
 
struct Field {
    name: String,
    field_type: String,
}
 
impl Field {
    fn new(name: &str, field_type: &str) -> Self {
        Field {
            name: name.to_string(),
            field_type: field_type.to_string(),
        }
    }
    
    fn field_name(&self) -> String {
        // LowerCamelCase for struct fields
        self.name.to_lower_camel_case()
    }
    
    fn method_name(&self) -> String {
        // LowerCamelCase for methods
        format!("get_{}", self.name.to_snake_case())
    }
    
    fn type_name(&self) -> String {
        // UpperCamelCase for type names
        self.name.to_upper_camel_case()
    }
}
 
fn main() {
    let field = Field::new("user_name", "String");
    
    println!("Field name: {}", field.field_name());    // "userName"
    println!("Method name: {}", field.method_name());  // "get_user_name"
    println!("Type name: {}", field.type_name());      // "UserName"
}

Use CamelCase for code generation to match language conventions.

UI Display Use Case

use heck::ToTitleCase;
 
struct ConfigField {
    key: String,
    value: String,
}
 
impl ConfigField {
    fn new(key: &str, value: &str) -> Self {
        ConfigField {
            key: key.to_string(),
            value: value.to_string(),
        }
    }
    
    fn display_name(&self) -> String {
        // TitleCase for UI labels
        self.key.to_title_case()
    }
    
    fn tooltip(&self) -> String {
        format!("{}: {}", self.display_name(), self.value)
    }
}
 
fn main() {
    let fields = vec![
        ConfigField::new("max_connections", "100"),
        ConfigField::new("timeout_seconds", "30"),
        ConfigField::new("enable_logging", "true"),
    ];
    
    println!("Configuration:");
    for field in &fields {
        println!("  {}: {}", field.display_name(), field.value);
    }
    // Output:
    // Configuration:
    //   Max Connections: 100
    //   Timeout Seconds: 30
    //   Enable Logging: true
}

Use TitleCase for human-readable UI labels from config keys.

API Response Transformation

use heck::{ToTitleCase, ToLowerCamelCase, ToSnakeCase};
use serde::Serialize;
 
#[derive(Serialize)]
struct ApiResponse {
    #[serde(rename = "user_name")]
    user_name: String,
    
    #[serde(rename = "created_at")]
    created_at: String,
}
 
fn transform_for_display(response: &ApiResponse) -> Vec<(String, String)> {
    // Convert snake_case API keys to TitleCase for display
    vec![
        ("User Name".to_string(), response.user_name.clone()),
        ("Created At".to_string(), response.created_at.clone()),
    ]
}
 
fn transform_for_client(response: &ApiResponse) -> serde_json::Value {
    // API uses snake_case, frontend expects camelCase
    serde_json::json!({
        "userName": response.user_name,
        "createdAt": response.created_at,
    })
}
 
fn main() {
    let response = ApiResponse {
        user_name: "alice".to_string(),
        created_at: "2024-03-15".to_string(),
    };
    
    let display = transform_for_display(&response);
    for (key, value) in display {
        println!("{}: {}", key, value);
    }
    
    let client_json = transform_for_client(&response);
    println!("Client JSON: {}", client_json);
}

Transform between API conventions (snake_case) and display (TitleCase) or frontend (camelCase).

Comparison Summary

use heck::{ToTitleCase, ToLowerCamelCase, ToUpperCamelCase, ToSnakeCase, ToKebabCase};
 
fn main() {
    let examples = vec![
        "hello_world",
        "user_id",
        "API_KEY",
        "http_request",
        "my_variable_name",
    ];
    
    println!("{:<20} {:<15} {:<15} {:<15} {:<15}", 
             "Input", "TitleCase", "LowerCamel", "UpperCamel", "SnakeCase");
    println!("{}", "-".repeat(80));
    
    for example in examples {
        println!(
            "{:<20} {:<15} {:<15} {:<15} {:<15}",
            example,
            example.to_title_case(),
            example.to_lower_camel_case(),
            example.to_upper_camel_case(),
            example.to_snake_case()
        );
    }
}

Output comparison shows the relationship between different case transforms.

Synthesis

Transformation comparison:

Input to_title_case() to_lower_camel_case() to_upper_camel_case()
hello_world "Hello World" "helloWorld" "HelloWorld"
hello-world "Hello World" "helloWorld" "HelloWorld"
helloWorld "Hello World" "helloWorld" "HelloWorld"
HELLO_WORLD "Hello World" "helloWorld" "HelloWorld"
API_KEY "Api Key" "apiKey" "ApiKey"
user_id "User Id" "userId" "UserId"

Key differences:

Aspect TitleCase CamelCase (Lower) CamelCase (Upper)
First word Capitalized Lowercase Capitalized
Word separator Space None (concatenated) None (concatenated)
Output style Human-readable Variable names Type names
Space in output Yes No No
Use case UI labels, headings Variables, methods Types, classes

Word boundary detection (shared by all transforms):

Pattern Detected boundary
_ Underscore
- Hyphen
aA Lower-to-upper transition
1a, a1 Number-letter transition

Key insight: heck::TitleCase and heck::CamelCase use identical word boundary detection but differ fundamentally in output format—TitleCase produces human-readable strings with spaces between words, while CamelCase produces programming-language identifiers with words concatenated. TitleCase capitalizes every word uniformly, making it ideal for display purposes like UI labels, headings, and documentation. LowerCamelCase (via to_lower_camel_case()) lowercases only the first word, following JavaScript/Java variable naming conventions. UpperCamelCase (via to_upper_camel_case()) capitalizes all words equally, following type/class naming conventions across most languages. Neither transformation preserves original acronym casing—"API_KEY" becomes "Api Key" or "apiKey" rather than "API Key" or "APIKey"—a limitation that requires custom logic if acronym preservation is needed. The choice between TitleCase and CamelCase should be guided by the destination context: TitleCase for human-facing strings and CamelCase for code identifiers.