Я использую библиотеку ratatui для создания программы на Rusc 1.77.2.
У меня есть следующий код:
self.terminal().draw(|frame| self.render_frame(frame))?;
где terminal()
— это метод получения структуры, содержащей экземпляр терминала, который принимает &self
, а draw — это метод ratatui для того, что рисует на экране. self.render_frame
берет себя на себя.
если terminal()
возвращает &mut Terminal
, то это вызывает ошибку заимствования, поэтому вместо terminal()
возвращает RefMut<Terminal>
, в то время как Self
имеет поле terminal
, которое содержит RefCell<Terminal
, и вызывает self.terminal.borrow_mut()
как реализацию terminal()
.
Моя логика такова: поскольку terminal()
возвращает refmut
, собственное значение, которое не будет учитываться проверкой заимствований, и пока terminal()
не используется в self.render_frame()
, проблем не будет.
Однако это не так, поскольку я получаю следующую ошибку:
error[E0502]: cannot borrow `self` as mutable because it is also borrowed as immutable
--> src/tui.rs:75:38
|
75 | self.terminal().draw(|frame| self.render_frame(frame))?;
| --------------- ^^^^^^^ ---- - ... and the immutable borrow might be used here, when that temporary is dropped and runs the destructor for type `RefMut<'_, ratatui::Terminal<ratatui::backend::CrosstermBackend<Stdout>>>`
| | | |
| | | second borrow occurs due to use of `self` in closure
| | mutable borrow occurs here
| immutable borrow occurs here
| a temporary with access to the immutable borrow is created here ...
Что меня сбивает с толку, так это то, почему RefMut
учитывается проверкой заимствований, когда соблюдение этих правил во время выполнения якобы и есть весь смысл RefCell
.
В частности, мне интересно узнать, почему я использую RefMut
/RefCell
неправильно и как я могу это исправить; так как я не могу найти никакой информации об ошибках проверки заимствований при использовании Refcell
.
Что меня сбивает с толку, так это то, почему
RefMut
учитывается проверкой заимствований, когда соблюдение этих правил во время выполнения якобы и есть весь смыслRefCell
.
Обеспечение соблюдения правил во время выполнения — это суть RefCell
, но в отношении его содержания. Единственное, что дают вам такие типы внутренней изменчивости, как RefCell
, — это возможность мутировать через общую ссылку. В случае RefCell
это позволяет вам получить &mut T
из &RefCell<T>
. Другими словами, это позволяет вам «обновить» общий заимствование (&
) до эксклюзивного заимствования (&mut
), но для этого вам все равно понадобится общий заимствование RefCell
.
Это отражено в подписи RefCell::borrow_mut:
pub fn borrow_mut(&self) -> RefMut<'_, T>
Если мы обесценим явно исключенное время жизни:
pub fn borrow_mut<'a>(&'a self) -> RefMut<'a, T>
Итак, вы можете видеть, что RefMut
заимствовано из *self
(RefCell
). (Если это не так, это означает, что вы можете отбросить RefCell
, пока у вас есть RefMut
, указывающий на него. Это было бы неправильно!)
Похоже, что render_frame
занимает &mut self
, что и вызывает проблему: RefMut
удерживает общий заимствование против self
, но render_frame
хочет получить эксклюзивный заимствование.
Трудно точно сказать, как решить эту проблему, не видя остальную часть вашего кода. Один из вариантов — использовать render_frame
вместо &self
, что в данном случае разрешено. Однако для этого может потребоваться, чтобы больше частей этого типа использовали внутреннюю изменчивость.
Очень похоже на то, что вы подходите к этой проблеме с объектно-ориентированной точки зрения, и такой дизайн, как правило, не очень хорошо работает в Rust. Возможно, какой бы тип &mut self
здесь ни представлял, он делает слишком много, и его нужно разделить.
Один из способов решить основную проблему — использовать
Rc<RefCell<T>>
. Затем вы можетеclone()
Rc полностью отделить время жизни RefCell от времени жизни объекта, на который он указывает.Rc<RefCell<_>>
считается своего рода антипаттерном в Rust, но это полезный инструмент, о котором следует знать, если он вам понадобится.