Loading page…
Rust walkthroughs
Loading page…
heck::TitleCase for converting identifiers to title case formatting?heck::TitleCase converts identifiers from programming case conventions like snake_case, camelCase, or kebab-case into human-readable title case like "Hello World" by splitting on word boundaries, capitalizing each word, and joining with spaces. This is particularly useful for generating documentation, error messages, configuration descriptions, and user-facing text from program identifiers. The heck crate provides a trait-based API where TitleCase is implemented for string types, allowing chainable case conversions with a consistent pattern across all case transformations.
use heck::TitleCase;
fn basic_usage() {
// Convert snake_case to Title Case
let title = "hello_world".to_title_case();
assert_eq!(title, "Hello World");
// Convert camelCase to Title Case
let title = "helloWorld".to_title_case();
assert_eq!(title, "Hello World");
// Convert SCREAMING_SNAKE_CASE to Title Case
let title = "HELLO_WORLD".to_title_case();
assert_eq!(title, "Hello World");
// Convert kebab-case to Title Case
let title = "hello-world".to_title_case();
assert_eq!(title, "Hello World");
}to_title_case() transforms any common programming case into human-readable title case.
use heck::TitleCase;
fn trait_explanation() {
// TitleCase is a trait in the heck crate
// pub trait TitleCase {
// fn to_title_case(&self) -> String;
// }
// Implemented for:
// - &str
// - String
// - Cow<'_, str>
let input: &str = "my_variable_name";
let title: String = input.to_title_case();
let owned: String = "myVariableName".to_string();
let title: String = owned.to_title_case();
// Works on any string-like type
}The trait is implemented for common string types, making it ergonomic to use.
use heck::TitleCase;
fn word_boundaries() {
// TitleCase detects word boundaries from various conventions:
// Underscore boundary (snake_case)
assert_eq!("user_name".to_title_case(), "User Name");
// Hyphen boundary (kebab-case)
assert_eq!("user-name".to_title_case(), "User Name");
// Capital letter boundary (camelCase)
assert_eq!("userName".to_title_case(), "User Name");
// Space boundary (already words)
assert_eq!("user name".to_title_case(), "User Name");
// Mixed boundaries
assert_eq!("user_firstName".to_title_case(), "User First Name");
assert_eq!("APIResponse_userId".to_title_case(), "Api Response User Id");
// Numbers create boundaries
assert_eq!("version2_update".to_title_case(), "Version 2 Update");
}The conversion detects word boundaries from underscores, hyphens, capital letters, and spaces.
use heck::TitleCase;
fn capitalization() {
// Each word is capitalized (first letter uppercase, rest lowercase)
assert_eq!("hello".to_title_case(), "Hello");
assert_eq!("HELLO".to_title_case(), "Hello");
assert_eq!("HeLLo".to_title_case(), "Hello");
// Multiple words
assert_eq!("hello_world".to_title_case(), "Hello World");
assert_eq!("HELLO_WORLD".to_title_case(), "Hello World");
assert_eq!("hello_WORLD".to_title_case(), "Hello World");
// Acronyms are not preserved
assert_eq!("api_response".to_title_case(), "Api Response");
// Note: "API" becomes "Api", not preserved as "API"
// This is a limitation for preserving acronyms
}Each word gets title-cased: first letter uppercase, rest lowercase.
use heck::TitleCase;
struct ConfigField {
name: String,
description: String,
default_value: String,
}
fn generate_documentation(fields: &[ConfigField]) -> String {
let mut doc = String::new();
doc.push_str("# Configuration Options\n\n");
for field in fields {
// Convert field name to human-readable title
let title = field.name.to_title_case();
doc.push_str(&format!("## {}\n\n", title));
doc.push_str(&format!("{}\n\n", field.description));
doc.push_str(&format!("**Default:** `{}`\n\n", field.default_value));
// Also show the config key
doc.push_str(&format!("**Key:** `{}`\n\n", field.name));
}
doc
}
fn documentation_example() {
let fields = vec
![
ConfigField {
name: "max_connections".to_string(),
description: "Maximum number of concurrent connections.".to_string(),
default_value: "100".to_string(),
},
ConfigField {
name: "connection_timeout_ms".to_string(),
description: "Timeout for connection attempts.".to_string(),
default_value: "5000".to_string(),
},
];
let doc = generate_documentation(&fields);
assert!(doc.contains("## Max Connections"));
assert!(doc.contains("## Connection Timeout Ms"));
}Title case makes documentation more readable from programmatic identifiers.
use heck::TitleCase;
#[derive(Debug)]
enum ValidationError {
RequiredFieldMissing { field: String },
InvalidFormat { field: String, expected: String },
OutOfRange { field: String, min: i32, max: i32 },
}
impl std::fmt::Display for ValidationError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ValidationError::RequiredFieldMissing { field } => {
// Convert snake_case field name to readable form
write!(f, "{} is required", field.to_title_case())
}
ValidationError::InvalidFormat { field, expected } => {
write!(f, "{} must be a valid {}", field.to_title_case(), expected)
}
ValidationError::OutOfRange { field, min, max } => {
write!(f, "{} must be between {} and {}", field.to_title_case(), min, max)
}
}
}
}
fn error_message_example() {
let error = ValidationError::RequiredFieldMissing {
field: "user_name".to_string(),
};
let message = error.to_string();
assert_eq!(message, "User Name is required");
let error = ValidationError::OutOfRange {
field: "connection_timeout_ms".to_string(),
min: 100,
max: 10000,
};
let message = error.to_string();
assert_eq!(message, "Connection Timeout Ms must be between 100 and 10000");
}Converting identifiers to title case makes error messages user-friendly.
use heck::TitleCase;
struct CliOption {
name: String,
short: Option<char>,
description: String,
}
fn format_help_text(options: &[CliOption]) -> String {
let mut help = String::new();
help.push_str("Usage: myapp [OPTIONS]\n\nOptions:\n");
for opt in options {
let short = opt.short.map(|c| format!("-{}, ", c)).unwrap_or_default();
let long = format!("--{}", opt.name);
// Use title case for the description header
let title = opt.name.to_title_case();
help.push_str(&format!(
" {}{} {:<20} {}\n",
short, long, "", opt.description
));
}
help
}
fn cli_help_example() {
let options = vec
![
CliOption {
name: "max_connections".to_string(),
short: Some('c'),
description: "Maximum concurrent connections".to_string(),
},
CliOption {
name: "verbose_output".to_string(),
short: Some('v'),
description: "Enable verbose output".to_string(),
},
];
let help = format_help_text(&options);
// Help text now uses readable descriptions
}CLI tools can generate human-readable help from programmatic option names.
use heck::TitleCase;
struct ServerConfig {
server_name: String,
max_connections: u32,
connection_timeout_ms: u64,
enable_logging: bool,
}
fn display_config(config: &ServerConfig) -> String {
let mut display = String::new();
display.push_str("Server Configuration\n");
display.push_str("===================\n\n");
// Use title case for field names when displaying
display.push_str(&format!("{}: {}\n", "Server Name".to_title_case(), config.server_name));
display.push_str(&format!("{}: {}\n", "Max Connections".to_title_case(), config.max_connections));
display.push_str(&format!("{}: {}ms\n", "Connection Timeout Ms".to_title_case(), config.connection_timeout_ms));
display.push_str(&format!("{}: {}\n", "Enable Logging".to_title_case(), config.enable_logging));
display
}
fn config_display_example() {
let config = ServerConfig {
server_name: "production".to_string(),
max_connections: 100,
connection_timeout_ms: 5000,
enable_logging: true,
};
let display = display_config(&config);
println!("{}", display);
// Output:
// Server Configuration
// ===================
//
// Server Name: production
// Max Connections: 100
// Connection Timeout Ms: 5000ms
// Enable Logging: true
}Displaying configuration with title case makes it more readable.
use heck::{TitleCase, CamelCase, SnakeCase, KebabCase, PascalCase};
fn case_comparison() {
let input = "hello_world_example";
// Title Case: "Hello World Example"
// Used for: User-facing text, documentation, headers
let title = input.to_title_case();
// Camel Case: "helloWorldExample"
// Used for: JavaScript identifiers, Java methods
let camel = input.to_camel_case();
// Snake Case: "hello_world_example" (normalized)
// Used for: Rust identifiers, Python, Ruby
let snake = input.to_snake_case();
// Kebab Case: "hello-world-example"
// Used for: CSS classes, URLs, CLI flags
let kebab = input.to_kebab_case();
// Pascal Case: "HelloWorldExample"
// Used for: Rust types, C# classes
let pascal = input.to_pascal_case();
assert_eq!(title, "Hello World Example");
assert_eq!(camel, "helloWorldExample");
assert_eq!(snake, "hello_world_example");
assert_eq!(kebab, "hello-world-example");
assert_eq!(pascal, "HelloWorldExample");
}heck provides multiple case transformations for different contexts.
use heck::{TitleCase, PascalCase};
fn title_vs_pascal() {
// TitleCase: Words separated by spaces
// PascalCase: Words joined together, each capitalized
let input = "user_account_settings";
let title = input.to_title_case(); // "User Account Settings"
let pascal = input.to_pascal_case(); // "UserAccountSettings"
// TitleCase is for human-readable text
// PascalCase is for type names, class names
// Use TitleCase when:
// - Generating documentation headers
// - Creating user-facing messages
// - Displaying configuration options
// - Writing error messages
// Use PascalCase when:
// - Generating code identifiers
// - Creating type names from strings
// - Converting strings to valid type identifiers
}Title case adds spaces between words; Pascal case concatenates them.
use heck::{TitleCase, SnakeCase, CamelCase};
fn combined_conversions() {
// Start with camelCase input
let input = "getUserSettings";
// Convert to snake_case first for normalization
let normalized = input.to_snake_case(); // "get_user_settings"
// Then convert to title case
let title = normalized.to_title_case(); // "Get User Settings"
// Or directly from camelCase
let title_direct = input.to_title_case(); // "Get User Settings"
// Both work because TitleCase handles camelCase directly
// Chain for complex transformations
let final_title = "APIResponseHandler"
.to_snake_case() // "apiresponse_handler" - note: loses acronym
.to_title_case(); // "Apiresponse Handler"
// Note: acronyms are not preserved perfectly
}Chaining conversions can help normalize input before final transformation.
use heck::TitleCase;
fn edge_cases() {
// Empty string
assert_eq!("".to_title_case(), "");
// Single word
assert_eq!("hello".to_title_case(), "Hello");
assert_eq!("HELLO".to_title_case(), "Hello");
// Numbers
assert_eq!("version2".to_title_case(), "Version 2");
assert_eq!("2fa_enabled".to_title_case(), "2 Fa Enabled");
// Consecutive separators
assert_eq!("hello__world".to_title_case(), "Hello World");
assert_eq!("hello--world".to_title_case(), "Hello World");
// Mixed separators
assert_eq!("hello_world-example".to_title_case(), "Hello World Example");
// Already title case
assert_eq!("Hello World".to_title_case(), "Hello World");
// All caps input
assert_eq!("DATABASE_CONNECTION".to_title_case(), "Database Connection");
}Title case handles various input formats and edge cases gracefully.
use heck::TitleCase;
fn struct_field_titles() {
// Get field names from a struct and convert to titles
// Manual approach (no macro)
let fields = vec
!["user_id", "first_name", "last_name", "email_address", "is_active"];
let titles: Vec<String> = fields
.iter()
.map(|f| f.to_title_case())
.collect();
assert_eq!(titles, vec
!["User Id", "First Name", "Last Name", "Email Address", "Is Active"]);
// For generating tables, forms, or display from struct definitions
for (field, title) in fields.iter().zip(titles.iter()) {
println!("{}: {}", title, field);
}
}Field names can be converted to human-readable titles for display.
use heck::TitleCase;
use serde::{Serialize, Serializer};
#[derive(Serialize)]
struct ErrorResponse {
#[serde(serialize_with = "serialize_as_title")]
field: String,
message: String,
}
fn serialize_as_title<S>(value: &str, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&value.to_title_case())
}
fn serde_example() {
let error = ErrorResponse {
field: "user_name".to_string(),
message: "is required".to_string(),
};
let json = serde_json::to_string(&error).unwrap();
assert!(json.contains("\"field\":\"User Name\""));
}Custom serializers can use title case for user-facing JSON.
use heck::TitleCase;
fn performance() {
// TitleCase allocation:
// - Creates a new String
// - Allocates once for the result
// - O(n) time complexity
let input = "very_long_identifier_with_many_words";
let title = input.to_title_case();
// For hot paths, consider caching:
use std::collections::HashMap;
let mut cache: HashMap<&str, String> = HashMap::new();
// Cache results for repeated conversions
let cached_title = cache
.entry(input)
.or_insert_with(|| input.to_title_case());
// Useful for:
// - Documentation generators
// - Template engines
// - Repeated field name conversions
}Each conversion allocates a new string; cache results if needed.
use heck::TitleCase;
struct TableColumn {
field: String,
data_type: String,
}
fn generate_table_header(columns: &[TableColumn]) -> String {
columns
.iter()
.map(|col| col.field.to_title_case())
.collect::<Vec<_>>()
.join(" | ")
}
fn table_example() {
let columns = vec
![
TableColumn { field: "user_id", data_type: "int".to_string() },
TableColumn { field: "first_name", data_type: "varchar(100)".to_string() },
TableColumn { field: "last_name", data_type: "varchar(100)".to_string() },
TableColumn { field: "is_active", data_type: "boolean".to_string() },
];
let header = generate_table_header(&columns);
assert_eq!(header, "User Id | First Name | Last Name | Is Active");
}Table headers benefit from title case conversion.
The purpose of heck::TitleCase:
// TitleCase converts programming identifiers to human-readable text
// Purpose: Bridge between machine-readable and human-readable formats
// Key use cases:
// 1. Documentation: Generate readable headers from identifiers
// 2. Error messages: Make field names user-friendly
// 3. CLI help: Convert flags to readable descriptions
// 4. Configuration display: Show settings with readable names
// 5. Tables and reports: Create headers from data fieldsHow it works:
// Word boundary detection:
// - Underscores: user_name -> "user", "name"
// - Hyphens: user-name -> "user", "name"
// - Capitals: userName -> "user", "Name"
// - Spaces: user name -> "user", "name"
// Capitalization:
// - First letter of each word: uppercase
// - Rest of word: lowercase
// - "HELLO_WORLD" -> "Hello World"When to use TitleCase:
// Use TitleCase for:
// - Any user-facing text from identifiers
// - Documentation headers and sections
// - Error messages and validation feedback
// - Configuration display and help text
// - Report headers and labels
// Don't use TitleCase for:
// - Code generation (use CamelCase, PascalCase)
// - URL generation (use KebabCase)
// - Database identifiers (use SnakeCase)
// - Internal identifiers (keep original format)Key insight: heck::TitleCase solves the common problem of converting programmatic identifiers to human-readable text. Programming conventions like snake_case, camelCase, and SCREAMING_SNAKE_CASE are optimized for code readability and compiler compatibility, but users expect proper English text with spaces and capitalization. The trait-based API makes it ergonomic to apply this transformation anywhere a string needs to become user-facing, and the consistent word boundary detection handles mixed conventions gracefully. This is particularly valuable for documentation generators, error message formatters, and any system that needs to present internal identifiers to end users in a readable format.