How does heck crate help with case conversion and what naming conventions does it support?
The heck crate provides case conversion traits and implementations for transforming strings between common naming conventions. It handles the messy details of splitting words, capitalization rules, and separator insertion, making it invaluable for code generation, configuration mapping, and user-facing string formatting.
Installation and Basic Usage
# Cargo.toml
[dependencies]
heck = "0.5"use heck::{ToSnakeCase, ToCamelCase, ToKebabCase, ToPascalCase};
fn basic_conversions() {
let input = "hello world";
println!("{}", input.to_snake_case()); // hello_world
println!("{}", input.to_camel_case()); // helloWorld
println!("{}", input.to_kebab_case()); // hello-world
println!("{}", input.to_pascal_case()); // HelloWorld
}Each naming convention has a corresponding trait with a single method.
Snake Case
use heck::ToSnakeCase;
fn snake_case_examples() {
// Lowercase with underscores
assert_eq!("hello_world", "hello world".to_snake_case());
assert_eq!("my_variable_name", "MyVariableName".to_snake_case());
assert_eq!("http_request", "HTTPRequest".to_snake_case());
assert_eq!("user_id", "UserId".to_snake_case());
assert_eq!("xml_parser", "XMLParser".to_snake_case());
// Numbers are handled
assert_eq!("version_2", "version2".to_snake_case());
assert_eq!("test_123", "test123".to_snake_case());
// Various input formats
assert_eq!("foo_bar", "fooBar".to_snake_case());
assert_eq!("foo_bar", "FooBar".to_snake_case());
assert_eq!("foo_bar", "foo-bar".to_snake_case());
assert_eq!("foo_bar", "FOO_BAR".to_snake_case());
}Snake case is common in Rust for function and variable names.
Camel Case (Lower Camel Case)
use heck::ToCamelCase;
fn camel_case_examples() {
// Lowercase first letter, then PascalCase
assert_eq!("helloWorld", "hello world".to_camel_case());
assert_eq!("myVariableName", "my_variable_name".to_camel_case());
assert_eq!("httpRequest", "HTTP_REQUEST".to_camel_case());
assert_eq!("xmlParser", "XML_PARSER".to_camel_case());
assert_eq!("userId", "USER_ID".to_camel_case());
// Various input formats
assert_eq!("fooBar", "foo_bar".to_camel_case());
assert_eq!("fooBar", "FooBar".to_camel_case());
assert_eq!("fooBar", "foo-bar".to_camel_case());
assert_eq!("fooBar", "FOO_BAR".to_camel_case());
// Numbers
assert_eq!("version2", "version_2".to_camel_case());
}Camel case is common in JavaScript and JSON APIs.
Pascal Case (Upper Camel Case)
use heck::ToPascalCase;
fn pascal_case_examples() {
// Capitalize first letter of each word
assert_eq!("HelloWorld", "hello world".to_pascal_case());
assert_eq!("MyVariableName", "my_variable_name".to_pascal_case());
assert_eq!("HttpRequest", "HTTP_REQUEST".to_pascal_case());
assert_eq!("XmlParser", "XML_PARSER".to_pascal_case());
assert_eq!("UserId", "USER_ID".to_pascal_case());
// Various input formats
assert_eq!("FooBar", "foo_bar".to_pascal_case());
assert_eq!("FooBar", "fooBar".to_pascal_case());
assert_eq!("FooBar", "foo-bar".to_pascal_case());
assert_eq!("FooBar", "FOO_BAR".to_pascal_case());
// Type names
assert_eq!("MyStruct", "my_struct".to_pascal_case());
assert_eq!("SomeEnum", "some_enum".to_pascal_case());
}Pascal case is the Rust convention for type names.
Kebab Case
use heck::ToKebabCase;
fn kebab_case_examples() {
// Lowercase with hyphens
assert_eq!("hello-world", "hello world".to_kebab_case());
assert_eq!("my-variable-name", "my_variable_name".to_kebab_case());
assert_eq!("http-request", "HTTPRequest".to_kebab_case());
assert_eq!("user-id", "UserId".to_kebab_case());
// Various input formats
assert_eq!("foo-bar", "foo_bar".to_kebab_case());
assert_eq!("foo-bar", "FooBar".to_kebab_case());
assert_eq!("foo-bar", "fooBar".to_kebab_case());
assert_eq!("foo-bar", "FOO_BAR".to_kebab_case());
// Common in URLs and config files
assert_eq!("my-app-config", "MyAppConfig".to_kebab_case());
assert_eq!("feature-flag", "FeatureFlag".to_kebab_case());
}Kebab case is common in URLs, CLI arguments, and configuration files.
Title Case
use heck::ToTitleCase;
fn title_case_examples() {
// Space-separated with capitalized words
assert_eq!("Hello World", "hello_world".to_title_case());
assert_eq!("My Variable Name", "myVariableName".to_title_case());
assert_eq!("Http Request", "HTTPRequest".to_title_case());
assert_eq!("User Id", "userId".to_title_case());
// Good for display
assert_eq!("Error Message", "error_message".to_title_case());
assert_eq!("Config File Path", "ConfigFilePath".to_title_case());
}Title case is useful for user-facing messages and labels.
Shouty Snake Case
use heck::ToShoutySnakeCase;
fn shouty_snake_examples() {
// Uppercase with underscores (constants)
assert_eq!("HELLO_WORLD", "hello world".to_shouty_snake_case());
assert_eq!("MY_VARIABLE_NAME", "myVariableName".to_shouty_snake_case());
assert_eq!("HTTP_REQUEST", "HTTPRequest".to_shouty_snake_case());
assert_eq!("USER_ID", "userId".to_shouty_snake_case());
// Various input formats
assert_eq!("FOO_BAR", "foo_bar".to_shouty_snake_case());
assert_eq!("FOO_BAR", "FooBar".to_shouty_snake_case());
assert_eq!("FOO_BAR", "foo-bar".to_shouty_snake_case());
// Rust constants
assert_eq!("MAX_RETRIES", "maxRetries".to_shouty_snake_case());
assert_eq!("DEFAULT_PORT", "default_port".to_shouty_snake_case());
}Shouty snake case is the Rust convention for constants.
Train Case
use heck::ToTrainCase;
fn train_case_examples() {
// Capitalized words with hyphens
assert_eq!("Hello-World", "hello world".to_train_case());
assert_eq!("My-Variable-Name", "my_variable_name".to_train_case());
assert_eq!("Http-Request", "HTTPRequest".to_train_case());
// Various input formats
assert_eq!("Foo-Bar", "foo_bar".to_train_case());
assert_eq!("Foo-Bar", "FooBar".to_train_case());
// Common in HTTP headers
assert_eq!("Content-Type", "content_type".to_train_case());
assert_eq!("X-Request-Id", "x_request_id".to_train_case());
}Train case is used for HTTP headers and some configuration formats.
Upper Camel Case Alias
use heck::ToUpperCamelCase;
fn upper_camel_examples() {
// ToUpperCamelCase is an alias for ToPascalCase
assert_eq!("HelloWorld", "hello_world".to_upper_camel_case());
assert_eq!("MyStruct", "my_struct".to_upper_camel_case());
}An alias exists for those who prefer "upper camel case" terminology.
Code Generation Use Case
use heck::{ToSnakeCase, ToPascalCase, ToShoutySnakeCase};
struct Field {
name: String,
field_type: String,
}
struct Struct {
name: String,
fields: Vec<Field>,
}
fn generate_rust_code(s: Struct) -> String {
let struct_name = s.name.to_pascal_case();
let mut code = String::new();
// Struct definition
code.push_str(&format!("pub struct {} {{\n", struct_name));
for field in &s.fields {
let field_name = field.name.to_snake_case();
code.push_str(&format!(" pub {}: {},\n", field_name, field.field_type));
}
code.push_str("}\n\n");
// Constants
for field in &s.fields {
let const_name = field.name.to_shouty_snake_case();
code.push_str(&format!(
"pub const {}: &str = \"{}\";\n",
const_name, field.name
));
}
code
}
fn code_gen_example() {
let person = Struct {
name: "Person".to_string(),
fields: vec![
Field { name: "firstName".to_string(), field_type: "String".to_string() },
Field { name: "lastName".to_string(), field_type: "String".to_string() },
Field { name: "age".to_string(), field_type: "u32".to_string() },
],
};
let code = generate_rust_code(person);
println!("{}", code);
// pub struct Person {
// pub first_name: String,
// pub last_name: String,
// pub age: u32,
// }
//
// pub const FIRST_NAME: &str = "firstName";
// pub const LAST_NAME: &str = "lastName";
// pub const AGE: &str = "age";
}Heck excels at generating code with correct naming conventions.
API Response Mapping
use heck::{ToSnakeCase, ToCamelCase};
use serde::{Deserialize, Serialize};
// Database model with snake_case
#[derive(Deserialize)]
struct UserModel {
user_id: u64,
first_name: String,
last_name: String,
created_at: String,
}
// API response with camelCase
#[derive(Serialize)]
struct UserResponse {
user_id: u64,
first_name: String,
last_name: String,
created_at: String,
}
// Convert field names for JSON
fn to_camel_case_map(keys: Vec<&str>) -> std::collections::HashMap<String, String> {
keys.into_iter()
.map(|k| (k.to_string(), k.to_camel_case()))
.collect()
}
fn api_mapping_example() {
let field_mapping = to_camel_case_map(vec![
"user_id", "first_name", "last_name", "created_at"
]);
for (snake, camel) in field_mapping {
println!("{} -> {}", snake, camel);
}
// user_id -> userId
// first_name -> firstName
// last_name -> lastName
// created_at -> createdAt
}Map between database conventions and API conventions.
CLI Argument Processing
use heck::{ToKebabCase, ToSnakeCase};
fn cli_argument_example() {
// CLI uses kebab-case for long options
let options = vec!["maxRetries", "timeoutMs", "outputPath", "verboseMode"];
println!("CLI options:");
for opt in &options {
println!(" --{}", opt.to_kebab_case());
}
// --max-retries
// --timeout-ms
// --output-path
// --verbose-mode
// Convert back to config file names (snake_case)
println!("\nConfig file keys:");
for opt in &options {
println!(" {}: {}", opt.to_snake_case(), opt);
}
// max_retries: maxRetries
// timeout_ms: timeoutMs
// output_path: outputPath
// verbose_mode: verboseMode
}Convert between CLI conventions and internal naming.
Handling Acronyms
use heck::{ToSnakeCase, ToPascalCase, ToCamelCase};
fn acronym_handling() {
// heck handles acronyms reasonably
assert_eq!("http_request", "HTTPRequest".to_snake_case());
assert_eq!("xml_parser", "XMLParser".to_snake_case());
assert_eq!("user_id", "UserId".to_snake_case());
assert_eq!("io_error", "IOError".to_snake_case());
// Round-tripping works
let original = "XMLHttpRequest";
let snake = original.to_snake_case(); // xml_http_request
let pascal = snake.to_pascal_case(); // XmlHttpRequest
// Note: capitalization of acronyms may change
println!("{} -> {} -> {}", original, snake, pascal);
// XMLHttpRequest -> xml_http_request -> XmlHttpRequest
}Acronyms are handled but may not round-trip perfectly.
Edge Cases
use heck::{ToSnakeCase, ToKebabCase, ToCamelCase};
fn edge_cases() {
// Empty string
assert_eq!("", "".to_snake_case());
// Single word
assert_eq!("hello", "hello".to_snake_case());
assert_eq!("hello", "HELLO".to_snake_case());
assert_eq!("hello", "Hello".to_snake_case());
// Numbers
assert_eq!("version_2", "version2".to_snake_case());
assert_eq!("test_123_abc", "test123Abc".to_snake_case());
// Multiple separators collapsed
assert_eq!("foo_bar", "foo__bar".to_snake_case());
assert_eq!("foo_bar", "foo--bar".to_kebab_case());
// Leading/trailing separators removed
assert_eq!("foo_bar", "_foo_bar_".to_snake_case());
assert_eq!("foo_bar", "foo_bar_".to_snake_case());
// Mixed separators
assert_eq!("foo_bar_baz", "foo-bar_baz".to_snake_case());
assert_eq!("foo-bar-baz", "foo_bar-baz".to_kebab_case());
}Heck normalizes messy input consistently.
AsRef Support
use heck::ToSnakeCase;
fn with_string_types() {
// Works with &str
let s: &str = "helloWorld";
assert_eq!("hello_world", s.to_snake_case());
// Works with String
let s: String = String::from("helloWorld");
assert_eq!("hello_world", s.to_snake_case());
// Works with Cow
use std::borrow::Cow;
let s: Cow<str> = Cow::Borrowed("helloWorld");
assert_eq!("hello_world", s.to_snake_case());
// Returns String
let result: String = "helloWorld".to_snake_case();
}The traits work with any type implementing AsRef<str>.
Custom Case Conversion
use heck::{ToSnakeCase, ToTitleCase};
fn custom_combinations() {
// Combine conversions
let input = "user_profile_data";
// Snake to title for display
let display = input.to_title_case();
println!("Display: {}", display); // User Profile Data
// Then to Pascal for a type name
let type_name = display.replace(" ", "");
println!("Type: {}", type_name); // UserProfileData
// Or directly
let type_name = input.to_pascal_case();
println!("Direct: {}", type_name); // UserProfileData
}
// Custom case by combining
fn to_dot_case(s: &str) -> String {
s.to_snake_case().replace('_', ".")
}
fn to_constant_case(s: &str) -> String {
s.to_shouty_snake_case()
}
fn custom_cases() {
assert_eq!("user.profile.data", to_dot_case("user_profile_data"));
assert_eq!("USER_PROFILE_DATA", to_constant_case("user_profile_data"));
}Build custom conversions using the provided traits.
Performance Considerations
use heck::ToSnakeCase;
fn performance_notes() {
// All conversions allocate a new String
let input = "myVariableName";
let output: String = input.to_snake_case();
// The conversion is O(n) where n is string length
// Multiple passes may be made to detect word boundaries
// For repeated conversions, consider caching
use std::collections::HashMap;
let mut cache: HashMap<String, String> = HashMap::new();
let keys = vec!["myVariable", "anotherKey", "myVariable"];
for key in keys {
let converted = cache.entry(key.to_string())
.or_insert_with(|| key.to_snake_case());
println!("{} -> {}", key, converted);
}
}Conversions allocate new strings; cache if converting repeatedly.
All Supported Cases
use heck::{
ToSnakeCase,
ToCamelCase,
ToPascalCase,
ToKebabCase,
ToTitleCase,
ToShoutySnakeCase,
ToTrainCase,
ToUpperCamelCase,
};
fn all_cases_demo() {
let input = "my_variable_name";
println!("Input: {}", input);
println!("snake_case: {}", input.to_snake_case());
println!("camelCase: {}", input.to_camel_case());
println!("PascalCase: {}", input.to_pascal_case());
println!("kebab-case: {}", input.to_kebab_case());
println!("Title Case: {}", input.to_title_case());
println!("SHOUTY_SNAKE: {}", input.to_shouty_snake_case());
println!("Train-Case: {}", input.to_train_case());
println!("UpperCamelCase: {}", input.to_upper_camel_case());
// Output:
// Input: my_variable_name
// snake_case: my_variable_name
// camelCase: myVariableName
// PascalCase: MyVariableName
// kebab-case: my-variable-name
// Title Case: My Variable Name
// SHOUTY_SNAKE: MY_VARIABLE_NAME
// Train-Case: My-Variable-Name
// UpperCamelCase: MyVariableName
}Synthesis
The heck crate provides case conversion through individual traits, each converting to a specific naming convention:
| Convention | Trait Method | Example |
|---|---|---|
| snake_case | to_snake_case() |
my_variable |
| camelCase | to_camel_case() |
myVariable |
| PascalCase | to_pascal_case() |
MyVariable |
| kebab-case | to_kebab_case() |
my-variable |
| Title Case | to_title_case() |
My Variable |
| SHOUTY_SNAKE | to_shouty_snake_case() |
MY_VARIABLE |
| Train-Case | to_train_case() |
My-Variable |
Key features:
-
Intelligent word boundary detection: Detects transitions between lowercase/uppercase, separators, and numbers.
-
Input flexibility: Accepts any of the supported formats and converts to the target format.
-
Acronym handling: Reasonably handles acronyms like
HTTPRequest→http_request. -
Normalization: Collapses multiple separators and trims leading/trailing separators.
Common use cases:
- Code generation (generating Rust types from schemas)
- API response transformation (snake_case ↔ camelCase)
- CLI argument processing (kebab-case options)
- Configuration file mapping
- User-facing message formatting (Title Case)
The trait-based design allows clean, readable code while handling the complexity of word boundary detection and capitalization rules consistently.
