Loading page…
Rust walkthroughs
Loading page…
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.
# 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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>.
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.
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.
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
}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:
The trait-based design allows clean, readable code while handling the complexity of word boundary detection and capitalization rules consistently.