What is the purpose of heck::ToKebabCase for converting identifiers to kebab-case strings?

heck::ToKebabCase is a trait that converts identifiers to kebab-case strings (lowercase with hyphens) by splitting on word boundaries and joining with hyphens, commonly used for CLI flags, configuration keys, and URL slugs. It provides automatic case conversion from various input formats like CamelCase, snake_case, and SHOUTY_CASE to consistent kebab-case output.

Basic ToKebabCase Usage

use heck::ToKebabCase;
 
fn basic_usage() {
    // ToKebabCase converts any case to kebab-case
    
    // From CamelCase
    assert_eq!("MyStruct".to_kebab_case(), "my-struct");
    assert_eq!("HTTPServer".to_kebab_case(), "http-server");
    
    // From snake_case
    assert_eq!("my_function".to_kebab_case(), "my-function");
    assert_eq!("user_id".to_kebab_case(), "user-id");
    
    // From SHOUTY_CASE
    assert_eq!("MAX_CONNECTIONS".to_kebab_case(), "max-connections");
    assert_eq!("API_KEY".to_kebab_case(), "api-key");
    
    // From mixed case
    assert_eq!("myHTTPServer".to_kebab_case(), "my-http-server");
    assert_eq!("XMLHttpRequest".to_kebab_case(), "xml-http-request");
}

ToKebabCase converts identifiers from any case convention to kebab-case.

The ToKebabCase Trait

// heck provides the ToKebabCase trait
// It's implemented for &str and String
 
pub trait ToKebabCase {
    fn to_kebab_case(&self) -> String;
}
 
// The trait is in heck's prelude
use heck::ToKebabCase;
 
fn trait_signature() {
    // The method takes &self and returns String
    // Input: any string
    // Output: kebab-case string
    
    let input = "MyFunctionName";
    let output: String = input.to_kebab_case();
    
    assert_eq!(output, "my-function-name");
}

The trait is simple: to_kebab_case(&self) -> String.

Word Boundary Detection

use heck::ToKebabCase;
 
fn word_boundaries() {
    // heck detects word boundaries using several rules:
    
    // 1. Uppercase letters start new words
    assert_eq!("CamelCase".to_kebab_case(), "camel-case");
    assert_eq!("PascalCase".to_kebab_case(), "pascal-case");
    
    // 2. Underscores are word separators
    assert_eq!("snake_case".to_kebab_case(), "snake-case");
    assert_eq!("my_variable_name".to_kebab_case(), "my-variable-name");
    
    // 3. Hyphens are preserved/treated as separators
    assert_eq!("kebab-case".to_kebab_case(), "kebab-case");
    
    // 4. Spaces are word separators
    assert_eq!("spaced words".to_kebab_case(), "spaced-words");
    
    // 5. Numbers are word boundaries
    assert_eq!("version2Update".to_kebab_case(), "version-2-update");
    assert_eq!("IPv4Address".to_kebab_case(), "ipv-4-address");
}

ToKebabCase detects word boundaries from uppercase, underscores, hyphens, spaces, and numbers.

Acronym and Abbreviation Handling

use heck::ToKebabCase;
 
fn acronym_handling() {
    // Acronyms are detected and converted appropriately
    
    // Multiple uppercase letters are grouped
    assert_eq!("XMLParser".to_kebab_case(), "xml-parser");
    assert_eq!("HTTPServer".to_kebab_case(), "http-server");
    assert_eq!("APIEndpoint".to_kebab_case(), "api-endpoint");
    
    // Transitions from uppercase to lowercase split
    assert_eq!("HTTPRequest".to_kebab_case(), "http-request");
    
    // SHOUTY_CASE converts properly
    assert_eq!("API_KEY".to_kebab_case(), "api-key");
    assert_eq!("HTTP_RESPONSE_CODE".to_kebab_case(), "http-response-code");
    
    // Mixed acronyms
    assert_eq!("XMLHTTPRequest".to_kebab_case(), "xml-http-request");
}

Acronyms are grouped and converted to lowercase with proper hyphenation.

CLI Flag Generation

use heck::ToKebabCase;
 
fn cli_flags() {
    // Common use: generating CLI flag names from field names
    
    struct Config {
        max_connections: u32,
        retry_timeout: u32,
        enable_logging: bool,
        output_file: String,
    }
    
    // Convert struct field names to CLI flags
    fn field_to_flag(field: &str) -> String {
        format!("--{}", field.to_kebab_case())
    }
    
    assert_eq!(field_to_flag("max_connections"), "--max-connections");
    assert_eq!(field_to_flag("retry_timeout"), "--retry-timeout");
    assert_eq!(field_to_flag("enable_logging"), "--enable-logging");
    assert_eq!(field_to_flag("output_file"), "--output-file");
}

CLI applications commonly use ToKebabCase for converting Rust field names to flag names.

Configuration Key Normalization

use heck::ToKebabCase;
use std::collections::HashMap;
 
fn config_normalization() {
    // Normalize configuration keys to kebab-case
    
    // Input config might use various conventions
    let raw_config = HashMap::from([
        ("database_url", "postgres://localhost"),
        ("maxConnections", "100"),
        ("API_KEY", "secret123"),
        ("retry_timeout_seconds", "30"),
    ]);
    
    // Normalize all keys to kebab-case
    let normalized: HashMap<String, &str> = raw_config
        .into_iter()
        .map(|(k, v)| (k.to_kebab_case(), v))
        .collect();
    
    assert_eq!(normalized.get("database-url"), Some(&"postgres://localhost"));
    assert_eq!(normalized.get("max-connections"), Some(&"100"));
    assert_eq!(normalized.get("api-key"), Some(&"secret123"));
    assert_eq!(normalized.get("retry-timeout-seconds"), Some(&"30"));
}

Configuration systems can normalize keys from various sources to consistent kebab-case.

URL Slug Generation

use heck::ToKebabCase;
 
fn url_slugs() {
    // Generate URL-friendly slugs from titles
    
    fn generate_slug(title: &str) -> String {
        title.to_kebab_case()
    }
    
    // Blog titles to slugs
    assert_eq!(
        generate_slug("How to Build a REST API"),
        "how-to-build-a-rest-api"
    );
    
    assert_eq!(
        generate_slug("Introduction to Rust Programming"),
        "introduction-to-rust-programming"
    );
    
    // Product names to slugs
    assert_eq!(
        generate_slug("Product Name With Special Characters"),
        "product-name-with-special-characters"
    );
}

URL slug generation uses kebab-case for readability and URL safety.

Integration with clap

use heck::ToKebabCase;
 
// Common pattern: derive CLI from struct fields
fn clap_integration() {
    // When building CLI apps, convert field names to kebab-case flags
    
    struct Args {
        input_file: String,
        output_directory: String,
        max_retries: u32,
        verbose_mode: bool,
    }
    
    // Generate flag names
    let flags = [
        "input_file".to_kebab_case(),
        "output_directory".to_kebab_case(),
        "max_retries".to_kebab_case(),
        "verbose_mode".to_kebab_case(),
    ];
    
    assert_eq!(flags, [
        "input-file",
        "output-directory",
        "max-retries",
        "verbose-mode"
    ]);
    
    // These become CLI flags:
    // --input-file, --output-directory, --max-retries, --verbose-mode
}

CLI frameworks like clap often use kebab-case for argument names.

Comparison with Other heck Traits

use heck::{ToKebabCase, ToSnakeCase, ToCamelCase, ToShoutySnakeCase, ToTitleCase, ToLowerCamelCase};
 
fn case_comparison() {
    let input = "my_function_name";
    
    // Each trait converts to a different case
    assert_eq!(input.to_kebab_case(), "my-function-name");         // kebab-case
    assert_eq!(input.to_snake_case(), "my_function_name");         // snake_case
    assert_eq!(input.to_camel_case(), "MyFunctionName");          // CamelCase
    assert_eq!(input.to_lower_camel_case(), "myFunctionName");    // camelCase
    assert_eq!(input.to_shouty_snake_case(), "MY_FUNCTION_NAME"); // SHOUTY_CASE
    assert_eq!(input.to_title_case(), "My Function Name");        // Title Case
    
    // From a different input
    let camel = "MyFunctionName";
    
    assert_eq!(camel.to_kebab_case(), "my-function-name");
    assert_eq!(camel.to_snake_case(), "my_function_name");
    assert_eq!(camel.to_lower_camel_case(), "myFunctionName");
}

heck provides multiple case conversion traits; ToKebabCase is one of them.

Comparison Summary

use heck::ToKebabCase;
 
fn case_comparison_table() {
    // β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    // β”‚ Input           β”‚ to_kebab_case()      β”‚ Use Case                  β”‚
    // β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
    // β”‚ "CamelCase"     β”‚ "camel-case"          β”‚ CLI flags                 β”‚
    // β”‚ "snake_case"    β”‚ "snake-case"          β”‚ Config keys               β”‚
    // β”‚ "SHOUTY_CASE"   β”‚ "shouty-case"         β”‚ Environment vars          β”‚
    // β”‚ "MixedCASE"     β”‚ "mixed-case"          β”‚ Normalization             β”‚
    // β”‚ "HTTPServer"    β”‚ "http-server"         β”‚ Acronym handling          β”‚
    // β”‚ "user_id"       β”‚ "user-id"            β”‚ API parameter names       β”‚
    // β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
}

All case styles normalize to kebab-case for consistent usage.

Edge Cases

use heck::ToKebabCase;
 
fn edge_cases() {
    // Empty string
    assert_eq!("".to_kebab_case(), "");
    
    // Single word
    assert_eq!("word".to_kebab_case(), "word");
    assert_eq!("Word".to_kebab_case(), "word");
    assert_eq!("WORD".to_kebab_case(), "word");
    
    // Already kebab-case
    assert_eq!("already-kebab".to_kebab_case(), "already-kebab");
    
    // Multiple separators
    assert_eq!("multi___underscore".to_kebab_case(), "multi-underscore");
    assert_eq!("multiple---hyphens".to_kebab_case(), "multiple-hyphens");
    
    // Numbers
    assert_eq!("v2.0.0".to_kebab_case(), "v-2-0-0");
    assert_eq!("test123".to_kebab_case(), "test-123");
    
    // Special characters (removed or converted)
    assert_eq!("hello@world".to_kebab_case(), "hello-world");
    
    // Leading/trailing separators
    assert_eq!("_private".to_kebab_case(), "private");
    assert_eq!("public_".to_kebab_case(), "public");
}

ToKebabCase handles edge cases gracefully, producing consistent output.

Environment Variable Mapping

use heck::ToKebabCase;
 
fn env_var_mapping() {
    // Convert environment variable names to config keys
    
    let env_vars = [
        "DATABASE_URL",
        "MAX_CONNECTIONS",
        "API_KEY",
        "LOG_LEVEL",
    ];
    
    let config_keys: Vec<String> = env_vars
        .iter()
        .map(|v| v.to_kebab_case())
        .collect();
    
    assert_eq!(config_keys, [
        "database-url",
        "max-connections",
        "api-key",
        "log-level"
    ]);
    
    // This enables consistent config regardless of source:
    // - Environment variables (SHOUTY_CASE)
    // - Config files (kebab-case or snake_case)
    // - CLI flags (kebab-case)
}

Environment variables in SHOUTY_CASE can be converted to kebab-case config keys.

Serde Integration

use heck::ToKebabCase;
use serde::{Deserialize, Serialize};
 
fn serde_integration() {
    // Common pattern: Rust struct with snake_case fields, serialized to kebab-case
    
    #[derive(Serialize, Deserialize)]
    struct Config {
        // Rust uses snake_case by convention
        #[serde(rename = "database-url")]
        database_url: String,
        
        #[serde(rename = "max-connections")]
        max_connections: u32,
        
        // Manual rename is tedious for many fields
    }
    
    // Alternative: generate renames with heck
    fn generate_rename(field: &str) -> String {
        format!("#[serde(rename = \"{}\")]", field.to_kebab_case())
    }
    
    // For field "database_url":
    // #[serde(rename = "database-url")]
}

ToKebabCase is useful for generating serde rename attributes.

Build Script Usage

// In a build.rs file, generate code with kebab-case names
 
use heck::ToKebabCase;
 
fn build_script_example() {
    // Generate CLI argument parsing code from struct definition
    
    let fields = [
        ("input_file", "String", "Input file path"),
        ("output_dir", "String", "Output directory"),
        ("max_retries", "u32", "Maximum retry attempts"),
        ("verbose", "bool", "Enable verbose output"),
    ];
    
    for (field, _ty, _help) in fields {
        let kebab = field.to_kebab_case();
        println!("Field '{}' becomes flag '--{}'", field, kebab);
    }
    // Output:
    // Field 'input_file' becomes flag '--input-file'
    // Field 'output_dir' becomes flag '--output-dir'
    // Field 'max_retries' becomes flag '--max-retries'
    // Field 'verbose' becomes flag '--verbose'
}

Build scripts can use ToKebabCase to generate code with consistent naming.

Complete Summary

use heck::ToKebabCase;
 
fn complete_summary() {
    // β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    // β”‚ Method              β”‚ to_kebab_case()                                 β”‚
    // β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
    // β”‚ Input               β”‚ Any string (CamelCase, snake_case, etc.)         β”‚
    // β”‚ Output              β”‚ kebab-case (lowercase with hyphens)              β”‚
    // β”‚ Word separators     β”‚ Uppercase, underscore, hyphen, space, numbers   β”‚
    // β”‚ Acronym handling    β”‚ Groups uppercase letters together                β”‚
    // β”‚ Performance         β”‚ Allocates new String                             β”‚
    // β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    
    // β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    // β”‚ Use Case            β”‚ Example                                          β”‚
    // β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
    // β”‚ CLI flags           β”‚ max_connections β†’ --max-connections             β”‚
    // β”‚ Config keys         β”‚ DatabaseURL β†’ database-url                       β”‚
    // β”‚ URL slugs           β”‚ My Blog Title β†’ my-blog-title                    β”‚
    // β”‚ Environment vars    β”‚ API_KEY β†’ api-key                                β”‚
    // β”‚ Serde renames       β”‚ field_name β†’ "field-name"                        β”‚
    // β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
    
    // Key behaviors:
    // 1. Detects word boundaries from multiple conventions
    // 2. Groups acronyms (HTTPServer β†’ http-server)
    // 3. Lowercases everything
    // 4. Joins with hyphens
    // 5. Handles edge cases (empty, single word, existing kebab)
}
 
// Key insight:
// heck::ToKebabCase provides automatic conversion from any case convention
// to kebab-case (lowercase with hyphens). It's commonly used for:
//
// - CLI argument names (max_connections β†’ --max-connections)
// - Configuration file keys (consistent format across sources)
// - URL slugs (readable, URL-safe identifiers)
// - Normalizing environment variables (API_KEY β†’ api-key)
//
// The trait handles various input conventions:
// - CamelCase / PascalCase
// - snake_case
// - SHOUTY_CASE
// - Mixed case
// - Already kebab-case (idempotent)
//
// Word boundary detection uses:
// - Uppercase letters (start of new word)
// - Underscores (separator)
// - Hyphens (separator)
// - Spaces (separator)
// - Numbers (boundary)
//
// Acronyms are handled specially:
// - HTTPServer β†’ http-server (not h-t-t-p-server)
// - XMLParser β†’ xml-parser
// - IPv4Address β†’ ipv-4-address
//
// Use ToKebabCase when you need consistent kebab-case output
// from varying input conventions.

Key insight: heck::ToKebabCase converts identifiers from any case convention to kebab-case by detecting word boundaries and joining with hyphens. It handles acronyms intelligently (grouping uppercase letters), works with multiple input formats, and produces consistent output suitable for CLI flags, configuration keys, and URL slugs. The trait is essential for normalizing names across systems that use different conventionsβ€”Rust's snake_case fields to kebab-case CLI flags, environment variables' SHOUTY_CASE to kebab-case config keys, or arbitrary strings to URL-safe slugs.