How to change this mutable-referenced struct's field value?

In the below code (a small reduced example extracted from my project) I'm trying to change the condition in query_construction where I have an condition: Option<&'a mut PlayerConditions>.

REPL: undefined | Rust Explorer

I cannot understand how to do this, I tried:

condition = Some(
    &mut condition
        .unwrap_or(&mut PlayerConditions::new())
        .set_email(None),
);

but the error is:

error[E0384]: cannot assign to immutable argument `condition`
  --> src\main.rs:72:9
   |
59 |           condition: Option<&'a mut PlayerConditions>,
   |           --------- help: consider making this binding mutable: `mut condition`
...
72 | /         condition = Some(
73 | |             &mut condition
74 | |                 .unwrap_or(&mut PlayerConditions::new())
75 | |                 .set_email(None),
76 | |         );
   | |_________^ cannot assign to immutable argument

but of course this is not something I want.

I would like to change the value of "mutable-referenced" condition.

Is it possible?

#[derive(Debug, Default)]
pub struct PlayerConditions {
    id: Option<String>,
    name: Option<String>,
    email: Option<String>,
}

impl PlayerConditions {
    pub fn new() -> Self {
        Self::default()
    }
    pub fn is_default(&self) -> bool {
        self.id.is_none() && self.name.is_none() && self.email.is_none()
    }
    pub fn id(&self) -> &Option<String> {
        &self.id
    }
    pub fn set_id(mut self, id: Option<String>) -> Self {
        self.id = id;
        self
    }
    pub fn name(&self) -> &Option<String> {
        &self.name
    }
    pub fn set_name(mut self, name: Option<String>) -> Self {
        self.name = name;
        self
    }
    pub fn email(&self) -> &Option<String> {
        &self.email
    }
    pub fn set_email(mut self, email: Option<String>) -> Self {
        self.email = email;
        self
    }
}

#[derive(Debug, Default)]
pub struct PlayerListInput {
    pub condition: Option<PlayerConditions>,
    pub order: Option<String>, // This is not String: is a struct in real code
                               // other fields here...
}

#[derive(Debug)]
pub struct DBPlayer {
    pub id: String,
    pub name: Option<String>,
    pub email: Option<String>,
}

impl DBPlayer {
    fn query_construction<'a>(
        condition: Option<&'a mut PlayerConditions>,
        order: &Option<String>,
    ) -> String {
        let mut query = "SELECT * FROM players".to_string();

        // --->>> I need to change condition here using this logic:

        // if condition is None add one condition with "set_email(None)"
        // if condition is Some and email == None set "set_email(None)"
        // if condition is Some and email == Some do nothing

        // I tried the below but it doesn't work:

        // condition = Some(
        //     &mut condition
        //         .unwrap_or(&mut PlayerConditions::new())
        //         .set_email(None),
        // );

        // From here it's ok

        if let Some(condition) = condition {
            if !condition.is_default() {
                // query = condition.add_each_one_to(query);
            }
        }

        if let Some(_order) = &order {
            // add order to query
        }

        query.to_string()
    }

    pub async fn query(
        _session: &str, // This is not String: is a struct in real code
        _db: &str,     // This is not String: is a connection/transaction in real code
        input: &mut PlayerListInput,
    ) -> Vec<DBPlayer> {
        // Check session here

        let _query = Self::query_construction(input.condition.as_mut(), &input.order);

        let players = vec![]; // fetch_them_from_db_using_query_here;

        players
    }
}

async fn players_from_repo(input: &mut PlayerListInput) -> Vec<DBPlayer> {
    DBPlayer::query("session", "db_connection", input).await
}

#[tokio::main]
async fn main() {
    let mut input = PlayerListInput::default();

    let players = players_from_repo(&mut input).await;

    dbg!(players);
}

I think I need new PlayerListInput or PlayerConditions methods maybe, what do you think?

Using a fn(Self, …) -> Self function on a &mut Self reference isn't possible. Except perhaps using a crate like replace_with - Rust. Or you could temporarily leave a dummy value in place with std::mem::take. Otherwise, it's generally best practice to re-write API like set_email to be of type fn(&mut Self, …). For convenient use / chaining, builder API pattern sometimes also does fn(&mut Self, …) -> &mut Self.

2 Likes

Need to change are:

  • the signature of set_names as @steffahn said
  • add let before condition = because lifetime of new player condition does not matches 'a
  • add two let statement before condition = because right side of the two &mut need to be assigned before be mutability referenced and assigned to something.

With these changes, does it work as expected?

1 Like