Loading page…
Rust walkthroughs
Loading page…
The heck crate provides case conversion utilities for transforming strings between different naming conventions. It supports converting to snake_case, camelCase, PascalCase, kebab-case, SCREAMING_SNAKE_CASE, and more. The crate handles edge cases like acronyms, numbers, and special characters gracefully. It's particularly useful for code generators, serialization, CLI tools, and any situation where you need to transform identifiers between conventions (like converting database column names to Rust struct fields).
Key concepts:
snake_case with underscores, lowercasecamelCase with leading lowercase, no separatorsPascalCase or UpperCamelCase, each word capitalizedkebab-case with hyphens, lowercaseSCREAMING_SNAKE_CASE, all caps with underscores# Cargo.toml
[dependencies]
heck = "0.5"use heck::{ToSnakeCase, ToCamelCase, ToPascalCase, ToKebabCase, ToShoutySnakeCase};
fn main() {
let input = "hello world";
println!("Snake: {}", input.to_snake_case());
println!("Camel: {}", input.to_camel_case());
println!("Pascal: {}", input.to_pascal_case());
println!("Kebab: {}", input.to_kebab_case());
println!("Shouty: {}", input.to_shouty_snake_case());
}use heck::{
ToSnakeCase, ToCamelCase, ToPascalCase,
ToKebabCase, ToShoutySnakeCase, ToShoutyKebabCase,
ToTitleCase, ToLowerCamelCase,
};
fn main() {
let input = "hello world example";
println!("Input: {}", input);
println!("");
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!("SHOUTY_SNAKE: {}", input.to_shouty_snake_case());
println!("SHOUTY-KEBAB: {}", input.to_shouty_kebab_case());
println!("Title Case: {}", input.to_title_case());
println!("lowerCamelCase: {}", input.to_lower_camel_case());
}use heck::{
ToSnakeCase, ToCamelCase, ToPascalCase,
ToKebabCase, ToShoutySnakeCase,
};
fn main() {
// From snake_case
let snake = "user_account_settings";
println!("From snake_case: {}", snake);
println!(" camel: {}", snake.to_camel_case());
println!(" pascal: {}", snake.to_pascal_case());
println!(" kebab: {}", snake.to_kebab_case());
// From camelCase
let camel = "userAccountSettings";
println!("\nFrom camelCase: {}", camel);
println!(" snake: {}", camel.to_snake_case());
println!(" pascal: {}", camel.to_pascal_case());
println!(" kebab: {}", camel.to_kebab_case());
// From PascalCase
let pascal = "UserAccountSettings";
println!("\nFrom PascalCase: {}", pascal);
println!(" snake: {}", pascal.to_snake_case());
println!(" camel: {}", pascal.to_camel_case());
println!(" kebab: {}", pascal.to_kebab_case());
// From kebab-case
let kebab = "user-account-settings";
println!("\nFrom kebab-case: {}", kebab);
println!(" snake: {}", kebab.to_snake_case());
println!(" camel: {}", kebab.to_camel_case());
println!(" pascal: {}", kebab.to_pascal_case());
}use heck::{ToSnakeCase, ToCamelCase, ToPascalCase, ToKebabCase};
fn main() {
let inputs = vec![
"user2fa",
"oauth2client",
"version2",
"html5",
"http2",
"ipv4",
"ipv6",
"sha256",
"aes128",
];
println!("Handling numbers:");
for input in inputs {
println!("\nInput: {}", input);
println!(" snake: {}", input.to_snake_case());
println!(" camel: {}", input.to_camel_case());
println!(" pascal: {}", input.to_pascal_case());
println!(" kebab: {}", input.to_kebab_case());
}
}use heck::{ToSnakeCase, ToCamelCase, ToPascalCase, ToKebabCase};
fn main() {
let inputs = vec![
"XMLParser",
"HTTPServer",
"HTTPSConnection",
"APIResponse",
"IOError",
"URLBuilder",
"HTMLDocument",
"JSONSerializer",
"SQLDatabase",
"UUIDGenerator",
];
println!("Acronyms:");
for input in inputs {
println!("\nInput: {}", input);
println!(" snake: {}", input.to_snake_case());
println!(" camel: {}", input.to_camel_case());
println!(" pascal: {}", input.to_pascal_case());
println!(" kebab: {}", input.to_kebab_case());
}
}use heck::{ToSnakeCase, ToCamelCase, ToPascalCase, ToKebabCase};
fn main() {
let inputs = vec![
"helloWorld",
"hello_world",
"hello-world",
"HelloWorld",
"HELLO_WORLD",
"helloWorld123",
"Hello_World-Test",
"already_snake_case",
"MixedCaseAnd_snake-mix",
];
println!("Mixed inputs:");
println!("{:<25} {:<20} {:<20} {:<20}", "Input", "snake", "camel", "pascal");
println!("{}", "-".repeat(85));
for input in inputs {
println!(
"{:<25} {:<20} {:<20} {:<20}",
input,
input.to_snake_case(),
input.to_camel_case(),
input.to_pascal_case()
);
}
}use heck::ToTitleCase;
fn main() {
let inputs = vec![
"hello_world",
"camelCase",
"PascalCase",
"kebab-case",
"SCREAMING_SNAKE",
"mixed_caseExample",
];
println!("Title Case conversions:");
for input in inputs {
println!(" {} -> {}", input, input.to_title_case());
}
}use heck::ToTrainCase;
fn main() {
let inputs = vec![
"hello_world",
"camelCase",
"PascalCase",
"SCREAMING_SNAKE",
];
println!("Train Case (Upper-Kebab):");
for input in inputs {
println!(" {} -> {}", input, input.to_train_case());
}
}use heck::ToUpperCamelCase;
fn main() {
let inputs = vec![
"hello_world",
"camelCase",
"kebab-case",
"SCREAMING_SNAKE",
];
println!("Upper Camel (Pascal/Cobol):");
for input in inputs {
println!(" {} -> {}", input, input.to_upper_camel_case());
}
}use heck::ToPascalCase;
struct DatabaseColumn {
name: String,
rust_name: String,
}
impl DatabaseColumn {
fn from_db(name: &str) -> Self {
Self {
name: name.to_string(),
rust_name: name.to_pascal_case(),
}
}
}
fn generate_struct(name: &str, columns: &[&str]) -> String {
let struct_name = name.to_pascal_case();
let fields: Vec<String> = columns
.iter()
.map(|col| {
let rust_name = col.to_snake_case();
format!(" pub {}: String,", rust_name)
})
.collect();
format!(
"pub struct {} {{\n{}\n}}",
struct_name,
fields.join("\n")
)
}
fn main() {
let columns = vec![
"user_id",
"first_name",
"last_name",
"email_address",
"created_at",
"updated_at",
"is_active",
];
let rust_struct = generate_struct("user_account", &columns);
println!("{}", rust_struct);
}use heck::{ToSnakeCase, ToCamelCase};
use std::collections::HashMap;
fn transform_keys_to_snake(map: HashMap<String, String>) -> HashMap<String, String> {
map.into_iter()
.map(|(k, v)| (k.to_snake_case(), v))
.collect()
}
fn transform_keys_to_camel(map: HashMap<String, String>) -> HashMap<String, String> {
map.into_iter()
.map(|(k, v)| (k.to_camel_case(), v))
.collect()
}
fn main() {
// Simulate API response with camelCase keys
let api_response: HashMap<String, String> = vec![
("userId".to_string(), "123".to_string()),
("firstName".to_string(), "John".to_string()),
("lastName".to_string(), "Doe".to_string()),
("emailAddress".to_string(), "john@example.com".to_string()),
("createdAt".to_string(), "2024-01-01".to_string()),
("isActive".to_string(), "true".to_string()),
]
.into_iter()
.collect();
println!("Original API response (camelCase):");
for (key, value) in &api_response {
println!(" {}: {}", key, value);
}
// Convert to snake_case for internal use
let snake_case = transform_keys_to_snake(api_response.clone());
println!("\nConverted to snake_case:");
for (key, value) in &snake_case {
println!(" {}: {}", key, value);
}
}use heck::{ToKebabCase, ToSnakeCase};
struct CliFlag {
name: String,
short: char,
long: String,
env_var: String,
help: String,
}
impl CliFlag {
fn new(name: &str, help: &str) -> Self {
let kebab = name.to_kebab_case();
let short = kebab.chars().next().unwrap();
Self {
name: name.to_snake_case(),
short,
long: kebab,
env_var: name.to_shouty_snake_case(),
help: help.to_string(),
}
}
fn generate_clap_code(&self) -> String {
format!(
r#".arg(
Arg::new("{}")
.short('{}')
.long("{}")
.env("{}")
.help("{}")
)"#,
self.name,
self.short,
self.long,
self.env_var,
self.help
)
}
}
fn main() {
let flags = vec![
CliFlag::new("output_file", "Output file path"),
CliFlag::new("max_retries", "Maximum number of retries"),
CliFlag::new("api_key", "API authentication key"),
CliFlag::new("enable_ssl", "Enable SSL encryption"),
CliFlag::new("database_url", "Database connection URL"),
];
println!("Generated CLI flags:\n");
for flag in flags {
println!("{}\n", flag.generate_clap_code());
}
}
// Need to import ToShoutySnakeCase
use heck::ToShoutySnakeCase;use heck::{ToPascalCase, ToSnakeCase, ToShoutySnakeCase};
struct EnumVariant {
original: String,
pascal: String,
snake: String,
shouty: String,
}
impl EnumVariant {
fn from(input: &str) -> Self {
Self {
original: input.to_string(),
pascal: input.to_pascal_case(),
snake: input.to_snake_case(),
shouty: input.to_shouty_snake_case(),
}
}
}
fn generate_enum(name: &str, values: &[&str]) -> String {
let enum_name = name.to_pascal_case();
let variants: Vec<String> = values
.iter()
.map(|v| format!(" {},", v.to_pascal_case()))
.collect();
let match_arms: Vec<String> = values
.iter()
.map(|v| {
let pascal = v.to_pascal_case();
let snake = v.to_snake_case();
format!(" \"{}\" => {}::{},", snake, enum_name, pascal)
})
.collect();
format!(
r#"pub enum {} {{
{}}}
impl {} {{
pub fn from_str(s: &str) -> Option<Self> {{
match s {{
{}
_ => None,
}}
}}
}}"#,
enum_name,
variants.join("\n"),
enum_name,
match_arms.join("\n")
)
}
fn main() {
let statuses = vec!["pending", "in_progress", "completed", "failed", "cancelled"];
let enum_code = generate_enum("status", &statuses);
println!("{}", enum_code);
}use heck::{ToSnakeCase, ToPascalCase};
struct ColumnMapping {
db_column: String,
rust_field: String,
getter: String,
setter: String,
}
impl ColumnMapping {
fn from_db_name(db_name: &str) -> Self {
let snake = db_name.to_snake_case();
let pascal = db_name.to_pascal_case();
Self {
db_column: db_name.to_string(),
rust_field: snake,
getter: format!("get_{}", db_name.to_snake_case()),
setter: format!("set_{}", db_name.to_snake_case()),
}
}
fn generate_accessors(&self, type_name: &str) -> String {
format!(
r#" pub fn {}(&self) -> &{} {{
&self.{}
}}
pub fn {}(&mut self, value: {}) {{
self.{} = value;
}}"#,
self.getter,
type_name,
self.rust_field,
self.setter,
type_name,
self.rust_field
)
}
}
fn main() {
let columns = vec![
ColumnMapping::from_db_name("user_id"),
ColumnMapping::from_db_name("firstName"),
ColumnMapping::from_db_name("EMAIL_ADDRESS"),
ColumnMapping::from_db_name("created-at"),
];
for col in columns {
println!("DB: {:<15} Rust: {:<15} Getter: {:<20} Setter: {}",
col.db_column,
col.rust_field,
col.getter,
col.setter);
}
}use heck::{ToSnakeCase, ToCamelCase, ToPascalCase};
use serde_json::{json, Value};
fn transform_json_keys(value: Value, to_case: fn(&str) -> String) -> Value {
match value {
Value::Object(map) => {
let new_map = map
.into_iter()
.map(|(k, v)| (to_case(&k), transform_json_keys(v, to_case)))
.collect();
Value::Object(new_map)
}
Value::Array(arr) => {
Value::Array(arr.into_iter().map(|v| transform_json_keys(v, to_case)).collect())
}
other => other,
}
}
fn main() {
let data = json!({
"userName": "john_doe",
"userId": 123,
"emailAddress": "john@example.com",
"accountSettings": {
"enableNotifications": true,
"preferredLanguage": "en"
},
"orderHistory": [
{"orderId": 1, "orderDate": "2024-01-01"},
{"orderId": 2, "orderDate": "2024-01-15"}
]
});
println!("Original (camelCase):");
println!("{}\n", serde_json::to_string_pretty(&data).unwrap());
println!("Converted to snake_case:");
let snake = transform_json_keys(data.clone(), |s| s.to_snake_case());
println!("{}\n", serde_json::to_string_pretty(&snake).unwrap());
}use heck::ToKebabCase;
fn generate_slug(title: &str) -> String {
title.to_kebab_case()
}
fn generate_unique_slug(title: &str, existing: &[String]) -> String {
let base_slug = generate_slug(title);
if !existing.contains(&base_slug) {
return base_slug;
}
let mut counter = 1;
loop {
let slug = format!("{}-{}", base_slug, counter);
if !existing.contains(&slug) {
return slug;
}
counter += 1;
}
}
fn main() {
let titles = vec![
"Hello World",
"My First Blog Post",
"Rust Programming Language",
"Hello World", // Duplicate
"10 Tips for Better Code",
"API Design Best Practices",
];
let mut existing: Vec<String> = Vec::new();
for title in titles {
let slug = generate_unique_slug(title, &existing);
existing.push(slug.clone());
println!("'{}' -> '{}'", title, slug);
}
}use heck::ToShoutySnakeCase;
struct ConfigField {
name: String,
env_var: String,
default: Option<String>,
}
impl ConfigField {
fn new(name: &str, default: Option<&str>) -> Self {
Self {
name: name.to_string(),
env_var: name.to_shouty_snake_case(),
default: default.map(|s| s.to_string()),
}
}
fn generate_config_code(&self) -> String {
let default_code = match &self.default {
Some(val) => format!(", default = \"{}\"", val),
None => String::new(),
};
format!(
" #[serde(rename = \"{}\"){}]\n pub {}: String,",
self.name,
default_code,
self.name.to_snake_case()
)
}
fn generate_env_var(&self) -> String {
format!("export {}=\"\"", self.env_var)
}
}
use heck::ToSnakeCase;
fn main() {
let config_fields = vec![
ConfigField::new("database_url", Some("localhost:5432")),
ConfigField::new("api_key", None),
ConfigField::new("max_connections", Some("10")),
ConfigField::new("enable_ssl", Some("true")),
ConfigField::new("log_level", Some("info")),
];
println!("Config struct fields:\n");
for field in &config_fields {
println!("{}", field.generate_config_code());
}
println!("\n\nEnvironment variables:\n");
for field in &config_fields {
println!("{}", field.generate_env_var());
}
}use heck::{ToSnakeCase, ToCamelCase};
struct Method {
name: String,
snake: String,
camel: String,
params: Vec<(String, String)>,
return_type: String,
}
impl Method {
fn new(name: &str, params: Vec<(&str, &str)>, return_type: &str) -> Self {
Self {
name: name.to_string(),
snake: name.to_snake_case(),
camel: name.to_camel_case(),
params: params
.iter()
.map(|(n, t)| (n.to_snake_case(), t.to_string()))
.collect(),
return_type: return_type.to_string(),
}
}
fn generate_rust(&self) -> String {
let params: String = self.params
.iter()
.map(|(n, t)| format!("{}: {}", n, t))
.collect::<Vec<_>>()
.join(", ");
format!(
"fn {}({}) -> {} {{\n // TODO: implement\n }}",
self.snake, params, self.return_type
)
}
fn generate_typescript(&self) -> String {
let params: String = self.params
.iter()
.map(|(n, t)| format!("{}: {}", n.to_camel_case(), t))
.collect::<Vec<_>>()
.join(", ");
format!(
"{}({}): {} {{\n // TODO: implement\n}}",
self.camel, params, self.return_type
)
}
}
fn main() {
let methods = vec![
Method::new("get_user_by_id", vec![("user_id", "i32")], "Option<User>"),
Method::new("create_new_user", vec![("user_data", "UserData")], "Result<User>"),
Method::new("update_user_settings", vec![("user_id", "i32"), ("settings", "Settings")], "Result<()>"),
Method::new("delete_user_account", vec![("user_id", "i32")], "Result<bool>"),
];
println!("Rust methods:\n");
for method in &methods {
println!("{}\n", method.generate_rust());
}
println!("\nTypeScript methods:\n");
for method in &methods {
println!("{}\n", method.generate_typescript());
}
}use heck::{ToSnakeCase, ToCamelCase, ToPascalCase, ToKebabCase};
fn detect_case(input: &str) -> &'static str {
let has_underscore = input.contains('_');
let has_hyphen = input.contains('-');
let has_upper = input.chars().any(|c| c.is_uppercase());
let has_lower = input.chars().any(|c| c.is_lowercase());
let all_upper = input.chars().filter(|c| c.is_alphabetic()).all(|c| c.is_uppercase());
let first_upper = input.chars().next().map(|c| c.is_uppercase()).unwrap_or(false);
if has_underscore && all_upper {
"SCREAMING_SNAKE_CASE"
} else if has_underscore && has_lower {
"snake_case"
} else if has_hyphen {
"kebab-case"
} else if first_upper && has_upper {
"PascalCase"
} else if has_upper {
"camelCase"
} else {
"unknown"
}
}
fn normalize(input: &str, target: &str) -> String {
match target {
"snake" => input.to_snake_case(),
"camel" => input.to_camel_case(),
"pascal" => input.to_pascal_case(),
"kebab" => input.to_kebab_case(),
_ => input.to_string(),
}
}
fn main() {
let inputs = vec![
"user_name",
"userName",
"UserName",
"user-name",
"USER_NAME",
];
println!("Case detection and normalization:\n");
println!("{:<15} {:<20} {:<15} {:<15} {:<15}",
"Input", "Detected", "snake", "camel", "pascal");
println!("{}", "-".repeat(80));
for input in inputs {
println!(
"{:<15} {:<20} {:<15} {:<15} {:<15}",
input,
detect_case(input),
normalize(input, "snake"),
normalize(input, "camel"),
normalize(input, "pascal")
);
}
}use heck::{
ToSnakeCase, ToCamelCase, ToPascalCase,
ToKebabCase, ToShoutySnakeCase, ToTitleCase,
};
struct CaseConverter {
input: String,
}
impl CaseConverter {
fn new(input: &str) -> Self {
Self { input: input.to_string() }
}
fn to_all_cases(&self) -> CaseOutput {
CaseOutput {
original: self.input.clone(),
snake: self.input.to_snake_case(),
camel: self.input.to_camel_case(),
pascal: self.input.to_pascal_case(),
kebab: self.input.to_kebab_case(),
shouty: self.input.to_shouty_snake_case(),
title: self.input.to_title_case(),
}
}
}
struct CaseOutput {
original: String,
snake: String,
camel: String,
pascal: String,
kebab: String,
shouty: String,
title: String,
}
impl CaseOutput {
fn print_table(&self) {
println!("Original: {}", self.original);
println!("snake_case: {}", self.snake);
println!("camelCase: {}", self.camel);
println!("PascalCase: {}", self.pascal);
println!("kebab-case: {}", self.kebab);
println!("SHOUTY_SNAKE: {}", self.shouty);
println!("Title Case: {}", self.title);
}
}
fn main() {
let inputs = vec![
"helloWorld",
"user_account_settings",
"APIResponseHandler",
"some-mixed-INPUT",
];
for input in inputs {
println!("\n{}", "=".repeat(50));
let converter = CaseConverter::new(input);
let output = converter.to_all_cases();
output.print_table();
}
}to_snake_case() — converts to lower_case_with_underscoresto_camel_case() — converts to lowerCamelCaseto_pascal_case() / to_upper_camel_case() — converts to UpperCamelCaseto_kebab_case() — converts to lower-case-with-hyphensto_shouty_snake_case() — converts to UPPER_CASE_WITH_UNDERSCORESto_shouty_kebab_case() — converts to UPPER-CASE-WITH-HYPHENSto_title_case() — converts to Title Caseto_train_case() — converts to Train-Case