How does heck::ToKebabCase differ from ToSnakeCase for identifier conversion conventions?
ToKebabCase converts identifiers to kebab-case using hyphens (kebab-case) while ToSnakeCase converts to snake_case using underscores (snake_case). Both traits handle word boundary detection identically but differ in the separator they insert between words, making them suited for different conventions: kebab-case for URLs, CSS, and configuration keys, and snake_case for programming language identifiers, database columns, and file names.
The heck Crate
use heck::{ToKebabCase, ToSnakeCase};
fn heck_overview() {
// heck provides case conversion traits for strings
// It detects word boundaries and applies transformations
let input = "HelloWorld";
// ToKebabCase: hello-world
let kebab = input.to_kebab_case();
// ToSnakeCase: hello_world
let snake = input.to_snake_case();
// Same input, different separators
println!("Kebab: {}", kebab); // hello-world
println!("Snake: {}", snake); // hello_world
}The heck crate provides consistent case conversion with automatic word boundary detection.
ToKebabCase: Hyphen-Separated Words
use heck::ToKebabCase;
fn kebab_case_examples() {
// ToKebabCase converts to kebab-case (lowercase with hyphens)
// CamelCase to kebab-case
assert_eq!("helloWorld".to_kebab_case(), "hello-world");
assert_eq!("HelloWorld".to_kebab_case(), "hello-world");
assert_eq!("XMLHttpRequest".to_kebab_case(), "xml-http-request");
// Snake_case to kebab-case
assert_eq!("hello_world".to_kebab_case(), "hello-world");
assert_eq!("user_name_id".to_kebab_case(), "user-name-id");
// Already kebab-case stays kebab-case
assert_eq!("already-kebab".to_kebab_case(), "already-kebab");
// SCREAMING_SNAKE_CASE to kebab-case
assert_eq!("MAX_VALUE".to_kebab_case(), "max-value");
assert_eq!("CONTENT_TYPE".to_kebab_case(), "content-type");
// Mixed cases
assert_eq!("mixedCase_With_underscores".to_kebab_case(), "mixed-case-with-underscores");
}ToKebabCase produces lowercase words separated by hyphens.
ToSnakeCase: Underscore-Separated Words
use heck::ToSnakeCase;
fn snake_case_examples() {
// ToSnakeCase converts to snake_case (lowercase with underscores)
// CamelCase to snake_case
assert_eq!("helloWorld".to_snake_case(), "hello_world");
assert_eq!("HelloWorld".to_snake_case(), "hello_world");
assert_eq!("XMLHttpRequest".to_snake_case(), "xml_http_request");
// Already snake_case stays snake_case
assert_eq!("hello_world".to_snake_case(), "hello_world");
assert_eq!("user_name_id".to_snake_case(), "user_name_id");
// kebab-case to snake_case
assert_eq!("kebab-case".to_snake_case(), "kebab_case");
assert_eq!("content-type".to_snake_case(), "content_type");
// SCREAMING_SNAKE_CASE to snake_case
assert_eq!("MAX_VALUE".to_snake_case(), "max_value");
assert_eq!("CONTENT_TYPE".to_snake_case(), "content_type");
// Mixed cases
assert_eq!("mixedCase-With-hyphens".to_snake_case(), "mixed_case_with_hyphens");
}ToSnakeCase produces lowercase words separated by underscores.
Word Boundary Detection
use heck::{ToKebabCase, ToSnakeCase};
fn word_boundaries() {
// Both traits use the same word boundary detection algorithm
// Only the separator differs
// Boundary types:
// 1. CamelCase boundaries
assert_eq!("CamelCase".to_kebab_case(), "camel-case");
assert_eq!("CamelCase".to_snake_case(), "camel_case");
// 2. Underscores become word boundaries
assert_eq!("hello_world".to_kebab_case(), "hello-world");
assert_eq!("hello_world".to_snake_case(), "hello_world");
// 3. Hyphens become word boundaries
assert_eq!("hello-world".to_kebab_case(), "hello-world");
assert_eq!("hello-world".to_snake_case(), "hello_world");
// 4. Spaces become word boundaries
assert_eq!("hello world".to_kebab_case(), "hello-world");
assert_eq!("hello world".to_snake_case(), "hello_world");
// 5. Numbers are separated
assert_eq!("version2Update".to_kebab_case(), "version-2-update");
assert_eq!("version2Update".to_snake_case(), "version_2_update");
// 6. Acronyms are split
assert_eq!("XMLHttpRequest".to_kebab_case(), "xml-http-request");
assert_eq!("XMLHttpRequest".to_snake_case(), "xml_http_request");
}Both traits detect word boundaries identically; only the output separator differs.
The Key Difference: Separators
use heck::{ToKebabCase, ToSnakeCase};
fn separator_difference() {
let input = "HelloWorld";
// ToKebabCase: hyphens
let kebab = input.to_kebab_case();
assert_eq!(kebab, "hello-world");
// ToSnakeCase: underscores
let snake = input.to_snake_case();
assert_eq!(snake, "hello_world");
// Visual difference
println!("Kebab: hello-world"); // Uses '-'
println!("Snake: hello_world"); // Uses '_'
// The only difference is the separator character
// Both: lowercase words, same boundary detection
}The sole difference is the separator: hyphens for kebab-case, underscores for snake_case.
Converting Between Cases
use heck::{ToKebabCase, ToSnakeCase};
fn converting_between() {
// kebab-case to snake_case
let kebab = "user-name-id";
let snake = kebab.to_snake_case();
assert_eq!(snake, "user_name_id");
// snake_case to kebab-case
let snake = "user_name_id";
let kebab = snake.to_kebab_case();
assert_eq!(kebab, "user-name-id");
// Both normalize to their target case
// Regardless of input format
// Mixed input to kebab-case
assert_eq!("user_name-id Mixed".to_kebab_case(), "user-name-id-mixed");
// Mixed input to snake_case
assert_eq!("user_name-id Mixed".to_snake_case(), "user_name_id_mixed");
}Both traits can convert from any case to their target case.
Use Cases for Kebab-Case
use heck::ToKebabCase;
fn kebab_use_cases() {
// 1. URLs and URL slugs
let title = "How to Write Good Code";
let slug = title.to_kebab_case();
// how-to-write-good-code
// 2. CSS class names
let component = "PrimaryButton";
let css_class = component.to_kebab_case();
// primary-button
// 3. HTML data attributes
let attr = "userId";
let data_attr = format!("data-{}", attr.to_kebab_case());
// data-user-id
// 4. Configuration keys
let config_key = "maxRetries".to_kebab_case();
// max-retries
// 5. CLI argument names
let arg = "outputDirectory".to_kebab_case();
// output-directory
// 6. HTTP headers (custom)
let header = "X-Request-Id".to_kebab_case();
// x-request-id (already kebab-case normalized)
}Kebab-case is standard for URLs, CSS, HTML attributes, and configuration files.
Use Cases for Snake Case
use heck::ToSnakeCase;
fn snake_use_cases() {
// 1. Programming language identifiers
let class_name = "UserAccount";
let variable = class_name.to_snake_case();
// user_account (Python, Ruby convention)
// 2. Database column names
let field = "createdAt";
let column = field.to_snake_case();
// created_at
// 3. File names
let module = "MyModule";
let file_name = module.to_snake_case();
// my_module.rs
// 4. Environment variables
let config = "databaseUrl";
let env_var = config.to_snake_case().to_uppercase();
// DATABASE_URL
// 5. JSON keys (in some conventions)
let json_key = "userName".to_snake_case();
// user_name
// 6. Rust identifiers
let struct_name = "HttpClient";
let method_name = struct_name.to_snake_case();
// http_client
}Snake_case is standard for programming identifiers, database columns, and file names.
Related Traits in heck
use heck::{
ToKebabCase, ToSnakeCase,
ToLowerCamelCase, ToUpperCamelCase,
ToShoutySnakeCase, ToTitleCase,
ToPascalCase, ToTrainCase,
};
fn related_traits() {
let input = "hello_world";
// Lowercase separators
assert_eq!(input.to_kebab_case(), "hello-world"); // kebab-case
assert_eq!(input.to_snake_case(), "hello_world"); // snake_case
// CamelCase variants
assert_eq!(input.to_lower_camel_case(), "helloWorld"); // camelCase
assert_eq!(input.to_upper_camel_case(), "HelloWorld"); // PascalCase
assert_eq!(input.to_pascal_case(), "HelloWorld"); // Same as UpperCamel
// Other variants
assert_eq!(input.to_shouty_snake_case(), "HELLO_WORLD"); // SCREAMING_SNAKE
assert_eq!(input.to_title_case(), "Hello World"); // Title Case
assert_eq!(input.to_train_case(), "Hello-World"); // Train-Case
}heck provides many case conversion traits following similar patterns.
Handling Edge Cases
use heck::{ToKebabCase, ToSnakeCase};
fn edge_cases() {
// Empty string
assert_eq!("".to_kebab_case(), "");
assert_eq!("".to_snake_case(), "");
// Single word
assert_eq!("hello".to_kebab_case(), "hello");
assert_eq!("hello".to_snake_case(), "hello");
// Numbers
assert_eq!("version2".to_kebab_case(), "version-2");
assert_eq!("version2".to_snake_case(), "version_2");
// Consecutive capitals
assert_eq!("XMLParser".to_kebab_case(), "xml-parser");
assert_eq!("XMLParser".to_snake_case(), "xml_parser");
// Multiple underscores/hyphens normalize
assert_eq!("hello__world".to_kebab_case(), "hello-world");
assert_eq!("hello__world".to_snake_case(), "hello_world");
// Leading/trailing separators
assert_eq!("_hello_world_".to_kebab_case(), "hello-world");
assert_eq!("_hello_world_".to_snake_case(), "hello_world");
}Both traits normalize input, removing duplicate and leading/trailing separators.
Performance Considerations
use heck::{ToKebabCase, ToSnakeCase};
fn performance() {
// Both traits allocate a new String
// The algorithm:
// 1. Iterate through input characters
// 2. Detect word boundaries
// 3. Build output with separators
// O(n) time complexity where n = input length
// O(n) space for the output string
// For hot code paths, consider:
// 1. Caching the result
let cached_kebab = "user_account_id".to_kebab_case();
// Reuse cached_kebab instead of reconverting
// 2. Using &'static str for constants
const KEY: &str = "user-account-id"; // Already kebab-case
// 3. Avoiding conversion in loops
let items = vec!["UserAccount", "UserId", "UserName"];
let kebab_items: Vec<_> = items.iter()
.map(|s| s.to_kebab_case())
.collect();
}Both conversions allocate new strings with O(n) complexity.
Practical Integration Examples
use heck::{ToKebabCase, ToSnakeCase};
// URL generation
fn generate_url(title: &str) -> String {
format!("/posts/{}", title.to_kebab_case())
}
// Database column mapping
fn to_column_name(field: &str) -> String {
field.to_snake_case()
}
// CLI argument parsing
fn to_cli_flag(option: &str) -> String {
format!("--{}", option.to_kebab_case())
}
// Environment variable naming
fn to_env_var(config: &str) -> String {
config.to_snake_case().to_uppercase()
}
fn integration_example() {
// URL slugs
assert_eq!(generate_url("Hello World Post"), "/posts/hello-world-post");
// Database columns
assert_eq!(to_column_name("userId"), "user_id");
// CLI flags
assert_eq!(to_cli_flag("outputDirectory"), "--output-directory");
// Environment variables
assert_eq!(to_env_var("databaseUrl"), "DATABASE_URL");
}Both cases serve different conventions in real applications.
Synthesis
Comparison table:
| Aspect | ToKebabCase |
ToSnakeCase |
|---|---|---|
| Separator | Hyphen (-) |
Underscore (_) |
| Output | kebab-case |
snake_case |
| Word detection | Same algorithm | Same algorithm |
| Common use | URLs, CSS, HTML attrs | Variables, DB columns, files |
| Languages | JavaScript, HTML, config | Python, Ruby, Rust, SQL |
When to use ToKebabCase:
// URLs and web slugs
let slug = "My Blog Post Title".to_kebab_case(); // my-blog-post-title
// CSS class names
let class_name = "PrimaryButton".to_kebab_case(); // primary-button
// HTML attributes
let attr = format!("data-{}", "userId".to_kebab_case()); // data-user-id
// Configuration files (YAML, TOML keys)
let key = "maxConnections".to_kebab_case(); // max-connectionsWhen to use ToSnakeCase:
// Programming language identifiers
let var_name = "userAccount".to_snake_case(); // user_account
// Database column names
let column = "createdAt".to_snake_case(); // created_at
// File names
let filename = format!("{}.rs", "MyModule".to_snake_case()); // my_module.rs
// Environment variables
let env_var = "apiKey".to_snake_case().to_uppercase(); // API_KEYKey insight: ToKebabCase and ToSnakeCase are essentially the same algorithm with different output separators. Both detect word boundaries from CamelCase, spaces, existing hyphens/underscores, and numeric transitions, then output lowercase words. ToKebabCase uses hyphens as separators, making it ideal for web contexts like URLs, CSS classes, and HTML attributes. ToSnakeCase uses underscores, making it appropriate for programming language identifiers, database schemas, and system file names. The choice depends entirely on the convention of your target context—use kebab-case for web/config contexts and snake_case for code/database contexts. Both can convert from any input format to their normalized output form.
