Best practices on setters and accessor methods in general

Hello,
I'm a new rust developer, coming from object oriented languages, so I have a question about best practices on how to translate some concepts about getters/setters method.
Suppose I have a struct that mimics a record in a database:

struct User {
    username: String,
    password: String
}

This struct would be passed to some sort of database library that will create an INSERT sql statement and execute it.
Normally the password field would be encrypted with an algorhytm of some sort before being written to the db.
In an object oriented language I would make the username and password fields private and then declare methods like getUserrname, setUsername, getPassword and setPassword.
The setPassword method in particular would be something like this (I use php as an example):

function setPassword($value)
{
    $this->password = encrpyt_magic_function($value);
}

This would allow me to always have valid values inside an instance of User because they are validated when setting them.
What are the best practices to achieve this sort of goal in Rust?
Thanks,
Frank

In Rust fields of structs are private outside of the module that defines the struct.

mod user { // content of {} could be in a file too
   pub struct User { password: String }
   impl User {
      pub fn set_password(&mut self, pass: &str) { 
         self.password = encrypt(pass); 
      }
   }
}

fn foo(u: &user::User) {
    u.password; // error!
}

So you can write getters and setters as usual. In Rust it's easier if you can avoid getters and setters (because borrow checker is more flexible with direct field access), but if you need them, they're possible to write.

In Rust, since data is by default immutable, we usually use simple fields and modules can use them directly, on a "need to know" basis. This is facilitated by the module and visibility system which is quite rich.

https://doc.rust-lang.org/reference/visibility-and-privacy.html#pubin-path-pubcrate-pubsuper-and-pubself

Thanks for the answers!
I sure need to switch my mind and begin to think in a more "rusty" way, so pardon me if my questions are a bit abstract.
The problem with get/set methods in other languages is that you need to always specify them everywhere. From what I see in other people rust code, these methods are rarely used.
Maybe I should stop thinking about having those big classes/objects that I've always used in oop and take a different approach...
Frank

If you decide to use getters (e.g. because you want to make the fields non-pub for some reason), it is possible to name them without the get_ prefix, as you can have a method and a field with the same name (and they won't collide).

For example:

pub struct Person {
    name: String,
    age: u8,
}

impl Person {
    pub fn name(&self) -> &str {
        &self.name
    }
    pub fn age(&self) -> u8 {
        self.age
    }
}

I'm not sure if it's idiomatic to leave out the get_ prefix, but that's what I would do (if it's bad practice, please correct me).

Note that there are some other prefixes common in Rust, such as into_ when you consume the value:

impl Person {
    pub fn into_name(self) -> String {
        self.name
    }
    pub fn into_name_and_age(self) -> (String, u8) {
        (self.name, self.age)
    }
}

You cannot call these into_ methods more than once, as they take ownership of self.

1 Like

It struck me that there is something perhaps a bit philosophical off about the example.

The struct has a String field called password. But the thing you want to store in that field is not the actual password string as a human would understand it. Rather it is something derived form a password String by some algorithm. password need not even be a valid unicode String, perhaps it is some large integer or a vector u8 or some something else.

I think what I'm saying is that password is a different type than String. (Happens by chance to be the same in this example`). It's a type that has certain properties, at least that is the output of some encryption algorithm. Likely that the input to that algorithm was checked for "strength", minimum length, includes upper case, lower case, numbers, etc. What about checking how old the password is?

That suggests that one should separate out this password idea. Define a type that is a password, that creates passwords from String, does strength checking and whatever else.

Then use that type in the struct:

struct Password {
     // Whatever you need here to hold a password.
}

impl Passord {
    fn create (s: String) {
        // Whatever you need here
        // to create a password.    
    }
}


struct User {
    username: String,
    password: Password,
}

Now you can set password without any setter method. Just write it to the Struct. Let Rust's type system check that it is actually a Password not just any old string.

Also, given that Struct definition I don't see how you can even have a setter for password. The Struct has to be constructed with valid username and passwords. I know you can create the Struct with a zero length password String if none is available, then fill it in later with a setter method. But would it not be better to use 'Option' to indicate a password is not known yet?

struct User {
    username: String,
    password: Option<Password>,
}

That protects you from using empty strings as passwords!

Edit: None of this is anything to do with OOP or otherwise. Surly the same considerations apply in C++ or whatever.

4 Likes

Thanks to all for the very useful suggestions!
The analysis by ZiCog is very interesting, to my understanding it's the most idiomatic way of doing what I need to do.
Let's extend a bit the example and suppose that the username field should be checked for minimum length or something like that, before being set, so that if you pass the struct around you are sure that it contains valid data. I know there are some validation crates around where you mark the struct fields with an attribute for data validation, is there a way that doesn't involve other crates?
Maybe the solution is again using types that "wrap" the given string like that in ZiCog's example?
Thanks,
Frank

My question is: is there a reason your fields need to be private? Privacy in Rust is usually used for two reasons:

  1. Logical correctness and/or memory safety (soundness): if you have a type equipped with some invariant that the type system can't otherwise express, you can make its fields private, and ensure that the constructor and all of its methods always create/leave the object in a valid state.
  2. Forward compatibility: if you ensure that users can't poke at fields directly, you can change their type and name without breaking the API.

I can't tell whether either applies in your case. Is this User supposed to be a rich domain model object, or is it a simple DTO? If it's the latter, don't bother with privacy. If it's the former, you will probably need to think a lot more about its design; storing "encrypted" (or hashed?) passwords inside domain model objects sounds really fishy to me.

My questions are more on a philosophical level, I mean that I would like to translate or better convert some concepts that are common in oop and learn a methodology that is easier to express in rust.
About the specific case you are right, maybe the password should stay (whether encrypted or not) in memory the shortest amount of time for security reasons. But think about changing the password with an email address where you just need to be sure that it's a valid address when passing the struct around.

Rust tends to put a bigger emphasis on data/structure while traditional OO languages tend to put emphasis on behaviour/encapsulation.

That means you tend to see a lot more "plain old data" types in Rust, and by exposing the fields you open the door for really nice things like pattern matching. There is also a greater emphasis on using the type system to make sure things are correct by construction, which means you don't have to worry about external code fiddling with your internals and breaking things. You might do this by making the "sensitive" field private and ensuring people can only construct your object through the provided constructors, then make sure no public operations can break internal invariants.

That's not to say you don't see "objects" in Rust (i.e. types whose main purpose is exposing behaviour), but they'll often only expose behaviour and not break encapsulation by providing getters and setters.

There is also the argument that writing getters and setters in an OO language are *also* a waste of time. I mean, when was the last time you actually needed to modify a setter to inject some logic in a way that didn't also require refactoring the rest of your code to take into account new semantics? Now compare that with the number of times you've written getters and setters and ask yourself whether all that extra boilerplate was worth it... But I digress.
5 Likes

Thanks Michael, the concepts are clear and very useful.
I agree with your last opinion on getters and setters, they are often just a waste of time but sometimes they prove very useful like the examples I gave. Getters and setters are often a way to overcome what is in my opinion the true enormous problem with dynamic typed languages like php: you never know what type a damn variable is.
I think that the concept of preferring immutability and favouring correctness by construction that you mentioned are key points when switching to non strictly object oriented languages like rust. The programmer should take advantage of the powerful type system in rust as often as possible.

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.