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:

  1. Intelligent word boundary detection: Detects transitions between lowercase/uppercase, separators, and numbers.

  2. Input flexibility: Accepts any of the supported formats and converts to the target format.

  3. Acronym handling: Reasonably handles acronyms like HTTPRequesthttp_request.

  4. 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.