How to handle object state in threads

Hello all,

This is my first post hence I want to say warm "hello" for the Rustaceans.

I'm writing an application which is based on factory pattern - there is an object created and this object exposes some methods and data, it's only one instance while programming is running. So nothing special.

But my problem is: this object itself has couple methods which work in separate threads (thread::spawn) - only these methods in threads are allowed to modify some fields - they receive appropriate data from channels. And here's my problem, lifetimes and static etc.

Initical code:

use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;

trait Exchange {
    fn get_all_symbols(&self);
    fn get_all_prices(&self);
}

#[derive(Debug, Clone)]
struct Binance {
    pub name: String,
    pub my_map: HashMap<i64, i64>,
    pub limit: u64,
}

impl Exchange for Binance {
    fn get_all_symbols(&self) {
        println!("get_all_symbols");
    }

    fn get_all_prices(&self) {
        println!("get_all_prices");
    }
}

impl Binance {
    pub fn new() -> Self {
        Binance{
            name: String::from("starting value"),
            my_map: HashMap::new(),
            limit: 5,
        }
    }

    fn set_limit(&mut self, limit: u64) {
        self.limit = limit;
    }

    fn get_limit(&self) {
        println!("current limit is {}", &self.limit);
    }

    pub fn init(&mut self) {
        let mut copy = self.clone(); // this is probably the issue
        thread::spawn(move || {
            copy.change_name("default".to_string());
        });

        let mut copy = self.clone(); // this is probably the issue
        thread::spawn(move || {

            copy.add_map_entry(42);
        });

        // here will be instantiate couple threads, not only these two
    }
}

impl Binance {
   pub fn change_name(&mut self, new_name: String) {
       self.name = new_name
   }

    pub fn add_map_entry(&mut self, entry: i64) {
        self.my_map.entry(entry)
                        .or_insert(entry);
    }

    // more functions
}


fn main() {
    let mut bin = Binance::new();
    println!("name: {}", bin.name);
    println!("my_map: {:?}", bin.my_map);

    // below doesn't change anything, probably because .clone()
    bin.init();
    println!("after init name: {}", bin.name);
    println!("after init my_map: {:?}", bin.my_map);

    // this modifies data 
    bin.change_name("new_name".to_string());
    bin.add_map_entry(100);
    println!("after change name: {}", bin.name);
    println!("after change my_map: {:?}", bin.my_map);

}

It's easy to understand for experienced developers that functions in thread::spawn operate on duplicated receiver (?) and they modify value which is not same for everywhere. And I've got couple impl blocks as in my code this is scattered in couple files.

I recognized this problem and yes, tried to borrow to init() method with lifetimes, so my code now is:

use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;

trait Exchange<'a> {
    fn get_all_symbols(&self);
    fn get_all_prices(&self);
}

#[derive(Debug)]
struct Binance<'a> {
    pub name: &'a String,
    pub my_map: &'a mut HashMap<i64, i64>,
    pub limit: u64,
}

impl Exchange<'static> for Binance<'static> {
    fn get_all_symbols(&self) {
        println!("get_all_symbols");
    }

    fn get_all_prices(&self) {
        println!("get_all_prices");
    }
}

impl Binance<'static> {
    pub fn new() -> Self {
        Binance{
            name: &String::from("starting value"),
            my_map: &mut HashMap::new(),
            limit: 5,
        }
    }

    fn set_limit(&mut self, limit: u64) {
        self.limit = limit;
    }

    fn get_limit(&self) {
        println!("current limit is {}", &self.limit);
    }

    pub fn init(&mut self) {
        thread::spawn(move || {
            // comment
            self.change_name("default".to_string());
        });

        thread::spawn(move || {
            // comment
            self.add_map_entry(42);
        });

        // here will be instantiate couple threads, not only these two
    }
}

impl Binance<'static> {
   pub fn change_name(&mut self, new_name: String) {
       self.name = &new_name
   }

    pub fn add_map_entry(&mut self, entry: i64) {
        self.my_map.entry(entry)
                        .or_insert(entry);
    }

    // change map etc
}


fn main() {
    let mut bin = Binance::new();
    println!("name: {}", bin.name);
    println!("my_map: {:?}", bin.my_map);

    // below doesn't change anything, probably because .clone()
    bin.init();
    println!("after init name: {}", bin.name);
    println!("after init my_map: {:?}", bin.my_map);

    // this modifies data
    bin.change_name("new_name".to_string());
    bin.add_map_entry(100);
    println!("after change name: {}", bin.name);
    println!("after change my_map: {:?}", bin.my_map);

}

I think I understand what lifetimes means here, but now my problem with init() method is that compiler still complains: error[E0759]: self has an anonymous lifetime '_ but it needs to satisfy a 'static lifetime requirement - I don't know how to do it syntactically correct to satisfy compiler

Also I was trying to do the same with Arc<Mutex<Self>> but finally I run into the same issue.

So my problems are:

  1. What's the correct syntax for this lifetime in example 2? How do it correctly to changing something in separate threads (assuming only functions from these thread can mutate some fields from struct)
  2. Maybe I'm doing it wrong from Rust perspective - maybe init() should not be a part of impl block but be outside? I don't know what's the practise in such case, but there is possibility that this is also X/Y problem.

Appreaciate for any clues!
Thanks in advance,

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.