Introduction to Constants in Rust
Constants in Rust are values that are bound to a name and cannot be changed. They represent a fundamental concept in programming for defining values that remain the same throughout a program's execution. Rust's approach to constants emphasizes compile-time evaluation, type safety, and clear semantics.
Key Concepts
- Immutable: Constants can never be modified
- Compile-time: Evaluated at compile time
- Global Scope: Can be declared in any scope
- Type Annotation: Always require explicit type annotations
- No Fixed Address: Inlined at each usage location
- Naming Convention: Screaming snake case (e.g.,
MAX_POINTS)
1. Basic Constants
Declaring Constants
// Basic constant declaration const MAX_POINTS: u32 = 100_000; const PI: f64 = 3.14159265359; const APP_NAME: &str = "My Rust Application"; const DEBUG_MODE: bool = true; fn main() { // Using constants println!("Max points: {}", MAX_POINTS); println!("PI: {}", PI); println!("App name: {}", APP_NAME); println!("Debug mode: {}", DEBUG_MODE); // Constants can be used in expressions let area = PI * 5.0 * 5.0; let half_max = MAX_POINTS / 2; println!("Area: {}", area); println!("Half max: {}", half_max); } Constants in Different Scopes
// Global scope constant const GLOBAL_CONST: i32 = 42; fn main() { println!("Global: {}", GLOBAL_CONST); // Constant in function scope const LOCAL_CONST: i32 = 100; println!("Local: {}", LOCAL_CONST); // Constant in block scope { const BLOCK_CONST: i32 = 200; println!("Block: {}", BLOCK_CONST); } // println!("{}", BLOCK_CONST); // Error: not in scope // Constants can shadow each other const SHADOW: i32 = 1; println!("Shadow 1: {}", SHADOW); { const SHADOW: i32 = 2; println!("Shadow 2: {}", SHADOW); } println!("Shadow 3: {}", SHADOW); } 2. Constant Evaluation
Compile-Time Evaluation
// Constants must be evaluable at compile time const SECONDS_IN_HOUR: u32 = 60 * 60; const SECONDS_IN_DAY: u32 = SECONDS_IN_HOUR * 24; const SECONDS_IN_WEEK: u32 = SECONDS_IN_DAY * 7; // Complex constant expressions const ARRAY_SIZE: usize = 5 * 4 * 3 * 2; const LARGE_NUMBER: i64 = 2_i64.pow(20); const MASK: u8 = 0b1010_1010; const HEX_MASK: u32 = 0xFF00FF00; fn main() { println!("Seconds in hour: {}", SECONDS_IN_HOUR); println!("Seconds in day: {}", SECONDS_IN_DAY); println!("Seconds in week: {}", SECONDS_IN_WEEK); println!("Array size: {}", ARRAY_SIZE); println!("Large number: {}", LARGE_NUMBER); println!("Mask: {:b}", MASK); // Constants in array sizes let array: [i32; ARRAY_SIZE] = [0; ARRAY_SIZE]; println!("Array length: {}", array.len()); } Const Functions
// Const functions can be called at compile time const fn multiply(a: i32, b: i32) -> i32 { a * b } const fn factorial(n: u64) -> u64 { let mut result = 1; let mut i = 1; // Limited control flow in const functions while i <= n { result *= i; i += 1; } result } const fn power(base: u64, exp: u32) -> u64 { let mut result = 1; let mut i = 0; while i < exp { result *= base; i += 1; } result } // Using const functions in constants const MULTIPLIED: i32 = multiply(5, 6); const FACTORIAL_5: u64 = factorial(5); const POWER_2_10: u64 = power(2, 10); fn main() { println!("Multiplied: {}", MULTIPLIED); println!("Factorial of 5: {}", FACTORIAL_5); println!("2^10: {}", POWER_2_10); // Const functions can also be called at runtime let runtime_val = multiply(10, 20); println!("Runtime multiply: {}", runtime_val); } 3. Constant vs Static
Comparing Constants and Statics
// Constant - inlined at each usage const CONST_VAL: i32 = 42; // Static - single memory location static STATIC_VAL: i32 = 42; // Mutable static (requires unsafe) static mut MUTABLE_STATIC: i32 = 0; // Static with complex type static CONFIG: Config = Config { name: "default", version: 1, }; struct Config { name: &'static str, version: u32, } fn main() { // Constants are inlined println!("Constant: {}", CONST_VAL); println!("Constant address: {:p}", &CONST_VAL); // Each usage may have different address // Statics have fixed address println!("Static: {}", STATIC_VAL); println!("Static address: {:p}", &STATIC_VAL); // Multiple references to static point to same location let ref1 = &STATIC_VAL; let ref2 = &STATIC_VAL; println!("ref1: {:p}, ref2: {:p}", ref1, ref2); // Mutable statics require unsafe unsafe { MUTABLE_STATIC += 1; println!("Mutable static: {}", MUTABLE_STATIC); } // Static with complex type println!("Config: {} v{}", CONFIG.name, CONFIG.version); } // Key differences: // - Constants: Inlined, no fixed memory location // - Statics: Single memory location, can be mutable (unsafe) // - Constants: Only primitives and const functions // - Statics: Can have destructors, can be mutable When to Use Constants vs Statics
// Use constants for: // - Mathematical constants const PI: f64 = 3.141592653589793; const E: f64 = 2.718281828459045; // - Configuration values const MAX_CONNECTIONS: u32 = 100; const TIMEOUT_SECONDS: u64 = 30; const BUFFER_SIZE: usize = 1024; // - Magic numbers with meaningful names const HTTP_OK: u16 = 200; const HTTP_NOT_FOUND: u16 = 404; const HTTP_INTERNAL_ERROR: u16 = 500; // Use statics for: // - Large data that shouldn't be duplicated static LARGE_LOOKUP_TABLE: [u32; 1024] = [0; 1024]; // - Global state (with Mutex for thread safety) use std::sync::Mutex; static GLOBAL_COUNTER: Mutex<u32> = Mutex::new(0); // - Lazy initialization patterns static LAZY_DATA: once_cell::sync::Lazy<Vec<String>> = once_cell::sync::Lazy::new(|| { vec!["data1".to_string(), "data2".to_string()] }); fn main() { // Using constants println!("PI: {}", PI); println!("Buffer size: {} bytes", BUFFER_SIZE); // Using statics println!("Lookup table first element: {}", LARGE_LOOKUP_TABLE[0]); // Thread-safe static mutation let mut counter = GLOBAL_COUNTER.lock().unwrap(); *counter += 1; println!("Global counter: {}", *counter); // Lazy static println!("Lazy data: {:?}", *LAZY_DATA); } 4. Constant Expressions
Allowed Operations in Constants
// Basic arithmetic const SUM: i32 = 10 + 20; const DIFFERENCE: i32 = 100 - 30; const PRODUCT: i32 = 5 * 6; const QUOTIENT: i32 = 100 / 5; const REMAINDER: i32 = 17 % 5; const NEGATED: i32 = -42; // Bitwise operations const BIT_AND: u8 = 0b1010 & 0b1100; // 0b1000 const BIT_OR: u8 = 0b1010 | 0b1100; // 0b1110 const BIT_XOR: u8 = 0b1010 ^ 0b1100; // 0b0110 const BIT_SHL: u8 = 0b0001 << 3; // 0b1000 const BIT_SHR: u8 = 0b1000 >> 2; // 0b0010 const BIT_NOT: u8 = !0b1010; // Bitwise complement // Logical operations (for bool) const TRUE_VAL: bool = true; const FALSE_VAL: bool = false; const AND_RESULT: bool = true && false; const OR_RESULT: bool = true || false; const NOT_RESULT: bool = !true; // Comparisons const IS_EQUAL: bool = 42 == 42; const IS_NOT_EQUAL: bool = 42 != 43; const IS_GREATER: bool = 100 > 50; const IS_LESS: bool = 50 < 100; const IS_GREATER_EQ: bool = 50 >= 50; const IS_LESS_EQ: bool = 50 <= 100; // Type casting const CASTED: i64 = 42_i32 as i64; const CHAR_FROM_U8: char = 65 as char; // 'A' const BOOL_FROM_INT: bool = 1 != 0; // Not direct cast, but expression fn main() { println!("Sum: {}", SUM); println!("Bit AND: {:b}", BIT_AND); println!("AND result: {}", AND_RESULT); println!("Is equal: {}", IS_EQUAL); println!("Casted: {}", CASTED); } Complex Constant Expressions
// Arrays in constants const NUMBERS: [i32; 5] = [1, 2, 3, 4, 5]; const ZEROS: [i32; 10] = [0; 10]; // Tuples in constants const POINT: (i32, i32) = (10, 20); const RGB: (u8, u8, u8) = (255, 128, 0); // Strings in constants const GREETING: &str = "Hello, world!"; const EMPTY_STRING: &str = ""; // Option in constants const SOME_VALUE: Option<i32> = Some(42); const NONE_VALUE: Option<i32> = None; // Result in constants const OK_RESULT: Result<i32, &str> = Ok(42); const ERR_RESULT: Result<i32, &str> = Err("error"); // Complex expressions const COMPUTED_ARRAY: [i32; 3] = [ 1 + 2, 3 * 4, 10 - 5, ]; const NESTED_TUPLE: (i32, (i32, i32)) = (1, (2, 3)); fn main() { println!("Numbers: {:?}", NUMBERS); println!("Point: {:?}", POINT); println!("Greeting: {}", GREETING); println!("Some value: {:?}", SOME_VALUE); println!("Computed array: {:?}", COMPUTED_ARRAY); println!("Nested tuple: {:?}", NESTED_TUPLE); // Using constants in patterns match 42 { SOME_VALUE => println!("Matched 42!"), _ => println!("No match"), } } 5. Constants in Different Contexts
Constants in Enums
enum HttpStatus { Ok = 200, NotFound = 404, InternalError = 500, } // Constants for enum variants const OK_CODE: u16 = HttpStatus::Ok as u16; const NOT_FOUND_CODE: u16 = HttpStatus::NotFound as u16; // Enum with associated constants enum Planet { Mercury, Venus, Earth, Mars, } impl Planet { // Associated constants const COUNT: usize = 4; const NAMES: [&'static str; 4] = ["Mercury", "Venus", "Earth", "Mars"]; fn gravity(&self) -> f64 { match self { Planet::Mercury => 3.7, Planet::Venus => 8.9, Planet::Earth => 9.8, Planet::Mars => 3.7, } } } fn main() { println!("OK code: {}", OK_CODE); println!("Not found: {}", NOT_FOUND_CODE); println!("Number of planets: {}", Planet::COUNT); println!("Planet names: {:?}", Planet::NAMES); let earth = Planet::Earth; println!("Earth gravity: {} m/s²", earth.gravity()); } Constants in Structs
struct Circle { radius: f64, } impl Circle { // Associated constants const PI: f64 = 3.141592653589793; const DEFAULT_RADIUS: f64 = 1.0; const UNITS: &'static str = "meters"; fn new(radius: f64) -> Self { Circle { radius } } fn area(&self) -> f64 { Circle::PI * self.radius * self.radius } fn default() -> Self { Circle { radius: Circle::DEFAULT_RADIUS, } } } // Constants in struct fields (not directly, but as defaults) struct Config { host: &'static str, port: u16, timeout: u64, } impl Config { const DEFAULT_HOST: &'static str = "localhost"; const DEFAULT_PORT: u16 = 8080; const DEFAULT_TIMEOUT: u64 = 30; fn default() -> Self { Config { host: Config::DEFAULT_HOST, port: Config::DEFAULT_PORT, timeout: Config::DEFAULT_TIMEOUT, } } } fn main() { println!("PI: {}", Circle::PI); println!("Units: {}", Circle::UNITS); let circle = Circle::default(); println!("Default circle area: {}", circle.area()); let config = Config::default(); println!("Config: {}:{} (timeout: {}s)", config.host, config.port, config.timeout); } Constants in Traits
trait MathConstants { const PI: f64; const E: f64; const PHI: f64; } impl MathConstants for f64 { const PI: f64 = 3.141592653589793; const E: f64 = 2.718281828459045; const PHI: f64 = 1.618033988749895; } impl MathConstants for f32 { const PI: f32 = 3.1415927; const E: f32 = 2.7182818; const PHI: f32 = 1.618034; } trait WithDefault { const DEFAULT: Self; } impl WithDefault for i32 { const DEFAULT: Self = 0; } impl WithDefault for String { const DEFAULT: Self = String::new(); } impl WithDefault for bool { const DEFAULT: Self = false; } fn main() { println!("f64 PI: {}", f64::PI); println!("f32 PI: {}", f32::PI); println!("i32 default: {}", i32::DEFAULT); println!("bool default: {}", bool::DEFAULT); } 6. Advanced Constant Patterns
Constant Generic Parameters
// Using constants in generic parameters struct Array<T, const N: usize> { data: [T; N], } impl<T, const N: usize> Array<T, N> { fn new(data: [T; N]) -> Self { Array { data } } fn len(&self) -> usize { N } } // Const generics with expressions fn sum_array<const N: usize>(arr: [i32; N]) -> i32 { let mut sum = 0; for i in 0..N { sum += arr[i]; } sum } // Const generics with defaults struct Buffer<const SIZE: usize = 1024> { data: [u8; SIZE], pos: usize, } impl<const SIZE: usize> Buffer<SIZE> { fn new() -> Self { Buffer { data: [0; SIZE], pos: 0, } } } fn main() { let arr = Array::new([1, 2, 3, 4, 5]); println!("Array length: {}", arr.len()); let sum = sum_array([1, 2, 3, 4, 5]); println!("Sum: {}", sum); // Using const generics with different sizes let buffer1: Buffer<512> = Buffer::new(); let buffer2: Buffer<2048> = Buffer::new(); let buffer3: Buffer = Buffer::new(); // Uses default 1024 println!("Buffer sizes: {}, {}, {}", std::mem::size_of_val(&buffer1), std::mem::size_of_val(&buffer2), std::mem::size_of_val(&buffer3)); } Conditional Constants with cfg
// Platform-specific constants #[cfg(target_os = "windows")] const LINE_ENDING: &str = "\r\n"; #[cfg(target_os = "linux")] const LINE_ENDING: &str = "\n"; #[cfg(target_os = "macos")] const LINE_ENDING: &str = "\r"; // Feature-dependent constants #[cfg(feature = "debug")] const LOG_LEVEL: &str = "debug"; #[cfg(not(feature = "debug"))] const LOG_LEVEL: &str = "info"; // Architecture-specific constants #[cfg(target_arch = "x86_64")] const ARCH: &str = "x86_64"; #[cfg(target_arch = "aarch64")] const ARCH: &str = "ARM64"; // Environment-dependent constants #[cfg(debug_assertions)] const BUILD_TYPE: &str = "debug"; #[cfg(not(debug_assertions))] const BUILD_TYPE: &str = "release"; fn main() { println!("Line ending: {:?}", LINE_ENDING); println!("Log level: {}", LOG_LEVEL); println!("Architecture: {}", ARCH); println!("Build type: {}", BUILD_TYPE); } Recursive Constants
// Recursive constants (careful with infinite recursion) const fn factorial(n: u64) -> u64 { match n { 0 | 1 => 1, _ => n * factorial(n - 1), } } const fn fibonacci(n: u64) -> u64 { if n <= 1 { n } else { fibonacci(n - 1) + fibonacci(n - 2) } } // Using recursive const functions const FACT_10: u64 = factorial(10); const FIB_10: u64 = fibonacci(10); fn main() { println!("Factorial of 10: {}", FACT_10); println!("Fibonacci of 10: {}", FIB_10); // These are computed at compile time, no runtime cost println!("Factorial of 20: {}", factorial(20)); } 7. Constants in Pattern Matching
Using Constants in Patterns
// Constants in match patterns const RED: u8 = 0; const GREEN: u8 = 1; const BLUE: u8 = 2; const ALPHA: u8 = 3; fn get_color_name(index: u8) -> &'static str { match index { RED => "Red", GREEN => "Green", BLUE => "Blue", ALPHA => "Alpha", _ => "Unknown", } } // Constants in range patterns const MIN_VALUE: i32 = 0; const MAX_VALUE: i32 = 100; const THRESHOLD: i32 = 50; fn categorize(value: i32) -> &'static str { match value { MIN_VALUE..=THRESHOLD => "Low", THRESHOLD + 1..=MAX_VALUE => "High", _ => "Out of range", } } // Constants in if let patterns const SOME_CONST: Option<i32> = Some(42); fn main() { println!("Color at 1: {}", get_color_name(1)); println!("Color at 3: {}", get_color_name(3)); println!("Categorize 25: {}", categorize(25)); println!("Categorize 75: {}", categorize(75)); let value = Some(42); if let SOME_CONST = value { println!("Matched the constant!"); } } 8. Performance Considerations
Inlining Behavior
// Constants are inlined at compile time const INLINED_VALUE: i32 = 42; // This function will have the constant inlined fn use_constant() -> i32 { INLINED_VALUE // Replaced with 42 at compile time } // Static has a single memory location static STATIC_VALUE: i32 = 42; fn use_static() -> i32 { STATIC_VALUE // Loads from memory location } // Compile-time evaluation const COMPLEX_CONST: i32 = { let mut result = 0; let mut i = 0; while i < 10 { result += i; i += 1; } result }; fn main() { // These look similar but have different runtime behavior println!("Constant: {}", use_constant()); println!("Static: {}", use_static()); // The complex const is fully computed at compile time println!("Complex const: {}", COMPLEX_CONST); // Assembly difference (conceptual): // Constant: mov eax, 42 // Static: mov eax, [rip + __STATIC_VALUE] } Memory Usage
// Constants don't occupy memory at runtime (inlined) const LARGE_CONSTANT: [u8; 1024] = [0; 1024]; // Static occupies memory throughout program lifetime static LARGE_STATIC: [u8; 1024] = [0; 1024]; // Multiple uses of constant - each usage inlines the value fn multiple_uses_constant() { // Each of these might create a new copy at the usage site let a = LARGE_CONSTANT[0]; let b = LARGE_CONSTANT[512]; let c = LARGE_CONSTANT[1023]; println!("{}, {}, {}", a, b, c); } // Multiple uses of static - all refer to same memory fn multiple_uses_static() { // All refer to the same memory location let a = LARGE_STATIC[0]; let b = LARGE_STATIC[512]; let c = LARGE_STATIC[1023]; println!("{}, {}, {}", a, b, c); } fn main() { // For small values, inlining is efficient // For large values, consider using static to avoid duplication multiple_uses_constant(); multiple_uses_static(); } 9. Best Practices and Patterns
Naming Conventions
// Constants use SCREAMING_SNAKE_CASE const MAX_CONNECTIONS: u32 = 100; const DEFAULT_TIMEOUT_SECONDS: u64 = 30; const API_BASE_URL: &str = "https://api.example.com/v1"; // Type aliases for complex constant types type ErrorCode = u32; const ERROR_NOT_FOUND: ErrorCode = 404; const ERROR_UNAUTHORIZED: ErrorCode = 401; // Group related constants mod http { pub const OK: u16 = 200; pub const CREATED: u16 = 201; pub const ACCEPTED: u16 = 202; pub const BAD_REQUEST: u16 = 400; pub const UNAUTHORIZED: u16 = 401; pub const NOT_FOUND: u16 = 404; } mod limits { pub const MAX_NAME_LENGTH: usize = 100; pub const MAX_DESCRIPTION_LENGTH: usize = 1000; pub const MAX_ITEMS_PER_PAGE: usize = 50; } fn main() { println!("HTTP OK: {}", http::OK); println!("Max name length: {}", limits::MAX_NAME_LENGTH); } Configuration Pattern
// Configuration constants module mod config { // Environment-based configuration pub const APP_NAME: &str = env!("CARGO_PKG_NAME"); pub const APP_VERSION: &str = env!("CARGO_PKG_VERSION"); // Feature flags as constants pub const FEATURE_AUTH_ENABLED: bool = true; pub const FEATURE_LOGGING_ENABLED: bool = cfg!(feature = "logging"); // Database configuration pub const DB_MAX_CONNECTIONS: u32 = 10; pub const DB_TIMEOUT_SECONDS: u64 = 5; // API configuration pub const API_RATE_LIMIT: u32 = 100; pub const API_TIMEOUT_SECONDS: u64 = 30; // Feature-dependent defaults #[cfg(debug_assertions)] pub const LOG_LEVEL: &str = "debug"; #[cfg(not(debug_assertions))] pub const LOG_LEVEL: &str = "info"; } // Usage in application fn main() { println!("Starting {} v{}", config::APP_NAME, config::APP_VERSION); println!("Log level: {}", config::LOG_LEVEL); if config::FEATURE_AUTH_ENABLED { println!("Authentication enabled"); } println!("DB connections: {}", config::DB_MAX_CONNECTIONS); } Magic Number Replacement
// Bad: Magic numbers fn calculate_discount_bad(price: f64, customer_type: u8) -> f64 { if customer_type == 1 { price * 0.9 // What is 1? What is 0.9? } else if customer_type == 2 { price * 0.85 // Hard to understand and maintain } else { price } } // Good: Named constants const CUSTOMER_TYPE_REGULAR: u8 = 1; const CUSTOMER_TYPE_PREMIUM: u8 = 2; const CUSTOMER_TYPE_VIP: u8 = 3; const DISCOUNT_REGULAR: f64 = 0.90; const DISCOUNT_PREMIUM: f64 = 0.85; const DISCOUNT_VIP: f64 = 0.80; fn calculate_discount_good(price: f64, customer_type: u8) -> f64 { match customer_type { CUSTOMER_TYPE_REGULAR => price * DISCOUNT_REGULAR, CUSTOMER_TYPE_PREMIUM => price * DISCOUNT_PREMIUM, CUSTOMER_TYPE_VIP => price * DISCOUNT_VIP, _ => price, } } // Even better: Use enum #[derive(Debug, Clone, Copy)] enum CustomerTier { Regular, Premium, VIP, } impl CustomerTier { const fn discount(&self) -> f64 { match self { CustomerTier::Regular => 0.90, CustomerTier::Premium => 0.85, CustomerTier::VIP => 0.80, } } } fn main() { let price = 100.0; println!("Regular: {}", calculate_discount_good(price, CUSTOMER_TYPE_REGULAR)); println!("Premium: {}", calculate_discount_good(price, CUSTOMER_TYPE_PREMIUM)); let tier = CustomerTier::VIP; println!("VIP: {}", price * tier.discount()); } 10. Common Pitfalls and Solutions
Type Mismatches
// Wrong: Type mismatch // const BAD_CONST: u8 = 300; // Error: 300 doesn't fit in u8 // Correct: Use appropriate type const GOOD_CONST: u16 = 300; // Wrong: No type annotation // const BAD = 42; // Error: need type annotation // Correct: Always annotate const GOOD: i32 = 42; // Wrong: Runtime computation // const BAD: i32 = std::time::SystemTime::now() // .duration_since(std::time::UNIX_EPOCH) // .unwrap() // .as_secs() as i32; // Error: not constant // Correct: Only compile-time operations const GOOD_COMPUTED: i32 = 42 * 2 + 10; fn main() { println!("Good const: {}", GOOD); println!("Good computed: {}", GOOD_COMPUTED); } Shadowing and Visibility
// Module with private constant mod inner { const PRIVATE_CONST: i32 = 42; // Private by default pub const PUBLIC_CONST: i32 = 100; // Public // Public constant with private type is impossible // struct PrivateType; // pub const BAD: PrivateType = PrivateType; // Error } // Constant shadowing const SHADOW: i32 = 1; fn shadow_example() { const SHADOW: i32 = 2; // Shadows outer constant println!("Inner shadow: {}", SHADOW); { const SHADOW: i32 = 3; // Shadows again println!("Block shadow: {}", SHADOW); } println!("Back to function shadow: {}", SHADOW); } fn main() { println!("Outer shadow: {}", SHADOW); shadow_example(); println!("Back to outer: {}", SHADOW); // println!("{}", inner::PRIVATE_CONST); // Error: private println!("Public const: {}", inner::PUBLIC_CONST); } Circular Dependencies
// Bad: Circular dependency // const A: i32 = B + 1; // const B: i32 = A - 1; // Error: circular dependency // Good: Define in proper order const BASE: i32 = 10; const A: i32 = BASE * 2; const B: i32 = A - 5; // Complex but valid dependencies const fn compute_x() -> i32 { 5 } const X: i32 = compute_x(); const Y: i32 = X * 2; const Z: i32 = Y + X; fn main() { println!("A: {}, B: {}", A, B); println!("X: {}, Y: {}, Z: {}", X, Y, Z); } Conclusion
Constants in Rust provide a powerful mechanism for defining immutable, compile-time evaluated values:
Key Takeaways
- Immutability: Constants are always immutable
- Compile-time: Evaluated at compile time
- Type Safety: Require explicit type annotations
- Inlining: Inlined at each usage location
- Global Scope: Can be defined in any scope
- Naming Convention: Use
SCREAMING_SNAKE_CASE
When to Use Constants
- Mathematical constants (PI, E, etc.)
- Configuration values (timeouts, limits, etc.)
- Magic number replacement (HTTP status codes, error codes)
- Array sizes and compile-time known values
- Default values for types
- Feature flags and compile-time configuration
Best Practices
- Always annotate types - Constants require explicit types
- Use screaming snake case - Follow Rust naming conventions
- Group related constants - Use modules for organization
- Prefer constants over magic numbers - Improve code readability
- Consider static for large data - Avoid duplication for large constants
- Use const functions - For complex compile-time computations
- Document constants - Explain what they represent and why
Constants vs Statics
| Feature | Constants | Statics |
|---|---|---|
| Memory | Inlined | Single location |
| Mutability | Never | Can be mutable (unsafe) |
| Type | Any type | Any type |
| Initialization | Compile-time | Compile-time |
| Drop | No drop | Can have drop |
| Address | No fixed address | Fixed address |
Constants are fundamental to writing clear, maintainable Rust code. They help eliminate magic numbers, centralize configuration, and enable compile-time optimizations while maintaining type safety and clarity.