Loading page…
Rust walkthroughs
Loading page…
The heck crate provides utilities for converting strings between different case styles—snake_case, camelCase, PascalCase, kebab-case, and more. It handles the complexities of word boundary detection, ensuring consistent and correct transformations. This is invaluable for code generation, configuration file handling, API serialization, and any situation where naming conventions need to be normalized or transformed.
Key concepts:
.to_snake_case(), .to_camel_case(), etc. on any string# Cargo.toml
[dependencies]
heck = "0.5"use heck::ToSnakeCase;
fn main() {
let input = "helloWorld";
let output = input.to_snake_case();
println!("{}", output); // "hello_world"
}use heck::{
ToSnakeCase, ToCamelCase, ToPascalCase,
ToKebabCase, ToShoutySnakeCase, ToTitleCase,
};
fn main() {
let input = "hello_world";
// Snake case: hello_world
println!("snake_case: {}", input.to_snake_case());
// Camel case: helloWorld
println!("camelCase: {}", input.to_camel_case());
// Pascal case: HelloWorld
println!("PascalCase: {}", input.to_pascal_case());
// Kebab case: hello-world
println!("kebab-case: {}", input.to_kebab_case());
// Shouty snake: HELLO_WORLD
println!("SHOUTY_SNAKE: {}", input.to_shouty_snake_case());
// Title case: Hello World
println!("Title Case: {}", input.to_title_case());
}use heck::{
ToSnakeCase, ToCamelCase, ToPascalCase,
ToKebabCase, ToShoutySnakeCase, ToShoutyKebabCase,
ToTitleCase, ToLowerCamelCase, ToUpperCamelCase,
};
fn demonstrate_conversions(input: &str) {
println!("\nInput: '{}'", 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!(" SHOUTY_SNAKE: {}", input.to_shouty_snake_case());
println!(" SHOUTY-KEBAB: {}", input.to_shouty_kebab_case());
println!(" Title Case: {}", input.to_title_case());
}
fn main() {
demonstrate_conversions("helloWorld");
demonstrate_conversions("hello_world");
demonstrate_conversions("HelloWorld");
demonstrate_conversions("hello-world");
demonstrate_conversions("HELLO_WORLD");
demonstrate_conversions("Hello World");
}use heck::ToSnakeCase;
fn main() {
// Various inputs to snake_case
let examples = [
"helloWorld", // camelCase
"HelloWorld", // PascalCase
"hello-world", // kebab-case
"HELLO_WORLD", // SHOUTY_SNAKE
"Hello World", // Title Case
"helloWorld123", // with numbers
"XMLHttpRequest", // acronyms
"userID", // ending ID
"someHTTPServer", // embedded acronym
];
for example in examples {
println!("{:20} -> {}", example, example.to_snake_case());
}
}use heck::{ToCamelCase, ToPascalCase, ToLowerCamelCase, ToUpperCamelCase};
fn main() {
let inputs = [
"hello_world",
"user_id",
"xml_http_request",
"api_key",
"some_value_here",
];
println!("Input -> camelCase / PascalCase");
println!("================================");
for input in inputs {
println!("{} -> {} / {}",
input,
input.to_camel_case(),
input.to_pascal_case()
);
}
// to_lower_camel_case is same as to_camel_case
// to_upper_camel_case is same as to_pascal_case
println!("\nAliases:");
println!("lower_camel: {}", "hello_world".to_lower_camel_case());
println!("upper_camel: {}", "hello_world".to_upper_camel_case());
}use heck::{ToKebabCase, ToShoutyKebabCase};
fn main() {
let inputs = [
"helloWorld",
"HelloWorld",
"hello_world",
"HELLO_WORLD",
"SomeLongName",
];
println!("Input -> kebab-case / SHOUTY-KEBAB");
println!("====================================");
for input in inputs {
println!("{} -> {} / {}",
input,
input.to_kebab_case(),
input.to_shouty_kebab_case()
);
}
// Common use: URL slugs
let titles = [
"My First Blog Post",
"Rust Programming Tips",
"How to Use heck",
];
println!("\nURL Slugs:");
for title in titles {
println!(" {} -> {}", title, title.to_kebab_case());
}
}use heck::ToTitleCase;
fn main() {
let inputs = [
"hello_world",
"helloWorld",
"HELLO_WORLD",
"some-mixed-case",
"xmlHttpRequest",
];
for input in inputs {
println!("{} -> {}", input, input.to_title_case());
}
}use heck::ToTrainCase;
fn main() {
let inputs = [
"hello_world",
"helloWorld",
"HelloWorld",
"some-mixed-case",
];
for input in inputs {
println!("{} -> {}", input, input.to_train_case());
}
// Output: Hello-World, Hello-World, Hello-World, Some-Mixed-Case
}use heck::ToKebabCase;
#[derive(Debug)]
enum HttpMethod {
Get,
Post,
Put,
Delete,
Patch,
Head,
Options,
}
impl HttpMethod {
fn to_kebab(&self) -> String {
let debug = format!("{:?}", self);
debug.to_kebab_case()
}
}
fn main() {
for method in [
HttpMethod::Get,
HttpMethod::Post,
HttpMethod::Put,
HttpMethod::Delete,
HttpMethod::Patch,
] {
println!("{:?} -> {}", method, method.to_kebab());
}
}use heck::{ToSnakeCase, ToPascalCase};
struct Field {
name: String,
type_name: String,
}
struct Struct {
name: String,
fields: Vec<Field>,
}
fn generate_rust_struct(s: &Struct) -> String {
let struct_name = s.name.to_pascal_case();
let fields: String = s.fields
.iter()
.map(|f| {
let field_name = f.name.to_snake_case();
format!(" {}: {},", field_name, f.type_name)
})
.collect::<Vec<_>>()
.join("\n");
format!("struct {} {{\n{}\n}}", struct_name, fields)
}
fn generate_builder_methods(s: &Struct) -> String {
let struct_name = s.name.to_pascal_case();
s.fields
.iter()
.map(|f| {
let field_name = f.name.to_snake_case();
let method_name = field_name.clone();
format!(
r#" pub fn {}(mut self, value: {}) -> Self {{
self.{} = value;
self
}}"#,
method_name, f.type_name, field_name
)
})
.collect::<Vec<_>>()
.join("\n\n")
}
fn main() {
let user_struct = Struct {
name: "user_profile".to_string(),
fields: vec![
Field { name: "firstName".to_string(), type_name: "String".to_string() },
Field { name: "lastName".to_string(), type_name: "String".to_string() },
Field { name: "emailAddress".to_string(), type_name: "String".to_string() },
Field { name: "isActive".to_string(), type_name: "bool".to_string() },
],
};
println!("{}\n", generate_rust_struct(&user_struct));
println!("// Builder methods:\n{}", generate_builder_methods(&user_struct));
}use heck::ToSnakeCase;
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct User {
#[serde(rename = "user_id")]
user_id: u32,
#[serde(rename = "first_name")]
first_name: String,
#[serde(rename = "last_name")]
last_name: String,
#[serde(rename = "created_at")]
created_at: String,
}
// Helper function to generate column names
fn to_column_name(field: &str) -> String {
field.to_snake_case()
}
fn main() {
let fields = ["userId", "firstName", "lastName", "createdAt", "isActive"];
println!("Field -> Database Column");
println!("========================");
for field in fields {
println!("{:15} -> {}", field, to_column_name(field));
}
}use heck::{ToKebabCase, ToSnakeCase};
// Convert struct field names to CLI argument names
fn field_to_cli_arg(field: &str) -> String {
format!("--{}", field.to_kebab_case())
}
// Convert CLI arg back to struct field
fn cli_arg_to_field(arg: &str) -> String {
arg.trim_start_matches("--")
.to_snake_case()
}
fn main() {
let field_names = [
"outputFile",
"inputFormat",
"maxRetries",
"enableVerbose",
"configPath",
];
println!("Field -> CLI Argument -> Field");
println!("================================");
for field in field_names {
let cli_arg = field_to_cli_arg(field);
let back = cli_arg_to_field(&cli_arg);
println!("{:15} -> {:20} -> {}", field, cli_arg, back);
}
}use heck::ToKebabCase;
fn generate_slug(title: &str) -> String {
title.to_kebab_case()
}
fn main() {
let blog_titles = [
"Hello World!",
"Rust Programming: A Deep Dive",
"How to Install Rust on macOS",
"Understanding Ownership in Rust",
"WebAssembly with Rust in 2024",
];
println!("Blog Title -> URL Slug");
println!("=======================");
for title in blog_titles {
println!("{} -> {}", title, generate_slug(title));
}
}use heck::{ToSnakeCase, ToCamelCase};
use serde_json::{json, Value};
// Convert JSON keys to snake_case (database format)
fn to_db_format(json: &Value) -> Value {
match json {
Value::Object(map) => {
let new_map: serde_json::Map<String, Value> = map
.into_iter()
.map(|(k, v)| (k.to_snake_case(), to_db_format(v)))
.collect();
Value::Object(new_map)
}
Value::Array(arr) => {
Value::Array(arr.iter().map(to_db_format).collect())
}
other => other.clone(),
}
}
// Convert JSON keys to camelCase (API format)
fn to_api_format(json: &Value) -> Value {
match json {\n Value::Object(map) => {
let new_map: serde_json::Map<String, Value> = map
.into_iter()
.map(|(k, v)| (k.to_camel_case(), to_api_format(v)))
.collect();
Value::Object(new_map)
}
Value::Array(arr) => {
Value::Array(arr.iter().map(to_api_format).collect())
}
other => other.clone(),
}
}
fn main() {
let api_response = json!({
"userId": 123,
"firstName": "John",
"lastName": "Doe",
"emailAddress": "john@example.com",
"profileSettings": {
"enableNotifications": true,
"preferredLanguage": "en"
}
});
println!("API Format (camelCase):");
println!("{}\n", serde_json::to_string_pretty(&api_response).unwrap());
let db_format = to_db_format(&api_response);
println!("DB Format (snake_case):");
println!("{}", serde_json::to_string_pretty(&db_format).unwrap());
}use heck::StrIteratorExt;
fn main() {
let input = "hello_world_example";
// Iterate over words in the string
println!("Words in '{}':", input);
for word in input.split_word_bounds() {
println!(" {:?}", word);
}
// Custom transformation: UPPERCASE.WITH.DOTS
let custom: String = input
.split_word_bounds()
.map(|w| w.to_uppercase())
.collect::<Vec<_>>()
.join(".");
println!("\nCustom format: {}", custom);
// Custom: First letter of each word
let initials: String = input
.split_word_bounds()
.filter_map(|w| w.chars().next())
.collect();
println!("Initials: {}", initials);
}use heck::ToSnakeCase;
fn main() {
// Heck handles various input formats intelligently
let mixed_inputs = [
"AlreadySnakeCase",
"camelCaseInput",
"kebab-case-input",
"SHOUTY_SNAKE_INPUT",
"Title Case Input",
"mixed_Case-Input",
"JSON2XMLConverter",
"XMLHttpRequest",
"parseURL",
"getUserID",
];
println!("Mixed Input -> snake_case");
println!("==========================");
for input in mixed_inputs {
println!("{:25} -> {}", input, input.to_snake_case());
}
}use heck::{ToKebabCase, ToSnakeCase};
struct ConfigKey {
name: String,
default: String,
description: String,
}
impl ConfigKey {
fn env_var(&self) -> String {
self.name.to_shouty_snake_case()
}
fn config_key(&self) -> String {
self.name.to_kebab_case()
}
fn field_name(&self) -> String {
self.name.to_snake_case()
}
}
trait ToShoutySnakeCase {
fn to_shouty_snake_case(&self) -> String;
}
impl ToShoutySnakeCase for str {
fn to_shouty_snake_case(&self) -> String {
self.to_snake_case().to_uppercase()
}
}
fn main() {
let configs = [
ConfigKey {
name: "serverPort".to_string(),
default: "8080".to_string(),
description: "Port for the server".to_string(),
},
ConfigKey {
name: "databaseUrl".to_string(),
default: "localhost".to_string(),
description: "Database connection URL".to_string(),
},
ConfigKey {
name: "maxConnections".to_string(),
default: "10".to_string(),
description: "Maximum database connections".to_string(),
},
];
println!("Config Field | Env Variable | Config Key");
println!("-------------|-----------------|----------------");
for config in &configs {
println!(
"{:12} | {:15} | {}",
config.field_name(),
config.env_var(),
config.config_key()
);
}
}use heck::{ToSnakeCase, ToCamelCase};
trait CaseConvertible: AsRef<str> {
fn as_snake(&self) -> String {
self.as_ref().to_snake_case()
}
fn as_camel(&self) -> String {
self.as_ref().to_camel_case()
}
fn as_pascal(&self) -> String {
self.as_ref().to_pascal_case()
}
fn as_kebab(&self) -> String {
self.as_ref().to_kebab_case()
}
}
impl CaseConvertible for str {}
impl CaseConvertible for String {}
fn process_name<T: CaseConvertible>(name: &T) {
println!("Original: {}", name.as_ref());
println!(" snake: {}", name.as_snake());
println!(" camel: {}", name.as_camel());
println!(" pascal: {}", name.as_pascal());
println!(" kebab: {}", name.as_kebab());
}
fn main() {
process_name(&"helloWorld");
println!();
process_name(&String::from("some_value"));
}use heck::{ToPascalCase, ToSnakeCase, ToCamelCase};
struct ApiEndpoint {
path: String,
method: String,
operation_id: String,
}
impl ApiEndpoint {
fn handler_name(&self) -> String {
self.operation_id.to_snake_case()
}
fn struct_name(&self) -> String {
self.operation_id.to_pascal_case()
}
fn route_name(&self) -> String {
self.path
.trim_start_matches('/')
.replace("/", "_")
.replace("{", "")
.replace("}", "")
.to_snake_case()
}
}
fn main() {
let endpoints = [
ApiEndpoint {
path: "/users".to_string(),
method: "GET".to_string(),
operation_id: "listUsers".to_string(),
},
ApiEndpoint {
path: "/users/{id}".to_string(),
method: "GET".to_string(),
operation_id: "getUserById".to_string(),
},
ApiEndpoint {
path: "/users/{userId}/posts".to_string(),
method: "POST".to_string(),
operation_id: "createUserPost".to_string(),
},
];
println!("Operation ID -> Handler / Struct / Route");
println!("===========================================");
for endpoint in &endpoints {
println!(
"{:15} -> {} / {} / {}",
endpoint.operation_id,
endpoint.handler_name(),
endpoint.struct_name(),
endpoint.route_name()
);
}
}use heck::ToSnakeCase;
fn main() {
// All allocations happen in the output String
// Input string is not modified
let input = "helloWorld";
let output = input.to_snake_case();
println!("Original: {}", input); // Still "helloWorld"
println!("Converted: {}", output); // "hello_world"
// For repeated conversions, consider caching
let cached_names: Vec<(String, String)> = [
"userId", "firstName", "lastName", "createdAt"
]
.iter()
.map(|&s| (s.to_string(), s.to_snake_case()))
.collect();
for (original, snake) in &cached_names {
println!("{} -> {}", original, snake);
}
}to_snake_case() converts to hello_world formatto_camel_case() converts to helloWorld format (lowerCamel)to_pascal_case() / to_upper_camel_case() converts to HelloWorld formatto_kebab_case() converts to hello-world formatto_shouty_snake_case() converts to HELLO_WORLD formatto_title_case() converts to Hello World format&str, String, and any type implementing AsRef<str>