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.
