Не может заимствовать как изменяемый более чем один раз за один код - но может в другом очень похоже
У меня есть этот фрагмент, который не проходит проверку заимствования:
use std::collections::HashMap;
enum Error {
FunctionNotFound,
}
#[derive(Copy, Clone)]
struct Function<'a> {
name: &'a str,
code: &'a [u32],
}
struct Context<'a> {
program: HashMap<&'a str, Function<'a>>,
call_stack: Vec<Function<'a>>,
}
impl<'a> Context<'a> {
fn get_function(&'a mut self, fun_name: &'a str) -> Result<Function<'a>, Error> {
self.program
.get(fun_name)
.map(|f| *f)
.ok_or(Error::FunctionNotFound)
}
fn call(&'a mut self, fun_name: &'a str) -> Result<(), Error> {
let fun = try!(self.get_function(fun_name));
self.call_stack.push(fun);
Ok(())
}
}
fn main() {}
error[E0499]: cannot borrow `self.call_stack` as mutable more than once at a time
--> src/main.rs:29:9
|
27 | let fun = try!(self.get_function(fun_name));
| ---- first mutable borrow occurs here
28 |
29 | self.call_stack.push(fun);
| ^^^^^^^^^^^^^^^ second mutable borrow occurs here
...
32 | }
| - first borrow ends here
Я чувствую, что проблема связана с тем, что HashMap
либо возвращает None
или ссылка на значение внутри структуры данных. Но я не хочу этого: я хочу, чтобы self.get_function
должен вернуть либо байтовую копию сохраненного значения, либо ошибку (вот почему я ставлю .map(|f| *f)
, а также Function
является Copy
).
изменения &'a mut self
что-то еще не помогает.
Тем не менее, следующий фрагмент, несколько похожий по духу, принят:
#[derive(Debug)]
enum Error {
StackUnderflow,
}
struct Context {
stack: Vec<u32>,
}
impl Context {
fn pop(&mut self) -> Result<u32, Error> {
self.stack.pop().ok_or(Error::StackUnderflow)
}
fn add(&mut self) -> Result<(), Error> {
let a = try!(self.pop());
let b = try!(self.pop());
self.stack.push(a + b);
Ok(())
}
}
fn main() {
let mut a = Context { stack: vec![1, 2] };
a.add().unwrap();
println!("{:?}", a.stack);
}
Теперь я в замешательстве. В чем проблема с первым фрагментом? Почему это не происходит во втором?
Фрагменты кода являются частью большего куска кода. Чтобы предоставить больше контекста, это на Rust Playground показывает более полный пример с неисправным кодом, и это показывает более раннюю версию безHashMap
, который проходит проверку и работает нормально.
2 ответа
Вы попали в пожизненную ловушку. Добавление того же времени жизни к большему количеству ссылок еще больше ограничит вашу программу. Добавление большего количества жизней и предоставление каждой ссылки минимально возможного времени жизни позволит больше программ. Как отмечает @o11c, снятие ограничений на 'a
время жизни решит вашу проблему.
impl<'a> Context<'a> {
fn get_function(&mut self, fun_name: &str) -> Result<Function<'a>, Error> {
self.program
.get(fun_name)
.map(|f| *f)
.ok_or(Error::FunctionNotFound)
}
fn call(&mut self, fun_name: &str) -> Result<(), Error> {
let fun = try!(self.get_function(fun_name));
self.call_stack.push(fun);
Ok(())
}
}
Это работает потому, что Rust вставляет новые времена жизни, поэтому в компиляторе сигнатуры вашей функции будут выглядеть так:
fn get_function<'b>(&'b mut self, fun_name: &'b str) -> Result<Function<'a>, Error>
fn call<'b>(&'b mut self, fun_name: &'b str) -> Result<(), Error>
Всегда старайтесь не использовать время жизни и пусть компилятор будет умным. Если это не помогло, не распыляйте время жизни везде, подумайте, где вы хотите передать право собственности и где вы хотите ограничить срок жизни ссылки.
Вам нужно только удалить ненужные пожизненные квалификаторы, чтобы ваш код компилировался:
fn get_function(&mut self, fun_name: &str) -> Result<Function<'a>, Error> { ... }
fn call(&mut self, fun_name: &str) -> Result<(), Error> { ... }
Ваша проблема заключалась в том, что вы связали жизнь &mut self
и время жизни значения, хранящегося в нем (Function<'a>
), что в большинстве случаев не нужно. С этой зависимостью, которая присутствовала в get_function()
определение, компилятор должен был предположить, что результат вызова self.get_function(...)
заимствует self
и, следовательно, он запрещает вам заимствовать его снова.
Время жизни на &str
Аргумент также не нужен - он просто ограничивает возможный набор значений аргумента без причины. Ваш ключ может быть строкой с произвольным временем жизни, а не только 'a
,