Luca Sbardella
Luca Sbardella

Reputation: 1124

How to avoid a double borrow error in struct method

I'm newish to Rust and I'm trying to figure out how to fix the Trader.gateway_client method in the snippet below. It is caused by a double borrow of a mutable. What would be the correct way to fix the code (without cloning the self.gateway string before).

use std::collections::{HashMap};


pub struct GatewayClient {
    gateway: String,
    strategy: String,
}


pub struct Trader {
    gateway_clients: HashMap<String, GatewayClient>,
    strategy: String,
}


impl GatewayClient {
    pub fn new(gateway: &str, strategy: &str) -> Self {
        GatewayClient {
            gateway: String::from(gateway),
            strategy: String::from(strategy),
        }
    }
}


impl Trader {
    pub fn new(strategy: &str) -> Self {
        Trader {
            gateway_clients: HashMap::default(),
            strategy: String::from(strategy),
        }
    }

    pub fn gateway_client(&mut self, gateway: &str) -> &mut GatewayClient {
        self.gateway_clients.entry(String::from(gateway)).or_insert_with(|| {
            GatewayClient::new(gateway, &self.strategy)
        })
    }
}

The error thrown by the compiler is

63 |       pub fn gateway_client(&mut self, gateway: &str) -> &mut GatewayClient {
   |                             - let's call the lifetime of this reference `'1`
64 |           self.gateway_clients.entry(String::from(gateway)).or_insert_with(|| {
   |           --------------------                                             ^^ immutable borrow occurs here
   |           |
   |  _________mutable borrow occurs here
   | |
65 | |             GatewayClient::new(gateway, &self.strategy)
   | |                                          ------------- second borrow occurs due to use of `self` in closure
66 | |         })
   | |__________- returning this value requires that `self.gateway_clients` is borrowed for `'1`

Upvotes: 0

Views: 437

Answers (2)

Todd
Todd

Reputation: 5385

A way to get around the borrowing conflict would be to eliminate the second explicit borrow within the callback by extracting what you need from self outside of it.

    pub fn gateway_client(&mut self, gateway: &str) -> &mut GatewayClient {
        let strategy = &self.strategy;

        self.gateway_clients
            .entry(gateway.into())
            .or_insert_with(|| GatewayClient::new(gateway, strategy))
    }

This looks like it may be a second borrow against self within the callback, but the compiler accepts this.

.or_insert_with() vs. .or_insert()

A fix was suggested using .or_insert(), but your choice to use .or_insert_with() is correct.

The advantage of using .or_insert_with() over .or_insert() in this case is the callback only gets executed if the entry doesn't exist. If using .or_insert(), GatewayClient::new() would be invoked whether or not the entry existed every time.

Upvotes: 2

Alexey S. Larionov
Alexey S. Larionov

Reputation: 7927

You just didn't need to use .or_insert_with which allows to execute custom code via a closure. You just wanted to insert a value, and there's a method .or_insert, that accepts a value.

pub fn gateway_client(&mut self, gateway: &str) -> &mut GatewayClient {
    self.gateway_clients
        .entry(gateway.to_string())
        .or_insert(GatewayClient::new(gateway, &self.strategy))
}

Also if you'll ever want to get rid of a million of creations of new strings with String::from(...), you can look towards std::borrow::Cow type

Upvotes: 1

Related Questions