Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

primitive-soup

Level: warn · Article: Primitives

Three bugs. All type-safe. The compiler is happy. Your users are not.

What it checks

A function takes two or more parameters of the same primitive type (String, &str, integers, floats, bool, Option<..> of those). Two String parameters can be swapped at any call site and the program still compiles. Methods inside impl Trait for T are skipped: that signature is the trait’s.

Don’t

#![allow(unused)]
fn main() {
impl Mailer {
    fn send_invoice(&self, user_id: String, email: String, invoice_id: String) {
        self.send_email(&user_id, &invoice_id); // swapped
        self.log_access(&invoice_id, &email); // wrong order
    }
}
}

Do

#![allow(unused)]
fn main() {
struct UserId(String);
struct Email(String);
struct InvoiceId(String);

impl Mailer {
    fn send_invoice(&self, user_id: UserId, email: Email, invoice_id: InvoiceId) {
        self.send_email(&email, &invoice_id);
        self.log_access(&user_id, &invoice_id);
    }
}
}

You write the type once. The build catches the swap instead of the review. The newtype compiles to the same representation as the primitive; the cost is zero.

Options

[thresholds]
primitive-soup = 2   # parameters of the same primitive type before it fires

Silence it

#![allow(unused)]
fn main() {
// rabot: allow(primitive-soup) stateless math with no subject: min, max are both just numbers
fn clamp(value: f64, min: f64, max: f64) -> f64 { .. }
}

The article’s own exception: genuinely stateless math, where no parameter means anything on its own.