1. The Problem
Consdier the following struct and constructor:
#[derive(Clone, Debug)] pub struct ApiServer { port: u16, // mutex is not clonable, we wrap by Arc cache: Arc<Mutex<HashMap<String, BlockChain>>>, }
impl ApiServer { pub fn new(port: u16) -> Self { let api_server = ApiServer { port, cache: Arc::new(Mutex::new(HashMap::<String, BlockChain>::new())), }; let wallet_miner = Wallet::new(); let unlocked_cache = &mut api_server.cache.lock().unwrap(); unlocked_cache.insert( "blockchain".to_string(), BlockChain::new(wallet_miner.get_address()), ); api_server } }
now our compiler will complain:
2. What Causes the Problem?
2.1. Rules for Reference and Ownership
Rust's borrow checker follows simple, strict rules:
-
"If you have a reference to something, you can't move that something"
-
"References must always point to valid data".
It doesn't matter that the reference dies immediately - the rule is applied at compile time based on code structure, not runtime execution.
2.2. Which rule have we broken?
Let's start with the line of borrowing:
let unlocked_cache = &mut api_server.cache.lock().unwrap(); unlocked_cache.insert( "blockchain".to_string(), BlockChain::new(wallet_miner.get_address()), ); api_server }
-
api_server
is returned and its data ownership is potentially moved to another variable that receives the return -
unlocked_cache
references toapi_server.cache
, however, asapi_server
is moved, the accessapi_server.cache
crashed -
No matter
unlocked_cache
dies immediately outside of the scope ofnew()
or not, we are referencing and moving a data at the same time.
3. Solution 1
We don't create intermediate reference, we simply mutate the data that the MutexGuard
referencing to:
impl ApiServer { pub fn new(port: u16) -> Self { let api_server = ApiServer { port, cache: Arc::new(Mutex::new(HashMap::<String, BlockChain>::new())), }; let wallet_miner = Wallet::new(); api_server.cache.lock().unwrap().insert( "blockchain".to_string(), BlockChain::new(wallet_miner.get_address()), ); api_server } }
4. Solution 2
We avoid moving and referencing to the same variable in the same scope:
impl ApiServer { pub fn new(port: u16) -> Self { let api_server = ApiServer { port, cache: Arc::new(Mutex::new(HashMap::<String, BlockChain>::new())), }; let wallet_miner = Wallet::new(); { let unlocked_cache = &mut api_server.cache.lock().unwrap(); unlocked_cache.insert( "blockchain".to_string(), BlockChain::new(wallet_miner.get_address()), ); } api_server } }