Skip to content

S31: Function with too many parameters (no builder or config struct)

S31Severity: Low

Long parameter lists that are easy to call in the wrong order.

Clippy: too_many_arguments

Eight parameters, five of them &str:

pub fn create_invoice(
customer: &str,
street: &str,
city: &str,
postal_code: &str,
country: &str,
currency: &str,
due_days: u32,
vat_percent: u32,
) -> String {
format!(
"{customer}, {street}, {postal_code} {city}, {country}: {currency}, due in {due_days} days, VAT {vat_percent}%"
)
}

A call site, with the city and the postal code swapped:

create_invoice("Ada", "Main St 1", "3000", "Bern", "CH", "CHF", 30, 8) // city and postal code swapped
  • Each new requirement adds one parameter. “Also print the VAT rate” becomes vat_percent: u32 at the end of the list. The agent changes the smallest amount of code, and nobody groups the values.
  • It copies the Java constructor. A Java class with eight fields often has a constructor with eight parameters. The agent writes new the same way.
  • Rust has no named or default arguments. In Python or C#, create_invoice(city="Bern", ...) makes a long list safer. Rust has no such feature, so the list stays positional.
  • Swapped values compile. "3000" and "Bern" are both &str. The invoice says “Bern 3000” instead of “3000 Bern”, and no test of the types finds it. The test worse_call_site_swaps_two_strings shows it.
  • Call sites cannot be read. "CHF", 30, 8 needs the definition to understand. A reviewer must count positions.
  • The values that belong together are not grouped. Street, city, postal code, and country are an address. Every function that needs an address repeats the four parameters.
  • Changing the list breaks every caller, and a new parameter in the middle can shift the meaning of other values of the same type.
  1. WorseEight positional parameters, five of the same type.

    pub fn create_invoice(
    customer: &str,
    street: &str,
    city: &str,
    postal_code: &str,
    country: &str,
    currency: &str,
    due_days: u32,
    vat_percent: u32,
    ) -> String {
    format!(
    "{customer}, {street}, {postal_code} {city}, {country}: {currency}, due in {due_days} days, VAT {vat_percent}%"
    )
    }
  2. BetterThree parameters: the customer, an Address, and Terms. Each struct groups values that belong together.

    pub struct Address {
    pub street: String,
    pub city: String,
    pub postal_code: String,
    pub country: String,
    }
    pub struct Terms {
    pub currency: String,
    pub due_days: u32,
    pub vat_percent: u32,
    }
    pub fn create_invoice(customer: &str, address: &Address, terms: &Terms) -> String {
    let Address {
    street,
    city,
    postal_code,
    country,
    } = address;
    format!(
    "{customer}, {street}, {postal_code} {city}, {country}: {}, due in {} days, VAT {}%",
    terms.currency, terms.due_days, terms.vat_percent
    )
    }

At the call site, every value has a name:

let address = Address {
street: "Main St 1".into(),
city: "Bern".into(),
postal_code: "3000".into(),
country: "CH".into(),
};
let terms = Terms {
currency: "CHF".into(),
due_days: 30,
vat_percent: 8,
};
create_invoice("Ada", &address, &terms)

For types with many optional settings, Default with struct update syntax (..Default::default()) or a builder are the other options. The gallery entry Build a request with optional fields compares them.

  • A few parameters of different types, where a swap does not compile: fn move_to(x: f64, y: f64) is clear by convention, and fn send(to: &Email, body: &str, attempts: u32) cannot be mixed up.
  • Private helpers called from one place, where grouping the values adds a type for nothing.
  • Generated code, such as bindings, that mirrors an external API.
  • Clippy too_many_arguments (complexity group, on by default) reports functions with more than seven parameters. The threshold is too-many-arguments-threshold in clippy.toml. The example has eight. A codebase that sets a lower threshold finds more.
  • Look for #[allow(clippy::too_many_arguments)]. An agent may have added it to silence the warning. See S25: #[allow(...)] to silence clippy or compiler warnings.
  • Look for two or more parameters of the same type next to each other, especially &str, u64, and bool.
Group the parameters of create_invoice into structs of values that belong
together: Address { street, city, postal_code, country } and
Terms { currency, due_days, vat_percent }. Take them by reference. Use
newtypes or enums where two parameters of the same type can be swapped.
Remove any #[allow(clippy::too_many_arguments)]. Update the callers to
build the structs with named fields, and run the tests.

Related: S37: Primitive types where a newtype gives safety (u64 for IDs), S15: bool flags or string constants instead of an enum, and Structs and impl.