S15: bool flags or string constants instead of an enum
What it looks like
Section titled “What it looks like”String constants for a status, and three bool parameters for one choice:
pub const STATUS_NEW: &str = "new";pub const STATUS_PAID: &str = "paid";pub const STATUS_SHIPPED: &str = "shipped";
pub fn shipping_cents(express: bool, overnight: bool, pickup: bool) -> u64 { if pickup { 0 } else if overnight { 2_500 } else if express { 1_500 } else { 500 }}
pub fn can_ship(status: &str) -> bool { status == STATUS_PAID}And the call sites:
let cents = shipping_cents(true, false, true); // which flag is which?let ok = can_ship("Paid"); // compiles, and is falseWhy AI agents produce it
Section titled “Why AI agents produce it”- It copies older Java.
public static final String STATUS_PAID = "paid"andboolean expressparameters are common in older Java code, and the agent translates them directly. - It follows the input. A JSON field
"status": "paid"or a database column of typeVARCHARbecomes aStringin the struct, and stays one. - Flags grow one at a time. The first version has
express: bool. A later prompt addsovernight: bool, thenpickup: bool. Each step is small, and nobody adds the enum.
Why it is a problem
Section titled “Why it is a problem”- The call site does not say what it means.
shipping_cents(true, false, true)needs the definition to read. Swapped flags compile. - Invalid combinations compile. Three flags have eight combinations. “Express and pickup” is meaningless, and the function silently picks one (
pickup, because of the order of theifchain). The testworse_has_impossible_and_silent_casesshows it. - Typos compile.
can_ship("Paid")compares with"paid"and returnsfalse. The constants do not help, because the parameter accepts any&str. - The compiler cannot help when a case is added. A new status is a new string. Nothing lists the code that must handle it.
The better version
Section titled “The better version”WorseThree
boolparameters for one choice, and a&strstatus compared with constants.pub const STATUS_NEW: &str = "new";pub const STATUS_PAID: &str = "paid";pub const STATUS_SHIPPED: &str = "shipped";pub fn shipping_cents(express: bool, overnight: bool, pickup: bool) -> u64 {if pickup {0} else if overnight {2_500} else if express {1_500} else {500}}pub fn can_ship(status: &str) -> bool {status == STATUS_PAID}BetterA
Deliveryenum and aStatusenum.shipping_centsis one exhaustivematch.#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum Delivery {Pickup,Standard,Express,Overnight,}#[derive(Debug, Clone, Copy, PartialEq, Eq)]pub enum Status {New,Paid,Shipped,}pub fn shipping_cents(delivery: Delivery) -> u64 {match delivery {Delivery::Pickup => 0,Delivery::Standard => 500,Delivery::Express => 1_500,Delivery::Overnight => 2_500,}}pub fn can_ship(status: Status) -> bool {status == Status::Paid}
The call sites now say what they mean, and a typo is a compile error:
let cents = shipping_cents(Delivery::Express);let ok = can_ship(Status::Paid);When it is acceptable
Section titled “When it is acceptable”- One
boolthat is really yes or no, with a clear name:include_archived: boolin a struct field. At a call site, even oneboolargument is easier to read as a named field or an enum. - Independent flags that can each be on or off in any combination, such as
gift_wrapandsend_receipt. That is not one choice, so it is not an enum. (Many flags in one struct can still be a sign; Clippystruct_excessive_boolsreports more than three.) - At the boundary: a string from JSON or a database is fine in the type that is deserialized. Convert it to an enum once, with
FromStrorserde, and use the enum inside the program.
How to detect it
Section titled “How to detect it”- No Clippy lint reports this pattern in the example. Clippy
fn_params_excessive_bools(pedantic) reports functions with more than threeboolparameters, andstruct_excessive_bools(pedantic) reports structs with more than threeboolfields. Both are off by default, and the example has exactly three. - Search for
const .*: &str =with values that look like states, and for== "comparisons. - Look for calls with two or more
trueorfalseliterals.
What to tell your agent
Section titled “What to tell your agent”Replace the bool parameters that describe one choice with an enum, forexample enum Delivery { Pickup, Standard, Express, Overnight }. Replace thestring status constants with enum Status, and parse strings into it once,where the data enters the program. Use exhaustive match on the enums, withno _ arm. Update all callers and run the tests.Related: S17: Struct with many Option fields that are really states, S16: Wildcard _ => match arm that hides new variants, and Enums and pattern matching.