Why does only one of those implementations work? They should be the same

I'm just learning Rust and I thought I would challenge myself by writing a Telegram RSS bot.

The concept is to fetch the feed, check (with the help of an SQLite database) if the feed contains any new items and then send them to me via the bot.

To check if an item is already in the DB, I count all rows where the id matches the guid of the item. I use rusqlite and I want to prepare a count statement only once, that I can then reuse for every guid and for every time I fetch the feed again.

Below is the struct and the constructor for my FeedReader

use rss::Item;
use rusqlite::{Connection, Statement};

use crate::feed::fetch_channel;

pub struct FeedReader<'a> {
    url: String,
    conn: &'a Connection,
    count_stmt: Option<Statement<'a>>,
}

impl<'a> FeedReader<'a> {
    pub fn new(url: &str, conn: &'a Connection) -> FeedReader<'a> {
        FeedReader {
            url: url.to_string(),
            conn,
            count_stmt: None,
        }
    }

    // ...
}

I implemented a singleton factory method like this:

    fn create_count_stmt(&mut self) -> &mut rusqlite::Statement<'a> {
        if let Some(stmt) = &mut self.count_stmt {
            return stmt;
        }

        let sql = &"SELECT COUNT(*) FROM guids WHERE id = ?1";
        let stmt = self.conn.prepare(sql).unwrap();

        self.count_stmt = Some(stmt);

        return self.count_stmt.as_mut().unwrap();
    }

and I get the following errors:

error[E0506]: cannot assign to `self.count_stmt` because it is borrowed
  --> src/feed_reader.rs:51:9
   |
43 |     fn create_count_stmt(&mut self) -> &mut rusqlite::Statement<'a> {
   |                          - let's call the lifetime of this reference `'1`
44 |         if let Some(stmt) = &mut self.count_stmt {
   |                             -------------------- borrow of `self.count_stmt` occurs here
45 |             return stmt;
   |                    ---- returning this value requires that `self.count_stmt` is borrowed for `'1`
...
51 |         self.count_stmt = Some(stmt);
   |         ^^^^^^^^^^^^^^^ assignment to borrowed `self.count_stmt` occurs here

error[E0499]: cannot borrow `self.count_stmt` as mutable more than once at a time
  --> src/feed_reader.rs:53:16
   |
43 |     fn create_count_stmt(&mut self) -> &mut rusqlite::Statement<'a> {
   |                          - let's call the lifetime of this reference `'1`
44 |         if let Some(stmt) = &mut self.count_stmt {
   |                             -------------------- first mutable borrow occurs here
45 |             return stmt;
   |                    ---- returning this value requires that `self.count_stmt` is borrowed for `'1`
...
53 |         return self.count_stmt.as_mut().unwrap();
   |                ^^^^^^^^^^^^^^^ second mutable borrow occurs here

I then tried to resolve it and changed it to:

    fn create_count_stmt(&mut self) -> &mut rusqlite::Statement<'a> {
        if self.count_stmt.is_none() {
            let sql = &"SELECT COUNT(*) FROM guids WHERE id = ?1";
            let stmt = self.conn.prepare(sql).unwrap();
            self.count_stmt = Some(stmt);
        }

        return self.count_stmt.as_mut().unwrap();
    }

This one works but I don't understand what the difference is. As far as I can tell, they both to the same (at least when viewed from the outside).

Can someone please explain this to me?

The borrow checker has a limitation where it does not understand the idea of taking a mutable borrow, and then either returning it from the function or dropping it and taking other borrows. It treats &mut self.count_stmt as if it still exists for the rest of the function, thus creating the conflict you get. (This may be fixed in the future, but it will be part of a major change — the new “Polonius” borrow checker algorithm.)

Your solution is one way to avoid it — generally stated, it's to avoid taking the mutable borrow that you might return, until you know for sure you're going to return it.

Another solution I just heard of is to move the mutable borrow into the match's pattern:

        if let Some(ref mut stmt) = self.count_stmt {
            return stmt;
        }

That way, the compiler recognizes that the borrow only exists if the match succeeds. However, this doesn't help for e.g. getting mutable references out of a collection; only for direct access to struct/enum fields.

4 Likes

Thanks for taking the time to explain it to me. :grinning:

Here's the issue in case anyone wants to follow it.

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.