Best practice CRUD: give struct or parameters to function?

Hello!

This is more of a "best practice" question, it bugs me a bit, so here it is :slight_smile:

I'm writing a CRUD backend in rust. I can create/edit/etc.. todos.

Thus, I have an axum router, which receives the requests and then forwards it to a todo service.

The question is, when creating or editing a todo, is it better to have signatures like these:

impl TodoService {
pub async fn create(&self, name: &str, parent_id: Option<i32>) -> ..
pub async fn edit(&self, id: i32, name: &str, parent_id: Option<i32>) -> ...
}

or is it better to give the structures themselves to the todo service, like so:

impl TodoService {
pub async fn create(&self, &mut todo: Todo) -> ... // or not mut and return the created Todo, with it's id, etc..
pub async edit(&self, &todo: Todo) -> ...
}

The first one seems more explicit, and clear, but then we loose the whole static stuff by passing integer ids, which is a shame (i.e. we can mix those up with a TodoDate id, etc..)

The second one seems less error prone, but then how do you deal with fields that are created by the db (eg. the autoincrement id) ?

What do you reckon?
Thank you!

In general, separate params are better, unless:

  • the number of params is large and in that case you may want to consider the builder pattern
  • or the params are naturally grouped into a struct, and the struct could be used in multiple places

In this case the number of fields is small so a struct is probably not needed.

Another way to deal with multiple integers that mean different things is the newtype idiom.

Right, in that case you wouldn't want to use the same struct for input parameters and for the stored object, since the stored object can have additional fields.

1 Like