Skip to content

S12: Getters and setters for every field

S12Severity: Low

JavaBean accessors that add code but no encapsulation.

Clippy: no lint finds this. Look for it in review.

pub struct Customer {
name: String,
email: String,
loyalty_points: u32,
}
impl Customer {
pub fn new(name: String, email: String, loyalty_points: u32) -> Self {
Self {
name,
email,
loyalty_points,
}
}
pub fn get_name(&self) -> String {
self.name.clone()
}
pub fn set_name(&mut self, name: String) {
self.name = name;
}
pub fn get_email(&self) -> String {
self.email.clone()
}
pub fn set_email(&mut self, email: String) {
self.email = email;
}
pub fn get_loyalty_points(&self) -> u32 {
self.loyalty_points
}
pub fn set_loyalty_points(&mut self, loyalty_points: u32) {
self.loyalty_points = loyalty_points;
}
}
  • It is the Java convention. JavaBeans, Lombok, and many style guides say “private fields, public accessors”. The agent applies the rule to every type.
  • It looks like encapsulation. The fields are private, so the code looks careful.
  • The names come from Java. get_name and set_name are getName and setName in snake case.
  • It protects nothing. A setter that writes any value is the same as a public field, with more code. In this example, set_email("not an email") is accepted.
  • Every getter clones. get_name() returns a new String each time it is called: a heap allocation to read a field.
  • The type is harder to use. Callers outside the module cannot destructure it or build it with a struct literal. customer.get_name() is also longer than customer.name.
  • Encapsulation in Rust is per module, not per type. A private field is visible to all code in its module. Accessors do not add a boundary that the module does not already have. See Structs and impl.
  1. WorsePrivate fields, a new with all fields, and a cloning getter and a setter for each one.

    pub struct Customer {
    name: String,
    email: String,
    loyalty_points: u32,
    }
    impl Customer {
    pub fn new(name: String, email: String, loyalty_points: u32) -> Self {
    Self {
    name,
    email,
    loyalty_points,
    }
    }
    pub fn get_name(&self) -> String {
    self.name.clone()
    }
    pub fn set_name(&mut self, name: String) {
    self.name = name;
    }
    pub fn get_email(&self) -> String {
    self.email.clone()
    }
    pub fn set_email(&mut self, email: String) {
    self.email = email;
    }
    pub fn get_loyalty_points(&self) -> u32 {
    self.loyalty_points
    }
    pub fn set_loyalty_points(&mut self, loyalty_points: u32) {
    self.loyalty_points = loyalty_points;
    }
    }
  2. BetterPublic fields, and derived traits. Five lines.

    #[derive(Debug, Clone, PartialEq, Eq)]
    pub struct Customer {
    pub name: String,
    pub email: String,
    pub loyalty_points: u32,
    }

When a field has a rule, keep it private and write methods that keep the rule:

/// The email must contain an `@`. The field is private so that no code can break that rule.
#[derive(Debug)]
pub struct Customer {
pub name: String,
email: String,
}
#[derive(Debug, PartialEq, Eq)]
pub struct InvalidEmail;
impl Customer {
pub fn new(name: &str, email: &str) -> Result<Self, InvalidEmail> {
let mut customer = Self {
name: name.to_string(),
email: String::new(),
};
customer.set_email(email)?;
Ok(customer)
}
pub fn email(&self) -> &str {
&self.email // a getter without `get_`, that returns a borrow
}
pub fn set_email(&mut self, email: &str) -> Result<(), InvalidEmail> {
if !email.contains('@') {
return Err(InvalidEmail);
}
email.clone_into(&mut self.email);
Ok(())
}
}
  • The setter returns a Result, because it can refuse a value.
  • The getter is named after the field (email(), not get_email()), and returns a borrow (&str), not a clone. That is the Rust API Guidelines convention.
  • name is still a public field, because it has no rule.

Accessors are also correct in a library that must be able to change its internal representation without breaking callers, and for computed values (total_cents() that sums the lines).

  • No Clippy lint reports accessors as such.
  • Search for fn get_ and fn set_. Setters that only assign, and getters that only return or clone a field, are the smell.
  • Look at the types of getters: -> String or -> Vec<T> from a getter usually means a clone.
Remove the getters and setters that only read or write a field, and make
those fields pub. Keep a field private only if a rule connects it to other
data or limits its values. For those fields, keep a setter that returns a
Result, and a getter named after the field (no get_ prefix) that returns a
borrow such as &str instead of a clone. Update the callers and run the tests.

Related: S38: pub on everything, and Structs and impl.