如何解决多次借用编译错误

huangapple go评论73阅读模式
英文:

How to solve multiple borrows compilation error

问题

execute_insert函数中,您尝试同时对table进行可变引用和不可变引用,这会导致借用冲突。在Rust中,不允许同时拥有可变引用和不可变引用,因为这可能会导致数据不一致。要解决这个问题,您可以将table的可变引用传递给table_end,然后将它传递给cursor_value。这样,您可以确保在同一时间只有一个可变引用。

以下是修改后的代码片段:

fn execute_insert(table: &mut Table) {
    let mut cursor = Cursor::table_end(table); // 使用可变引用
    let _buf = table.cursor_value(&mut cursor); // 传递可变引用给 cursor_value
    // 在这里进行插入操作
}

通过将可变引用传递给table_endcursor_value,您可以解决 borrow checker 错误。这确保了在同一时间只有一个可变引用,同时允许您进行插入操作。

英文:

I'm writing a toy SQL database to both learn Rust and about databases (I'm translating the code from a tutorial written in C). I'm having trouble getting the following code to compile, due to borrow checker errors:

struct Pager {
    pages: [Option<Box<[u8; PAGE_SIZE]>>; TABLE_MAX_PAGES]
}

impl Pager {
    fn new() -> Self {
        const INIT: Option<Box<[u8; PAGE_SIZE]>> = None;
        Self {
            pages: [INIT; TABLE_MAX_PAGES],
        }
    }

    fn get_page(&mut self, page_num: usize) -> &mut [u8] {
        // returns a page, creating a new one if necessary
        let page = Box::new([0u8; PAGE_SIZE]);
        self.pages[0].replace(page);

        &mut self.pages[page_num].as_mut().unwrap()[..]
    }
}

struct Table {
    pager: Pager,
    num_rows: usize,
}

impl Table {
    fn new() -> Self { 
        Self { pager: Pager::new(), num_rows: 0 }
    }

    fn cursor_value(&mut self, cursor: &Cursor) -> &mut [u8] { 
        let row_num = cursor.row_num;
        let page_num: usize = row_num / ROWS_PER_PAGE;

        let byte_offset = (row_num % ROWS_PER_PAGE) * ROW_SIZE;

        let page = self.pager.get_page(page_num);
        &mut page[byte_offset..byte_offset+ROW_SIZE]
    }
}

struct Cursor<'a> {
    table: &'a Table,
    row_num: usize,
    end_of_table: bool,
}

impl <'a> Cursor<'a> {
    fn table_end(table: &'a Table) -> Self { 
        Self { table, row_num: table.num_rows, end_of_table: true }
    }

    fn advance(&mut self) {
        self.row_num += 1;
        if self.row_num >= self.table.num_rows {
            self.end_of_table = true;
        }
    }
}

fn execute_insert(table: &mut Table) {
    let cursor = Cursor::table_end(&table);
    let _buf = table.cursor_value(&cursor);
    // here we insert new row into the table by writing into _buf
    // ...
}

The problem is that I'm borrowing the table twice inside the execute_insert function: once with a mutable reference (for cursor_value method) and once with an immutable one (for table_end method):

28 |     let cursor = Cursor::table_end(&table);
   |                                    ------ immutable borrow occurs here
29 |     let buf = table.cursor_value(&cursor);
   |               ^^^^^^------------^^^^^^^^^
   |               |     |
   |               |     immutable borrow later used by call
   |               mutable borrow occurs here

Is there a way I can achieve this in Rust, or should I maybe use a different design for those data structures?

答案1

得分: 2

你的代码在概念上存在问题。可能存在多个光标,如果一个光标执行table.cursor_value(&cursor),那么对于另一个光标,self.table.num_rows就会改变,而end_of_table可能为true,尽管光标并未到达末尾。

这就是这段代码无法编译的原因:在Rust中,有一个保证,只要你持有对某物的不可变引用(比如在Cursor中对self.table的引用),那么它的值 不会改变。因此,在Cursor中通过不可变引用引用它并且不使用Rc<RefCell>等内部可变性的情况下,你永远无法修改表。

对我来说,解决方案很简单:你一次只想要一个光标(至少这是我的假设),所以只需通过&mut引用引用self.table。然后你根本不需要Table::cursor_value(),可以直接从Cursor::value()中执行它。

像这样:

// 代码部分不翻译
英文:

Your code is conceptually unsound. It's possible that multiple cursors exist, and if one cursor does table.cursor_value(&amp;cursor), then the self.table.num_rows changes for the other cursor, and end_of_table might be true, although the cursor is not at the end.

This is why this code fails to compile: It's guaranteed in Rust that as long as you hold an immutable reference to something (like self.table in Cursor), then its value will not change. Therefore you won't ever be able to modify the table as long as you reference it immutably in Cursor and don't use inner mutability via Rc&lt;RefCell&gt; or similar.

The solution is simple to me: you only want one cursor at a time anyway (that's at least my assumption), so simply reference self.table via a &amp;mut reference. Then you don't need Table::cursor_value() at all, you can directly do it from Cursor::value().

Like this:

const ROWS_PER_PAGE: usize = 64;
const ROW_SIZE: usize = 128;
const PAGE_SIZE: usize = ROW_SIZE * ROWS_PER_PAGE;
const TABLE_MAX_PAGES: usize = 32;

struct Pager {
    pages: [Option&lt;Box&lt;[u8; PAGE_SIZE]&gt;&gt;; TABLE_MAX_PAGES],
}

impl Pager {
    fn new() -&gt; Self {
        const INIT: Option&lt;Box&lt;[u8; PAGE_SIZE]&gt;&gt; = None;
        Self {
            pages: [INIT; TABLE_MAX_PAGES],
        }
    }

    fn get_page(&amp;mut self, page_num: usize) -&gt; &amp;mut [u8] {
        // returns a page, creating a new one if necessary
        let page = Box::new([0u8; PAGE_SIZE]);
        self.pages[0].replace(page);

        &amp;mut self.pages[page_num].as_mut().unwrap()[..]
    }
}

struct Table {
    pager: Pager,
    num_rows: usize,
}

impl Table {
    fn new() -&gt; Self {
        Self {
            pager: Pager::new(),
            num_rows: 0,
        }
    }

    fn get_row(&amp;mut self, row_num: usize) -&gt; &amp;mut [u8] {
        let page_num: usize = row_num / ROWS_PER_PAGE;

        let byte_offset = (row_num % ROWS_PER_PAGE) * ROW_SIZE;

        let page = self.pager.get_page(page_num);
        &amp;mut page[byte_offset..byte_offset + ROW_SIZE]
    }
}

struct Cursor&lt;&#39;a&gt; {
    table: &amp;&#39;a mut Table,
    row_num: usize,
    end_of_table: bool,
}

impl&lt;&#39;a&gt; Cursor&lt;&#39;a&gt; {
    fn table_end(table: &amp;&#39;a mut Table) -&gt; Self {
        let row_num = table.num_rows;
        Self {
            table,
            row_num,
            end_of_table: true,
        }
    }

    fn advance(&amp;mut self) {
        self.row_num += 1;
        if self.row_num &gt;= self.table.num_rows {
            self.end_of_table = true;
        }
    }

    fn value(&amp;mut self) -&gt; &amp;mut [u8] {
        self.table.get_row(self.row_num)
    }
}

fn execute_insert(table: &amp;mut Table) {
    let mut cursor = Cursor::table_end(table);
    let _buf = cursor.value();
    // here we insert new row into the table by writing into _buf
    // ...
}

huangapple
  • 本文由 发表于 2023年3月7日 01:31:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/75654006.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定