Complete Guide to Rust Constants

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

  1. Immutability: Constants are always immutable
  2. Compile-time: Evaluated at compile time
  3. Type Safety: Require explicit type annotations
  4. Inlining: Inlined at each usage location
  5. Global Scope: Can be defined in any scope
  6. 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

  1. Always annotate types - Constants require explicit types
  2. Use screaming snake case - Follow Rust naming conventions
  3. Group related constants - Use modules for organization
  4. Prefer constants over magic numbers - Improve code readability
  5. Consider static for large data - Avoid duplication for large constants
  6. Use const functions - For complex compile-time computations
  7. Document constants - Explain what they represent and why

Constants vs Statics

FeatureConstantsStatics
MemoryInlinedSingle location
MutabilityNeverCan be mutable (unsafe)
TypeAny typeAny type
InitializationCompile-timeCompile-time
DropNo dropCan have drop
AddressNo fixed addressFixed 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper