Skip to content

Instantly share code, notes, and snippets.

@alex35mil
Created May 27, 2026 17:54
Show Gist options
  • Select an option

  • Save alex35mil/44be45e8ff591d8a7d748429c5ac9c05 to your computer and use it in GitHub Desktop.

Select an option

Save alex35mil/44be45e8ff591d8a7d748429c5ac9c05 to your computer and use it in GitHub Desktop.

Prefer Sum Types Over Strings and Booleans

Design domain types to make illegal states unrepresentable. Use enums/variants (sum types) instead of strings or booleans when a value has a fixed set of meaningful states.

Strings Allow Invalid Values

// ❌ WRONG: Any string is accepted, typos compile fine
struct Post {
    status: String, // "draft", "published", "archived"... or "publsihed"?
}

fn publish(post: &mut Post) {
    post.status = "published".to_string();
}

// ✅ CORRECT: Only valid states exist
enum PostStatus {
    Draft,
    Published,
    Archived,
}

struct Post {
    status: PostStatus,
}

fn publish(post: &mut Post) {
    post.status = PostStatus::Published;
}

Booleans Create Illegal State Combinations

// ❌ WRONG: 4 combinations exist, but only 3 are valid
struct User {
    is_verified: bool,
    is_banned: bool,
    // What does is_verified=true + is_banned=true mean?
}

// ✅ CORRECT: Only valid states are representable
enum UserStatus {
    Unverified,
    Verified,
    Banned,
}

struct User {
    status: UserStatus,
}

Multiple Booleans Often Hide an Enum

// ❌ WRONG: Boolean flags with implicit dependencies
struct Order {
    is_paid: bool,
    is_shipped: bool,
    is_delivered: bool,
    // Can it be delivered but not shipped? Shipped but not paid?
}

// ✅ CORRECT: State machine with valid transitions only
enum OrderStatus {
    Pending,
    Paid,
    Shipped,
    Delivered,
    Cancelled,
}

struct Order {
    status: OrderStatus,
}

Use Enums with Data for State-Specific Fields

// ❌ WRONG: Fields that only apply to certain states
struct Payment {
    status: String,
    error_message: Option<String>,  // Only for "failed"
    transaction_id: Option<String>, // Only for "completed"
    retry_count: Option<u32>,       // Only for "pending"
}

// ✅ CORRECT: Each variant carries only its relevant data
enum PaymentStatus {
    Pending { retry_count: u32 },
    Completed { transaction_id: String },
    Failed { error_message: String },
}

struct Payment {
    status: PaymentStatus,
}

Apply this rule:

  • Replace String with an enum when the field has a fixed set of valid values
  • Replace bool with an enum when the field represents more than true/false semantics
  • Replace multiple booleans with an enum when they have invalid combinations or dependencies
  • Replace Option<T> fields with enum variants carrying data when fields only apply to certain states

Benefits:

  • Compiler rejects invalid states at compile time
  • Pattern matching ensures all cases are handled
  • Self-documenting: the type definition shows all possible states
  • Refactoring is safe: adding a variant causes compile errors at all usage sites

Exhaustive Pattern Matching: Avoid Catch-All Wildcards or If's

When matching on enums you control, explicitly list all variants instead of using _ or .. catch-alls or if x == <enum-member>. This ensures the compiler alerts you when new variants are added.

// ❌ WRONG: Wildcard hides future variants
match role {
    Admin => true,
    _ => false,
}

// ❌ WRONG: If hides future variants
if role == Admin {
    true
} else {
    false
}

// ✅ CORRECT: Compiler will error when new variants are added
match role {
    Admin => true,
    Member => false,
}

Why this matters:

  • Adding UserRole::Moderator silently falls into false with the wildcard
  • Explicit matches force you to consider each case when the enum evolves
  • The compiler becomes your safety net for refactoring

Use wildcards only when:

  • Matching external crate enums marked #[non_exhaustive]
  • Handling spec-driven enums with many variants (HTTP status codes, MIME types) where only a few need special handling
  • The logic genuinely is "these specific cases vs. everything else" and won't change

Rule of thumb: Write out all match arms for enums you control. Use wildcards for 50-variant spec enums where you only care about 3 cases—but add a comment explaining the intent.

Fail Fast: Never Silently Swallow Errors or Invalid States

When a condition could indicate either "expected/acceptable" or "something is wrong," handle these cases explicitly and separately. Never combine them into a single fallback branch.

Principle: If a state would be a bug or misconfiguration in some contexts, make it loud. Silent fallbacks hide bugs and make debugging harder.

// ❌ WRONG: Silent fallback hides whether None is expected or a bug
fn get_user_display_name(user: &User) -> String {
    match &user.display_name {
        Some(name) => name.clone(),
        None => "Anonymous".to_string(), // Is this intentional or hiding missing data?
    }
}

// ✅ CORRECT: Separate functions with explicit intent
fn get_optional_display_name(user: &User) -> Option<&str> {
    user.display_name.as_deref() // Caller decides how to handle None
}

fn get_required_display_name(user: &User) -> &str {
    user.display_name.as_deref()
        .expect("display_name is required for verified users")
}

// ✅ CORRECT: Different handling based on known context
match &config.database_url {
    Some(url) => connect(url),
    None => panic!("DATABASE_URL is required"), // Required config: fail loud
}

match &config.cache_url {
    Some(url) => Some(connect_cache(url)),
    None => None, // Optional config: absence is valid
}

Check for silent fallbacks when handling:

  • Missing environment variables or configuration — panic if required, skip if optional
  • Failed parsing or validation — return error, don't substitute defaults
  • Unexpected enum variants — panic with context, don't silently ignore
  • Empty collections — fail if data is expected, proceed if emptiness is valid
  • Network/IO operations — propagate errors, don't silently retry or ignore

When in doubt: Ask a user whether absence/failure is expected or a bug.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment