Loading pageā¦
Rust walkthroughs
Loading pageā¦
strum::EnumCount to get the number of enum variants at compile time?strum::EnumCount provides a COUNT constant containing the number of enum variants, computed at compile time through procedural macros. Use it when you need the variant count as a constant for array sizing, validation boundaries, or compile-time assertionsāsituations where runtime iteration over variants would be inefficient or impossible. The key advantage is having VARIANT_COUNT available as a const value that the compiler can use for optimizations, unlike iter().count() which computes at runtime. Avoid EnumCount when the count is only needed for informational display or when the enum has many variants with significant compile-time cost from the macro expansion.
use strum::EnumCount;
#[derive(Debug, Clone, EnumCount)]
enum Color {
Red,
Green,
Blue,
}
fn main() {
println!("Number of colors: {}", Color::COUNT);
// Number of colors: 3
}The #[derive(EnumCount)] macro generates a COUNT constant.
use strum::EnumCount;
#[derive(Debug, Clone, Copy, EnumCount)]
enum Direction {
North,
East,
South,
West,
}
// Use COUNT to size arrays appropriately
fn main() {
// Array sized exactly for one value per variant
let mut direction_counts: [u32; Direction::COUNT] = [0; Direction::COUNT];
let movements = [Direction::North, Direction::South, Direction::East, Direction::North];
for dir in movements {
direction_counts[dir as usize] += 1;
}
println!("North: {}, East: {}, South: {}, West: {}",
direction_counts[0], direction_counts[1],
direction_counts[2], direction_counts[3]);
}COUNT enables compile-time array sizing with exact variant capacity.
use strum::EnumCount;
#[derive(Debug, Clone, Copy, EnumCount)]
enum Status {
Pending,
Processing,
Completed,
Failed,
}
// Assert at compile time that we have expected variants
const _: () = assert!(Status::COUNT == 4, "Status enum should have exactly 4 variants");
// This pattern catches accidental variant additions/removals
fn main() {
let status_names: [&str; Status::COUNT] = ["Pending", "Processing", "Completed", "Failed"];
println!("All {} statuses defined", Status::COUNT);
}Compile-time assertions catch enum changes during build.
use strum::{EnumCount, IntoEnumIterator};
#[derive(Debug, Clone, Copy, EnumCount, IntoEnumIterator)]
enum Priority {
Low,
Medium,
High,
}
fn main() {
// Using EnumCount - compile time constant
println!("EnumCount: {}", Priority::COUNT);
// Using iterator - runtime computation
let runtime_count = Priority::iter().count();
println!("Runtime count: {}", runtime_count);
// COUNT is evaluated at compile time
// iter().count() is evaluated at runtime
}EnumCount gives a constant; iter().count() computes each time.
use strum::EnumCount;
#[derive(Debug, Clone, Copy, EnumCount)]
enum Resource {
Wood,
Stone,
Gold,
}
// Compile-time sized array enables stack allocation
fn main() {
let resource_amounts: [u64; Resource::COUNT] = [100, 50, 25];
// Stack-allocated, no heap allocation needed
let mut total: u64 = resource_amounts.iter().sum();
println!("Total resources: {}", total);
}Exact sizing enables stack-allocated arrays without vectors.
use strum::EnumCount;
#[derive(Debug, Clone, Copy, EnumCount)]
enum Level {
Beginner,
Intermediate,
Advanced,
}
fn main() {
// strum::EnumCount
const COUNT1: usize = Level::COUNT;
// std::mem::variant_count (nightly feature)
// const COUNT2: usize = std::mem::variant_count::<Level>();
println!("Level count: {}", COUNT1);
}std::mem::variant_count is similar but requires nightly; EnumCount works on stable.
use strum::EnumCount;
#[derive(Debug, Clone, Copy, EnumCount)]
enum ErrorCode {
NetworkTimeout,
InvalidInput,
Unauthorized,
NotFound,
InternalError,
}
// Use case 1: Fixed-size arrays for per-variant data
fn get_error_messages() -> [&'static str; ErrorCode::COUNT] {
[
"Network timeout occurred",
"Invalid input provided",
"Unauthorized access",
"Resource not found",
"Internal server error",
]
}
// Use case 2: Validation boundaries
fn is_valid_error_code(code: u8) -> bool {
(code as usize) < ErrorCode::COUNT
}
// Use case 3: Compile-time size checks
fn main() {
const MESSAGES: [&str; ErrorCode::COUNT] = [
"Network timeout occurred",
"Invalid input provided",
"Unauthorized access",
"Resource not found",
"Internal server error",
];
// Array length matches COUNT exactly
for (i, msg) in MESSAGES.iter().enumerate() {
println!("Error {}: {}", i, msg);
}
}Use EnumCount for compile-time sized structures and boundary checks.
use strum::{EnumCount, IntoEnumIterator};
#[derive(Debug, Clone, EnumCount, IntoEnumIterator)]
enum HttpStatus {
Ok,
NotFound,
ServerError,
}
fn main() {
// DON'T use EnumCount just for display
println!("We have {} statuses", HttpStatus::COUNT);
// Just use the literal or a constant instead
// DON'T use EnumCount when you need to iterate anyway
for status in HttpStatus::iter() {
println!("{:?}", status);
}
// If iterating, iter().count() is free since you're already iterating
// DON'T use EnumCount for dynamic enums
// EnumCount is compile-time; can't handle dynamic variant counts
}Avoid EnumCount for simple display or when already iterating.
use strum::EnumCount;
#[derive(Debug, Clone, Copy, EnumCount)]
enum StatType {
Strength,
Dexterity,
Constitution,
Intelligence,
Wisdom,
Charisma,
}
struct Character {
// Fixed-size array sized by variant count
stats: [u8; StatType::COUNT],
}
impl Character {
fn new() -> Self {
Self {
stats: [10; StatType::COUNT],
}
}
fn get_stat(&self, stat: StatType) -> u8 {
self.stats[stat as usize]
}
fn set_stat(&mut self, stat: StatType, value: u8) {
self.stats[stat as usize] = value;
}
}
fn main() {
let mut char = Character::new();
char.set_stat(StatType::Strength, 18);
char.set_stat(StatType::Intelligence, 14);
println!("STR: {}, INT: {}",
char.get_stat(StatType::Strength),
char.get_stat(StatType::Intelligence));
}COUNT enables direct array indexing by variant.
use strum::EnumCount;
#[derive(Debug, Clone, Copy, EnumCount)]
enum Tile {
Grass,
Water,
Sand,
Rock,
}
fn main() {
// Array exactly sized for tile types
let tile_colors: [u32; Tile::COUNT] = [
0x00FF00, // Grass - green
0x0000FF, // Water - blue
0xFFFF00, // Sand - yellow
0x808080, // Rock - gray
];
// This would fail to compile if Tile::COUNT doesn't match array length
let _colors: [u32; Tile::COUNT] = tile_colors;
// Compile-time guarantee that indexing won't panic
fn get_color(tile: Tile) -> u32 {
let colors: [u32; Tile::COUNT] = [
0x00FF00, 0x0000FF, 0xFFFF00, 0x808080
];
colors[tile as usize] // Safe: index < COUNT guaranteed
}
}The array length must match COUNT, catching mismatches at compile time.
use strum::{EnumCount, IntoEnumIterator};
#[derive(Debug, Clone, Copy, EnumCount, IntoEnumIterator)]
enum Flag {
A,
B,
C,
}
fn main() {
// COUNT for sizing
let mut flag_values: [bool; Flag::COUNT] = [false; Flag::COUNT];
// iter() for iteration
for flag in Flag::iter() {
flag_values[flag as usize] = true;
}
// Both together: exact sizing + clean iteration
println!("Flags set: {:?}", flag_values);
// COUNT matches iterator count
assert_eq!(Flag::COUNT, Flag::iter().count());
}EnumCount and IntoEnumIterator work well together.
use strum::EnumCount;
#[derive(Debug, Clone, Copy, EnumCount)]
enum Small {
A,
B,
C,
}
#[derive(Debug, Clone, Copy, EnumCount)]
enum Large {
V0, V1, V2, V3, V4, V5, V6, V7, V8, V9,
V10, V11, V12, V13, V14, V15, V16, V17, V18, V19,
// ... many more variants
}
fn main() {
// COUNT is compile-time constant - no runtime overhead
let small_size: usize = Small::COUNT;
let large_size: usize = Large::COUNT;
// Both are just constant values in the binary
// No iteration or computation at runtime
// Macro expansion happens at compile time
// For very large enums, this adds to compile time, not runtime
}COUNT is a compile-time constant with zero runtime cost.
use strum::EnumCount;
#[derive(Debug, Clone, Copy, EnumCount)]
enum Input {
Keyboard,
Mouse,
Controller,
}
fn main() {
// COUNT can be used in const generics (with limitations)
const INPUT_COUNT: usize = Input::COUNT;
// Create fixed-size array for input states
let input_states: [bool; INPUT_COUNT] = [false; INPUT_COUNT];
// COUNT works in const contexts
const MAX_INPUTS: usize = Input::COUNT;
println!("Max inputs: {}", MAX_INPUTS);
}COUNT is usable in const contexts for fixed sizing.
use strum::EnumCount;
#[derive(Debug, Clone, Copy, EnumCount)]
enum Permission {
Read,
Write,
Execute,
}
struct PermissionSet {
// One bit per permission
bits: u32,
}
impl PermissionSet {
fn has(&self, perm: Permission) -> bool {
self.bits & (1 << (perm as usize)) != 0
}
fn set(&mut self, perm: Permission) {
self.bits |= 1 << (perm as usize);
}
fn all() -> Self {
// All bits up to COUNT set
Self {
bits: (1u32 << Permission::COUNT) - 1,
}
}
}
fn main() {
let all_perms = PermissionSet::all();
println!("Has Read: {}", all_perms.has(Permission::Read));
}COUNT enables bitwise operations bounded by variant count.
use strum::EnumCount;
#[derive(Debug, Clone, Copy, EnumCount)]
enum PlayerSlot {
Player1,
Player2,
Player3,
Player4,
}
struct GameState {
// Fixed array for player scores
scores: [u32; PlayerSlot::COUNT],
// Fixed array for player names
names: [Option<String>; PlayerSlot::COUNT],
}
impl GameState {
fn new() -> Self {
Self {
scores: [0; PlayerSlot::COUNT],
names: [None; PlayerSlot::COUNT],
}
}
fn add_score(&mut self, slot: PlayerSlot, points: u32) {
self.scores[slot as usize] += points;
}
}
fn main() {
let mut game = GameState::new();
game.add_score(PlayerSlot::Player1, 100);
game.add_score(PlayerSlot::Player2, 150);
println!("Player 1 score: {}", game.scores[PlayerSlot::Player1 as usize]);
}Game state often uses fixed-size arrays indexed by enum.
| Use Case | Appropriate? | Reason |
|----------|-------------|--------|
| Array sizing | Yes | Compile-time constant |
| Validation bounds | Yes | Zero runtime cost |
| Compile-time assertions | Yes | Catches enum changes |
| Bit manipulation | Yes | Known bit count |
| Display/informational | No | Use literal or doc |
| Already iterating | No | Use iter().count() |
| Dynamic counting | No | Must be compile-time |
strum::EnumCount provides compile-time variant counts for sizing and validation:
When to use it:
[T; Enum::COUNT] guarantees exact sizingCOUNT without runtime iterationWhen to avoid it:
iter().count() adds no overhead if you're already calling iter()status == Status::COUNT is clearer written directlyKey insight: EnumCount trades compile-time macro expansion for zero-cost runtime constant access. The COUNT constant enables patterns that would otherwise require vectors or runtime iteration. It's most valuable when the variant count directly influences data structure sizing or validation logic, turning what would be a runtime check into a compile-time guarantee.
The derive macro is simple but powerful: it gives you VARIANT_COUNT as a const value, enabling all the patterns that require compile-time sizing. Use it when array sizing or boundary constants matter; skip it when a simple numeric literal would suffice.