Reputation: 1
I am new to rust and wanted to create a simple application that looked at stock analytics to learn. Everything was going fine until I attempted to incorporate a cache into the application. I tried many different state management approaches but found that people recommended passing state from parent to child, so that is what I attempted. So now, many functions require the mutable reference to the Cache struct declared in the main function.
At first I was doing fine just tweaking function signatures until the compiler stopped complaining; however, one function incorporates a loop, and inside the loop the cache is passed to a method. This caused the issue where multiple mutable borrows were taking place inside the iteration.
Here is the error[E0499]:
cannot borrow `*cache` as mutable more than once at a time
--> src\screener.rs:34:54
|
34 | stocks.push(Stock::new(stock.symbol, cache).await);
| -------------------------^^^^^-
| | |
| | `*cache` was mutably borrowed here in the previous iteration of the loop
| argument requires that `*cache` is borrowed for `'static'
The method where the error exist:
async fn symbols_to_stocks(symbols: Vec<AvailableTraded>, cache: &'static mut Cache) -> Vec<&'static mut Stock> {
let mut stocks = vec![];
for stock in symbols {
if stock.type_ == "stock" && (stock.exchange_short_name == "NYSE"
|| stock.exchange_short_name == "NASDAQ")
{
stocks.push(Stock::new(stock.symbol, cache).await);
}
}
stocks
}
This is the new method which I am calling inside the loop:
pub async fn new(ticker: String, cache: &'static mut Cache) -> &'static mut Self {
let index = cache.stock_index_in_cache(ticker.clone());
if index >= 0 {
return &mut cache.stocks[index as usize];
}
let stock_to_be = Self {
ticker,
statements: Statements::new(),
metrics: Metrics::new(),
};
return cache.add_stock(stock_to_be).await;
}
The Cache struct:
pub struct Cache {
pub stocks: Vec<Stock>,
}
The stock_index_in_cache method:
pub fn stock_index_in_cache(&mut self, ticker: String) -> i64 {
let stock_already_exist = self.stocks.iter().position(|stock| stock.ticker == ticker);
match stock_already_exist {
Some(v) => {
return v as i64;
}
None => -1,
}
}
And the stock struct:
pub struct Stock {
pub ticker: String,
pub statements: Statements,
pub metrics: Metrics,
}
I have tried multiple recommendations on SO to avoid the multiple mut borrow issue but I either could not implement them or they did not work. What is really the issue here?
Upvotes: 0
Views: 66
Reputation: 43902
The problem is your use of &'static mut
. That says “this is borrowed forever”, which is incompatible with calling a function in a loop because each call must relinquish the borrow, i.e. not require it to last forever. You should remove all of the 'static
s; they are unnecessary.
However, that will not be enough to make the code work, because you will still have the conflict in symbols_to_stocks
between borrowing one stock
from the cache and adding more to it. You can't do that, for several reasons:
Vec
in particular, the Vec
may need to reallocate and therefore move its items, invalidating prior references.&mut
s to the same element simply by passing the same symbol to Stock::new
twice, which is unsound.You will need to adopt a different strategy; the most straightforward one is to find/insert entries in the cache and get indexes (not references), then use that index to lookup an individual Stock
when you want to read or modify it. Collecting a Vec<usize>
of indexes is much more tractable than a Vec<&mut Stock>
.
Another option is keeping Rc<RefCell<Stock>>
in your cache, for runtime-checked ownership and borrowing; this gets you the sort of behavior you might expect from a garbage-collection-and-OOP language, but at the cost of needing to explicitly access the RefCell
and some other complications.
Upvotes: 1