The following code is a small example of what has been bugging my brain for days: How to change this mutable-referenced struct's field value?.
I'm writing for the first time and by myself a small app that follows the "clean architecture" logic as I have done with other languages (garbage collected).
Can you help me understand what I'm doing wrong and what better pattern I can use (I'm thinking of a builder pattern, but also various setters methods that allow me to change the values when I need it, in several places)?
The biggest convenience I would like to achieve is to be able to use code like this:
if let Some(condition) = &mut input.condition {
condition.set_subscribed_at_is_nil(true);
} else {
input.condition = Some(PlayerConditions::new().unset_subscribed_at_is_nil());
}
or better:
input.condition.unwrap_or_default().set_subscribed_at_is_nil(true);
// or maybe input.condition = input.condition.unwrap_or_default().set_subscribed_at_is_nil(true);
if something_happened {
input.condition.unset_it().set_another(Some("something"));
}
if something_else_happened {
input.condition.set_another(Some("something")).check_something();
}
What can you suggest?
The code:
/*
[dependencies]
tokio = { version = "1", features = ["full"] }
*/
#[derive(Debug, Default)]
pub struct PlayerConditions {
id: Option<String>,
name: Option<String>,
subscribed_at_is_nil: Option<bool>,
}
impl PlayerConditions {
pub fn new() -> Self {
Self::default()
}
pub fn is_default(&self) -> bool {
self.id.is_none() && self.name.is_none() && self.subscribed_at_is_nil.is_none()
}
pub fn id(&self) -> &Option<String> {
&self.id
}
pub fn set_id(&mut self, id: Option<String>) -> &mut Self {
self.id = id;
self
}
pub fn name(&self) -> &Option<String> {
&self.name
}
pub fn set_name(&mut self, name: Option<String>) -> &mut Self {
self.name = name;
self
}
pub fn subscribed_at_is_nil(&self) -> &Option<bool> {
&self.subscribed_at_is_nil
}
pub fn set_subscribed_at_is_nil(&mut self, subscribed_at_is_nil: Option<bool>) -> &mut Self {
self.subscribed_at_is_nil = subscribed_at_is_nil;
self
}
}
#[derive(Debug, Default)]
pub struct PlayerListInput {
pub condition: Option<PlayerConditions>,
// This is not String: is a struct in real code
pub order: Option<String>,
// other fields here...
}
#[derive(Debug)]
pub struct DBPlayer {
pub id: String,
pub name: Option<String>,
// For simplicity this is String in this example
pub subscribed_at: Option<String>,
}
impl DBPlayer {
fn query_construction<'a>(
condition: Option<&'a mut PlayerConditions>,
order: &Option<String>,
) -> String {
// Sometimes I need to change condition here (so I need the mutability of it)
let query = "SELECT * FROM players".to_string();
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(
_db: &str, // This is not String: is a connection/transaction in real code
input: &mut PlayerListInput,
) -> Vec<DBPlayer> {
// Sometimes I need to change input here (so I need the mutability of it)
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> {
// Here I need to change input checking if something is already added, for example:
if let Some(condition) = &mut input.condition {
condition.set_subscribed_at_is_nil(Some(true));
} else {
input.condition = Some(PlayerConditions::new().set_subscribed_at_is_nil(Some(true)));
}
DBPlayer::query( "db_connection", input).await
}
#[tokio::main]
async fn main() {
let mut input = PlayerListInput::default();
let players = players_from_repo(&mut input).await;
// I still need input ownership here
dbg!(input);
dbg!(players);
}
What about simply:
Yes, it's a bit longer, but it's crystal clear and we don't always need the shortest code.