Lucas Kopp
Lucas Kopp

Reputation: 1

How am I handling lifetimes incorrectly?

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

Answers (1)

Kevin Reid
Kevin Reid

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 'statics; 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:

  • For Vec in particular, the Vec may need to reallocate and therefore move its items, invalidating prior references.
  • As written, if the code were accepted, you could get multiple overlapping &muts 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

Related Questions