What is the purpose of heck::ToPascalCase for transforming identifiers to PascalCase naming convention?
heck::ToPascalCase transforms string identifiers from various casing conventions (snake_case, kebab-case, camelCase) into PascalCase (also called UpperCamelCase), capitalizing the first letter of each word and removing separators, making it essential for generating type names, struct names, and other identifiers that conventionally use PascalCase in Rust. The trait provides a consistent, Unicode-aware transformation that handles edge cases like consecutive separators, leading/trailing separators, and mixed casing.
Understanding PascalCase Transformation
use heck::ToPascalCase;
// PascalCase: First letter of each word capitalized, no separators
// Also called: UpperCamelCase, SturdyScribeCase, CapWords
//
// Examples:
// "hello_world" -> "HelloWorld"
// "my_struct_name" -> "MyStructName"
// "user-id" -> "UserId"
// "first_name" -> "FirstName"
fn basic_transformation() {
let input = "hello_world";
let pascal: String = input.to_pascal_case();
assert_eq!(pascal, "HelloWorld");
// The transformation:
// 1. Split on separators (_ and -)
// 2. Capitalize first letter of each word
// 3. Lowercase remaining letters
// 4. Concatenate without separators
}PascalCase capitalizes each word's first letter and removes word boundaries.
The ToPascalCase Trait
use heck::ToPascalCase;
fn trait_usage() {
// ToPascalCase is a trait that extends ToString-like types
// It's implemented for &str, String, and String references
// On &str
let pascal1: String = "hello_world".to_pascal_case();
// On String
let s = String::from("hello_world");
let pascal2: String = s.to_pascal_case();
// On &String
let pascal3: String = (&String::from("hello_world")).to_pascal_case();
assert_eq!(pascal1, "HelloWorld");
assert_eq!(pascal2, "HelloWorld");
assert_eq!(pascal3, "HelloWorld");
}The trait is implemented for common string types, making it ergonomic to use.
Converting from snake_case
use heck::ToPascalCase;
fn from_snake_case() {
// snake_case is the conventional Rust identifier format
// PascalCase is conventional for types, structs, enums
// Field/function names -> Type names
assert_eq!("user_account".to_pascal_case(), "UserAccount");
assert_eq!("http_request".to_pascal_case(), "HttpRequest");
assert_eq!("database_connection".to_pascal_case(), "DatabaseConnection");
// Common use case: converting function names to struct names
let function_name = "parse_json_response";
let struct_name = function_name.to_pascal_case();
assert_eq!(struct_name, "ParseJsonResponse");
// Multi-word identifiers
assert_eq!("read_file_to_string".to_pascal_case(), "ReadFileToString");
assert_eq!("tcp_stream_wrapper".to_pascal_case(), "TcpStreamWrapper");
}Converting from snake_case is common when generating type names from field names.
Converting from kebab-case
use heck::ToPascalCase;
fn from_kebab_case() {
// kebab-case is common in URLs, CLI flags, config files
assert_eq!("user-id".to_pascal_case(), "UserId");
assert_eq!("content-type".to_pascal_case(), "ContentType");
assert_eq!("x-request-id".to_pascal_case(), "XRequestId");
// Converting CLI arguments to type-safe names
let arg_name = "--enable-ssl";
// Strip prefix first
let type_name = arg_name.trim_start_matches('-').to_pascal_case();
assert_eq!(type_name, "EnableSsl");
// Converting HTTP headers
let header = "content-length";
let header_struct = header.to_pascal_case();
assert_eq!(header_struct, "ContentLength");
}kebab-case conversion is useful for CLI tools, HTTP headers, and configuration.
Converting from camelCase
use heck::ToPascalCase;
fn from_camel_case() {
// camelCase (lowercase first letter) to PascalCase
assert_eq!("userService".to_pascal_case(), "UserService");
assert_eq!("httpClient".to_pascal_case(), "HttpClient");
assert_eq!("jsonParser".to_pascal_case(), "JsonParser");
// The transformation:
// 1. Detect word boundaries (uppercase letters)
// 2. Split on word boundaries
// 3. Capitalize first letter of each word
// Edge case: acronyms
assert_eq!("parseXML".to_pascal_case(), "ParseXml"); // Normalized
assert_eq!("HTTPServer".to_pascal_case(), "HttpServer"); // Normalized
assert_eq!("readHTMLFile".to_pascal_case(), "ReadHtmlFile"); // Normalized
}camelCase input is normalized to PascalCase, handling acronym boundaries.
Handling Edge Cases
use heck::ToPascalCase;
fn edge_cases() {
// Empty string
assert_eq!("".to_pascal_case(), "");
// Single word
assert_eq!("hello".to_pascal_case(), "Hello");
// Already PascalCase (idempotent-ish)
assert_eq!("HelloWorld".to_pascal_case(), "HelloWorld");
// Multiple consecutive separators
assert_eq!("hello__world".to_pascal_case(), "HelloWorld");
assert_eq!("hello--world".to_pascal_case(), "HelloWorld");
assert_eq!("hello_-world".to_pascal_case(), "HelloWorld");
// Leading/trailing separators
assert_eq!("_hello_world_".to_pascal_case(), "HelloWorld");
assert_eq!("-hello-world-".to_pascal_case(), "HelloWorld");
assert_eq!("___hello___".to_pascal_case(), "Hello");
// Mixed separators
assert_eq!("hello_world-name".to_pascal_case(), "HelloWorldName");
assert_eq!("user-id-number".to_pascal_case(), "UserIdNumber");
// Numbers (treated as word boundaries)
assert_eq!("version2_api".to_pascal_case(), "Version2Api");
assert_eq!("html5_parser".to_pascal_case(), "Html5Parser");
}The transformation handles various edge cases consistently.
Unicode and Special Characters
use heck::ToPascalCase;
fn unicode_handling() {
// Unicode letters are preserved
assert_eq!("cafΓ©_menu".to_pascal_case(), "CafΓ©Menu");
assert_eq!("ΓΌber_driver".to_pascal_case(), "ΓberDriver");
// Non-alphabetic separators create word boundaries
assert_eq!("hello world".to_pascal_case(), "HelloWorld"); // Space
assert_eq!("path/to/file".to_pascal_case(), "PathToFile"); // /
// Numbers preserved
assert_eq!("user_123".to_pascal_case(), "User123");
assert_eq!("v2_api".to_pascal_case(), "V2Api");
}Unicode characters are preserved while applying the transformation rules.
Code Generation Use Cases
use heck::ToPascalCase;
// Common pattern: generating Rust code from schemas
fn generate_struct_name(table_name: &str) -> String {
// database table: user_accounts
// generated struct: UserAccounts
table_name.to_pascal_case()
}
fn generate_enum_variants() -> String {
let variants = ["active", "pending", "completed", "failed"];
let enum_variants: Vec<String> = variants
.iter()
.map(|v| v.to_pascal_case())
.collect();
// Generates: Active, Pending, Completed, Failed
format!("enum Status {{\n{}\n}}",
enum_variants
.iter()
.map(|v| format!(" {},", v))
.collect::<Vec<_>>()
.join("\n")
)
}
fn generate_builder_methods() {
// Method names -> Type names
let methods = vec!["with_name", "with_age", "with_email"];
// Generate builder pattern types
for method in methods {
let type_name = method.to_pascal_case();
println!("pub fn {}(self, value: T) -> Self {{ ... }}", method);
println!("// Related type: {}Value", type_name);
}
}Code generation from database schemas, APIs, or protocols is a primary use case.
Integration with Procedural Macros
use heck::ToPascalCase;
// In a procedural macro, converting attribute names
fn proc_macro_example() {
// Imagine parsing a derive attribute:
// #[derive(MyTrait)]
// struct my_struct; // lowercase struct name
// The macro might want to generate a trait impl with PascalCase name
let struct_name = "my_struct";
let trait_impl_name = format!("{}Trait", struct_name.to_pascal_case());
assert_eq!(trait_impl_name, "MyStructTrait");
// Or generating error types
let error_type = "parse_error";
let error_struct = error_type.to_pascal_case();
assert_eq!(error_struct, "ParseError");
}
// Derive macro helper
fn generate_from_snake_name(snake_name: &str) -> Vec<String> {
// Generate multiple related identifiers
let pascal = snake_name.to_pascal_case();
vec![
format!("struct {} {{}}", pascal), // Struct name
format!("impl {}Trait for {} {{}}", pascal, pascal), // Trait impl
format!("{}Builder", pascal), // Builder type
format!("{}Error", pascal), // Error type
]
}Procedural macros frequently need to generate PascalCase names from snake_case inputs.
Comparison with Other heck Traits
use heck::{ToPascalCase, ToSnakeCase, ToKebabCase, ToCamelCase, ToShoutySnakeCase};
fn case_comparisons() {
let input = "hello_world";
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β Trait β Output β Use Case β
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
// β ToPascalCase β HelloWorld β Types, structs, enums β
// β ToCamelCase β helloWorld β Methods, fields (JS style) β
// β ToSnakeCase β hello_world β Functions, fields (Rust) β
// β ToKebabCase β hello-world β CLI flags, URLs β
// β ToShoutySnakeCase β HELLO_WORLD β Constants β
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
assert_eq!(input.to_pascal_case(), "HelloWorld");
assert_eq!(input.to_camel_case(), "helloWorld");
assert_eq!(input.to_snake_case(), "hello_world");
assert_eq!(input.to_kebab_case(), "hello-world");
assert_eq!(input.to_shouty_snake_case(), "HELLO_WORLD");
// Round-trip conversions
let original = "hello_world";
let pascal = original.to_pascal_case();
let back_to_snake = pascal.to_snake_case();
// Note: Information loss possible (acronyms normalized)
assert_eq!(back_to_snake, "hello_world");
}
fn mixed_case_input() {
// heck normalizes various inputs consistently
let inputs = vec![
"hello_world",
"hello-world",
"helloWorld",
"HelloWorld",
"HELLO_WORLD",
];
// All normalize to same PascalCase
for input in inputs {
let pascal = input.to_pascal_case();
println!("{:20} -> {}", input, pascal);
}
}heck provides a family of case conversion traits for different conventions.
Real-World Example: Schema Generation
use heck::ToPascalCase;
// Generate Rust types from a JSON schema
fn generate_rust_types() {
let schema_fields = vec![
("user_id", "i64"),
("user_name", "String"),
("email_address", "String"),
("is_active", "bool"),
("created_at", "DateTime<Utc>"),
];
// Generate struct
let struct_name = "UserProfile".to_pascal_case();
let mut struct_code = format!("pub struct {} {{\n", struct_name);
for (field_name, field_type) in schema_fields {
// Keep snake_case for fields (Rust convention)
// Use PascalCase for any generated types
struct_code.push_str(&format!(" pub {}: {},\n", field_name, field_type));
}
struct_code.push_str("}\n");
// Generate builder
let builder_name = format!("{}Builder", struct_name.to_pascal_case());
println!("pub struct {} {{ ... }}", builder_name);
// Generate error type
let error_name = format!("{}Error", struct_name.to_pascal_case());
println!("pub enum {} {{ ... }}", error_name);
}
// Generate enum from string values
fn generate_enum_from_strings(variants: &[&str]) -> String {
let enum_name = "Status".to_pascal_case();
let variants_code: String = variants
.iter()
.map(|v| format!(" {},", v.to_pascal_case()))
.collect::<Vec<_>>()
.join("\n");
format!("pub enum {} {{\n{}\n}}", enum_name, variants_code)
}
#[test]
fn test_enum_generation() {
let variants = ["active", "pending", "completed"];
let expected = "pub enum Status {\n Active,\n Pending,\n Completed,\n}";
assert_eq!(generate_enum_from_strings(&variants), expected);
}Schema-to-code generation is a common use case for case conversion.
Converting for FFI Bindings
use heck::ToPascalCase;
// Generate Rust types from C library function names
fn generate_ffi_wrapper() {
let c_functions = vec![
"create_user_account",
"delete_user_account",
"get_user_by_id",
"update_user_status",
];
for c_func in c_functions {
// Convert to Rust struct name
let struct_name = c_func.to_pascal_case();
println!("// C function: {}", c_func);
println!("pub struct {}Args {{", struct_name);
println!(" // ... arguments");
println!("}}");
println!();
}
// Or converting C struct names
let c_structs = vec!["user_data", "config_options", "error_info"];
for c_struct in c_structs {
let rust_struct = c_struct.to_pascal_case();
println!("#[repr(C)] pub struct {} {{ /* ... */ }}", rust_struct);
}
}FFI bindings often need to convert C naming conventions to Rust conventions.
Practical Patterns
use heck::ToPascalCase;
// Pattern 1: Type alias generation
fn generate_type_alias(field_name: &str, base_type: &str) -> String {
let type_name = field_name.to_pascal_case();
format!("pub type {} = {};", type_name, base_type)
}
// Pattern 2: Error type from function name
fn generate_error_type(function_name: &str) -> String {
let base_name = function_name.trim_end_matches("_error");
format!("pub enum {}Error {{ /* ... */ }}", base_name.to_pascal_case())
}
// Pattern 3: Converting database columns to struct fields
fn db_column_to_rust(column: &str) -> (String, String) {
// Returns (field_name, type_name)
let field_name = column.to_snake_case(); // Rust convention
let type_name = column.to_pascal_case(); // Generated types
(field_name, type_name)
}
// Pattern 4: Generating module names
fn generate_module_name(feature_name: &str) -> String {
// Convention: module names are snake_case
// But we might want a PascalCase public type
let module_name = feature_name.to_snake_case();
let public_type = feature_name.to_pascal_case();
format!("pub mod {} {{ pub struct {} {{}} }}", module_name, public_type)
}
// Pattern 5: Converting REST endpoints to handler types
fn endpoint_to_handler(endpoint: &str) -> String {
// /api/users/{id} -> UsersHandler
// /api/orders/{order_id}/items -> OrderItemsHandler
let segments: Vec<&str> = endpoint
.trim_start_matches('/')
.split('/')
.filter(|s| !s.starts_with('{')) // Remove path params
.collect();
let handler_name = segments.join("_").to_pascal_case();
format!("{}Handler", handler_name)
}Multiple patterns emerge for generating consistent naming from various sources.
Complete Summary
use heck::ToPascalCase;
fn complete_summary() {
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// β Input β Output β Transformation β
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
// β hello_world β HelloWorld β Split on _, capitalize β
// β hello-world β HelloWorld β Split on -, capitalize β
// β helloWorld β HelloWorld β Split on caps, capitalize β
// β HelloWorld β HelloWorld β Already PascalCase β
// β HELLO_WORLD β HelloWorld β Split on _, normalize case β
// β _hello_world_ β HelloWorld β Strip separators, transform β
// β hello__world β HelloWorld β Collapse multiple separators β
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Key properties:
// 1. Idempotent: applying twice yields same result
// 2. Normalizing: various cases converge to PascalCase
// 3. Unicode-aware: handles non-ASCII letters
// 4. Separator-agnostic: treats _ and - equivalently
// Primary use cases:
// 1. Code generation: schema -> Rust types
// 2. Procedural macros: attribute names -> type names
// 3. FFI bindings: C naming -> Rust naming
// 4. API clients: endpoint names -> handler types
// 5. ORM: table names -> struct names
// The trait integrates with other heck traits:
// - ToSnakeCase: for functions, fields
// - ToKebabCase: for CLI flags, URLs
// - ToCamelCase: for JSON APIs
// - ToShoutySnakeCase: for constants
}
// Key insight:
// ToPascalCase provides a reliable, Unicode-aware transformation from any
// common casing convention to PascalCase. It handles edge cases (multiple
// separators, leading/trailing separators, mixed case) consistently,
// making it ideal for code generation where consistent output matters.
// The transformation is idempotent (PascalCase input returns PascalCase)
// and normalizing (various inputs produce the same PascalCase output).
// This is essential for deriving type names from field names, function names,
// database columns, or API endpoints in procedural macros and code generators.Key insight: heck::ToPascalCase solves the common problem of converting identifiers between casing conventions, specifically producing PascalCase output from any common input format (snake_case, kebab-case, camelCase, or mixed). This is essential for code generation where source identifiers (database columns, API endpoints, function names) need to become type names (structs, enums, traits). The trait handles edge casesβmultiple separators, leading/trailing separators, mixed input formatsβconsistently, producing normalized PascalCase output. Combined with other heck traits (ToSnakeCase, ToKebabCase, ToCamelCase), it provides a complete toolkit for identifier transformation in macros, build scripts, and code generators.
