Avoiding too many arguments passing to a function

How can I prevent too many arguments passed to my function?

pub async fn insert_user(uid: String, pass: String, ud: String, role: Option<Role>) -> Result<Model, sea_orm::DbErr>{

    // A role can be only student or teacher
    let role_check: Role = {
        if role.is_none() {
            Role::Student
        } else {
            Role::Teacher
        }
    };

    let user: isumis_user::ActiveModel = isumis_user::ActiveModel {
        id: NotSet,
        user_id: sea_orm::ActiveValue::Set(uid),
        pass: sea_orm::ActiveValue::Set(pass),
        user_data: sea_orm::ActiveValue::Set(Some(ud)),
        class_id: NotSet,
        user_role: sea_orm::ActiveValue::Set(role_check),
        created_at: NotSet,
        modified_at: NotSet,
    };

    Ok(user.insert(&init().await).await?)
}

I would not consider 4 parameters too many.

I need more than 4 and before implementing them I wanted to ask for possible alternatives

You can shove them in a struct... but that's kind of just putting lipstick on a pig. It doesn't change how much stuff you're passing, just how you're passing it.

If there's a clear line between parts of the function that need different subsets of data, you can split it into multiple functions. But it doesn't look like that would particularly help in the case of the above example.

If a function needs a bit of data, then you have to pass it. *shrug*

2 Likes

Does rust have something like **kwargs in Python?

No, no **kwargs.

No. It doesn't even have *pargs. Besides, that wouldn't do anything about passing too many arguments, it would just make it more difficult to tell what arguments are being passed.

2 Likes

Ok, thanks. Maybe I can find a different solution.

As a rule of thumb, if a function takes too many arguments you should consider:

  1. Is the function doing too much? If so, perhaps you could split the logic in several, smaller functions instead
  2. Group the arguments into structs in a way that feels logically consistent
  3. If the function is for creating a resource and inserting it in the database, consider splitting the logic for building the resource from the insertion in the database by receiving an instance of the object or a builder instead (This is a variant of point 1)

In your example case, I would certainly go with #3.

6 Likes

I've not used it, but there is buildstructor which would let you do something like:

#[buildstructor]
MyStruct {
    #[builder(entry = "user", exit = "insert")]
    pub async fn insert_user(uid: String, pass: String, ud: String, role: Option<Role>) -> Result<Model, sea_orm::DbErr> { ... }
}

let model = MyStruct::user()
    .uid(12)
    .pass("12345")
    .ud("whatever")
    // role will default to None if not provided
    .insert().await?;
3 Likes

You could also just insert more newlines to make it look better.

pub async fn insert_user(
    uid: String,
    pass: String,
    ud: String,
    role: Option<Role>,
) -> Result<Model, sea_orm::DbErr> {
    ...
}

4 parameters isn't really a lot. But if you're finding that there's several functions which take the same group of parameters for similar reasons, it's probably a good idea to put them together in a struct.

2 Likes

Well, if you have a bunch o attributes you a have bunch of attributes, no way around it.

This might be an indication of your tables being to big, but maybe they aren't, sometimes you just have to do the straight forward thing.

In this particular case though, I think its a good idea to create a struct with these parameters, it also adds some other advantages like being able to add behavior to the struct so you can transform it, or validate it.

An example for a project I was working on:

#[derive(Deserialize, Validate)]
pub struct RequestData {
    pub organization_id: u32,
    pub username: String,
    #[validate(length(min = 8))]
    pub password: String,
    pub firstname: String,
    pub lastname: String,
    #[validate(email)]
    pub email: String,
}

pub async fn store(
    State(app): State<AppData>,
    Json(request_user): Json<RequestData>,
) -> Result<Json<ResponseData>, Error> {
    request_user
        .custom_validation(Arc::clone(&app), request_user.validate())
        .await?;

    let user = ActiveModel {
        organization_id: Set(request_user.organization_id),
        username: Set(request_user.username),
        password: Set(crate::utils::password(request_user.password)?),
        firstname: Set(request_user.firstname),
        lastname: Set(request_user.lastname),
        email: Set(request_user.email),
        created_at: Set(chrono_now().naive_utc()),
        ..Default::default()
    }
    .save(&app.connection)
    .await
    .map_err(|_| Error::DB)?
    .try_into_model()
    .map_err(|_| Error::DB)?;

    Ok(Json(user.into()))
}
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.