How does heck::ToShoutySnakeCase transform identifiers for constant naming conventions?
ToShoutySnakeCase converts identifiers to SHOUTY_SNAKE_CASE—uppercase letters with underscores between words—making it ideal for generating Rust constant names, configuration keys, or any identifier following the convention that "constants should be SCREAMING_SNAKE_CASE". The trait handles multiple input formats including snake_case, camelCase, PascalCase, and kebab-case, automatically detecting word boundaries and transforming them into the uppercase underscore-separated format. This is particularly useful in code generation, macros, and configuration systems where you need to programmatically generate conventional constant names from various identifier styles.
Basic Transformation
use heck::ToShoutySnakeCase;
fn main() {
// Convert from various input formats
let snake = "user_account_id".to_shouty_snake_case();
let camel = "userAccountId".to_shouty_snake_case();
let pascal = "UserAccountId".to_shouty_snake_case();
let kebab = "user-account-id".to_shouty_snake_case();
// All produce the same output
assert_eq!(snake, "USER_ACCOUNT_ID");
assert_eq!(camel, "USER_ACCOUNT_ID");
assert_eq!(pascal, "USER_ACCOUNT_ID");
assert_eq!(kebab, "USER_ACCOUNT_ID");
println!("{}", snake); // USER_ACCOUNT_ID
}ToShoutySnakeCase detects word boundaries automatically regardless of input style.
The Trait Definition
use heck::ToShoutySnakeCase;
fn main() {
// ToShoutySnakeCase is a trait that extends str
// It provides the to_shouty_snake_case() method
// Works on &str
let name: &str = "myConstantName";
let shouty: String = name.to_shouty_snake_case();
// Works on String
let owned: String = String::from("myConstantName");
let shouty: String = owned.to_shouty_snake_case();
// Returns an owned String
let result: String = "test".to_shouty_snake_case();
println!("{}", result);
}The trait is implemented for str and String, returning an owned String.
Word Boundary Detection
use heck::ToShoutySnakeCase;
fn main() {
// Underscores are word boundaries
assert_eq!("hello_world".to_shouty_snake_case(), "HELLO_WORLD");
// CamelCase: uppercase letters start new words
assert_eq!("helloWorld".to_shouty_snake_case(), "HELLO_WORLD");
assert_eq!("XMLHttpRequest".to_shouty_snake_case(), "XML_HTTP_REQUEST");
// PascalCase: similar to camelCase
assert_eq!("HelloWorld".to_shouty_snake_case(), "HELLO_WORLD");
// Kebab-case: hyphens are word boundaries
assert_eq!("hello-world".to_shouty_snake_case(), "HELLO_WORLD");
// Numbers are handled specially
assert_eq!("version2Update".to_shouty_snake_case(), "VERSION_2_UPDATE");
assert_eq!("http2_protocol".to_shouty_snake_case(), "HTTP_2_PROTOCOL");
// Multiple consecutive separators
assert_eq!("hello__world".to_shouty_snake_case(), "HELLO_WORLD");
assert_eq!("hello--world".to_shouty_snake_case(), "HELLO_WORLD");
}The transformation handles various conventions and edge cases.
Use in Procedural Macros
use heck::ToShoutySnakeCase;
// Simulating a derive macro that generates constants
fn generate_constants(struct_name: &str, field_names: &[&str]) -> String {
let mut output = String::new();
// Generate constant for struct name prefix
let prefix = struct_name.to_shouty_snake_case();
// Generate constants for each field
for field in field_names {
let const_name = format!("{}_{}", prefix, field).to_shouty_snake_case();
output.push_str(&format!(
"pub const {}: &str = \"{}.{}\";\n",
const_name, struct_name, field
));
}
output
}
fn main() {
let code = generate_constants("UserAccount", &["userId", "accountName", "createdAt"]);
println!("{}", code);
// Output:
// pub const USER_ACCOUNT_USER_ID: &str = "UserAccount.userId";
// pub const USER_ACCOUNT_ACCOUNT_NAME: &str = "UserAccount.accountName";
// pub const USER_ACCOUNT_CREATED_AT: &str = "UserAccount.createdAt";
}Procedural macros commonly use ToShoutySnakeCase for generating constant names.
Generating Configuration Keys
use heck::ToShoutySnakeCase;
fn main() {
// Environment variable names use SHOUTY_SNAKE_CASE
let config_keys = vec![
"databaseUrl",
"maxConnections",
"sessionTimeout",
"enableSsl",
];
for key in config_keys {
let env_var = key.to_shouty_snake_case();
println!("export {}=<value>", env_var);
}
// Output:
// export DATABASE_URL=<value>
// export MAX_CONNECTIONS=<value>
// export SESSION_TIMEOUT=<value>
// export ENABLE_SSL=<value>
}Environment variable naming follows the SHOUTY_SNAKE_CASE convention.
Related heck Traits
use heck::{
ToShoutySnakeCase,
ToSnakeCase,
ToLowerCamelCase,
ToUpperCamelCase,
ToKebabCase,
ToShoutyKebabCase,
};
fn main() {
let input = "userAccountId";
// SHOUTY_SNAKE_CASE: uppercase with underscores
println!("shouty_snake: {}", input.to_shouty_snake_case());
// USER_ACCOUNT_ID
// snake_case: lowercase with underscores
println!("snake: {}", input.to_snake_case());
// user_account_id
// lowerCamelCase: first word lowercase
println!("lower_camel: {}", input.to_lower_camel_case());
// userAccountId (unchanged)
// UpperCamelCase (PascalCase): all words capitalized
println!("upper_camel: {}", input.to_upper_camel_case());
// UserAccountId
// kebab-case: lowercase with hyphens
println!("kebab: {}", input.to_kebab_case());
// user-account-id
// SHOUTY-KEBAB-CASE: uppercase with hyphens
println!("shouty_kebab: {}", input.to_shouty_kebab_case());
// USER-ACCOUNT-ID
}heck provides a family of case transformation traits.
Edge Cases
use heck::ToShoutySnakeCase;
fn main() {
// Already shouty snake case (idempotent)
assert_eq!("USER_ACCOUNT_ID".to_shouty_snake_case(), "USER_ACCOUNT_ID");
// Mixed case input
assert_eq!("user_AccountID".to_shouty_snake_case(), "USER_ACCOUNT_ID");
// Single word
assert_eq!("user".to_shouty_snake_case(), "USER");
// Empty string
assert_eq!("".to_shouty_snake_case(), "");
// Acronyms
assert_eq!("parseXML".to_shouty_snake_case(), "PARSE_XML");
assert_eq!("HTTPServer".to_shouty_snake_case(), "HTTP_SERVER");
// Numbers within words
assert_eq!("item42Status".to_shouty_snake_case(), "ITEM_42_STATUS");
// Multiple underscores collapse
assert_eq!("user___name".to_shouty_snake_case(), "USER_NAME");
}The transformation handles edge cases sensibly.
Integration with serde for Serialization
use heck::ToShoutySnakeCase;
use serde::Serialize;
#[derive(Serialize)]
struct Config {
#[serde(rename = "database_url")]
database_url: String,
#[serde(rename = "max_connections")]
max_connections: u32,
}
fn main() {
// Generate environment variable names from struct fields
let fields = vec!["databaseUrl", "maxConnections"];
for field in fields {
let env_var = field.to_shouty_snake_case();
println!("Config field '{}' -> env var '{}'", field, env_var);
}
// Combine with serde for consistent naming
let config = Config {
database_url: "localhost".to_string(),
max_connections: 10,
};
let json = serde_json::to_string(&config).unwrap();
println!("Serialized: {}", json);
}Consistent naming conventions help maintain coherence across configuration systems.
Build Script Usage
// In build.rs
use heck::ToShoutySnakeCase;
fn main() {
// Generate constants from configuration
let features = vec!["enableFeatureX", "enableFeatureY"];
let mut constants = String::new();
for feature in features {
let const_name = feature.to_shouty_snake_case();
constants.push_str(&format!(
"pub const {}: &str = \"{}\";\n",
const_name, feature
));
}
// Write to generated file
std::fs::write(
std::path::Path::new("src/generated/constants.rs"),
format!("// Auto-generated\n\n{}", constants),
).unwrap();
}Build scripts can generate constants with proper naming conventions.
Comparison with Manual Conversion
use heck::ToShoutySnakeCase;
fn manual_to_shouty_snake_case(input: &str) -> String {
let mut result = String::new();
let mut prev_char = ' ';
for c in input.chars() {
if c.is_uppercase() && result.len() > 0 && prev_char != '_' {
result.push('_');
} else if c == '-' || c == ' ' {
if prev_char != '_' {
result.push('_');
}
prev_char = '_';
continue;
} else if c == '_' {
if prev_char != '_' {
result.push('_');
}
prev_char = '_';
continue;
}
result.push(c.to_ascii_uppercase());
prev_char = c;
}
result
}
fn main() {
let inputs = vec![
"userAccountId",
"XMLHttpRequest",
"my-kebab-case",
"simple",
];
for input in inputs {
let expected = input.to_shouty_snake_case();
let manual = manual_to_shouty_snake_case(input);
println!("Input: {}", input);
println!(" heck: {}", expected);
println!(" manual: {}", manual);
assert_eq!(expected, manual);
}
}Using heck avoids reinventing case conversion logic with subtle bugs.
Working with Enums
use heck::ToShoutySnakeCase;
enum Status {
PendingApproval,
InProgress,
Completed,
Failed,
}
fn main() {
// Convert enum variant names to constants
let variants = vec!["PendingApproval", "InProgress", "Completed", "Failed"];
for variant in variants {
let constant = variant.to_shouty_snake_case();
println!("pub const STATUS_{}: &str = \"{}\";", constant, variant);
}
// Output:
// pub const STATUS_PENDING_APPROVAL: &str = "PendingApproval";
// pub const STATUS_IN_PROGRESS: &str = "InProgress";
// pub const STATUS_COMPLETED: &str = "Completed";
// pub const STATUS_FAILED: &str = "Failed";
}Enum variant names commonly need conversion for serialization or external APIs.
CLI Tool Example
use heck::ToShoutySnakeCase;
use std::io::{self, BufRead, Write};
fn main() {
// Convert stdin to SHOUTY_SNAKE_CASE
let stdin = io::stdin();
let stdout = io::stdout();
let mut stdout = stdout.lock();
for line in stdin.lock().lines() {
let line = line.unwrap();
let converted = line.to_shouty_snake_case();
writeln!(stdout, "{}", converted).unwrap();
}
}
// Usage:
// $ echo "myConstantName" | cargo run
// MY_CONSTANT_NAMECLI tools can transform identifiers for various purposes.
Integration with strum
use heck::ToShoutySnakeCase;
// strum provides enum iteration; heck provides case conversion
// They work well together
fn main() {
// Simulating strum's IntoEnumIterator
#[derive(Debug)]
enum Color {
Red,
Green,
Blue,
}
let colors = vec!["Red", "Green", "Blue"];
for color in colors {
// Convert enum variant name to constant format
let constant_name = color.to_shouty_snake_case();
println!("pub const COLOR_{}: &str = \"{}\";", constant_name, color);
}
// Generate serialization helper
println!("\n// String conversion:");
for color in &["Red", "Green", "Blue"] {
let shouty = color.to_shouty_snake_case();
println!("\"{}\" => Color::{},", shouty.to_lowercase(), color);
}
}heck complements other ecosystem crates for code generation.
Real-World Example: Configuration Generator
use heck::ToShoutySnakeCase;
use std::collections::HashMap;
struct ConfigField {
name: String,
default_value: String,
description: String,
}
fn generate_config_code(fields: &[ConfigField]) -> String {
let mut output = String::new();
output.push_str("// Auto-generated configuration constants\n\n");
// Generate environment variable constants
for field in fields {
let env_var = field.name.to_shouty_snake_case();
output.push_str(&format!(
"/// {}\npub const ENV_{}: &str = \"{}\";\n\n",
field.description, env_var, field.name.to_shouty_snake_case()
));
}
// Generate default values
output.push_str("pub fn defaults() -> HashMap<&'static str, String> {\n");
output.push_str(" let mut map = HashMap::new();\n");
for field in fields {
output.push_str(&format!(
" map.insert(ENV_{}, String::from(\"{}\"));\n",
field.name.to_shouty_snake_case(),
field.default_value
));
}
output.push_str(" map\n}\n");
output
}
fn main() {
let fields = vec![
ConfigField {
name: "databaseUrl".to_string(),
default_value: "localhost:5432".to_string(),
description: "Database connection URL".to_string(),
},
ConfigField {
name: "maxConnections".to_string(),
default_value: "10".to_string(),
description: "Maximum number of database connections".to_string(),
},
ConfigField {
name: "sessionTimeout".to_string(),
default_value: "3600".to_string(),
description: "Session timeout in seconds".to_string(),
},
];
let code = generate_config_code(&fields);
println!("{}", code);
}Automatic generation of configuration constants with proper naming conventions.
Synthesis
Quick reference:
use heck::ToShoutySnakeCase;
fn main() {
// Basic conversion
assert_eq!("userId".to_shouty_snake_case(), "USER_ID");
assert_eq!("user_account_id".to_shouty_snake_case(), "USER_ACCOUNT_ID");
assert_eq!("user-account-id".to_shouty_snake_case(), "USER_ACCOUNT_ID");
assert_eq!("UserAccountId".to_shouty_snake_case(), "USER_ACCOUNT_ID");
// Word boundary detection
// - Underscores separate words
// - Hyphens separate words
// - Uppercase letters start new words
// - Numbers start new words
// Common use cases:
// 1. Generating constant names from field names
// 2. Creating environment variable names
// 3. Procedural macro code generation
// 4. Configuration key generation
// Related traits in heck:
// - ToSnakeCase: user_account_id
// - ToLowerCamelCase: userAccountId
// - ToUpperCamelCase: UserAccountId
// - ToKebabCase: user-account-id
// - ToShoutyKebabCase: USER-ACCOUNT-ID
}Key insight: ToShoutySnakeCase solves the common problem of generating conventional Rust constant names from various identifier styles without manually implementing word boundary detection. It handles the subtle cases like acronyms (XMLHttpRequest → XML_HTTP_REQUEST), numbers (version2 → VERSION_2), and mixed conventions that make manual implementation error-prone. Use it in procedural macros, build scripts, and code generators to maintain consistent SHOUTY_SNAKE_CASE naming across your project. The trait is part of a family of case-conversion utilities in heck that work together for identifier transformation needs.
