Мне нужна помощь в настройке средства проверки заимствований в Rust. Ниже приведен минимальный пример проблемы, с которой я столкнулся:
struct VecMutRef<'a> {
vec: &'a mut Vec<usize>,
}
trait Length {
fn len(&self) -> usize;
}
impl<'a> Length for VecMutRef<'a> {
fn len(&self) -> usize {
self.vec.len()
}
}
fn f<'a>(v: &'a mut Vec<usize>) -> usize
where
VecMutRef<'a>: Length,
{
let first = g(v);
let second = g(v);
first + second
}
fn g<'a>(v: &'a mut Vec<usize>) -> usize
where
VecMutRef<'a>: Length,
{
let vec = VecMutRef { vec: v };
vec.len()
}
Компилятор жалуется, что я не могу дважды заимствовать *v в функции f. Я думал, что к тому времени, когда будет возвращен первый вызов функции g, это будет сделано с изменяемой ссылкой на v, так что я снова смогу заимствовать *v с возможностью изменения. Но, судя по всему, это не так. Было бы здорово, если бы кто-нибудь объяснил мне, в чем дело, и предложил возможный обходной путь. Я понимаю, что в этом конкретном примере мне не нужна изменяемая ссылка на Vec только для того, чтобы получить ее длину. Пример приведен только для иллюстративных целей.
Использование for<'b> VecMutRef<'b>: Length также исправляет ошибку.
@kmdreko Следует ли сообщать об этой ошибке (или о ней уже сообщалось)?
@kmdreko @cdhowie Спасибо вам за комментарии. Я подтвердил, что удаление предложения where и использование привязки признака более высокого ранга устраняют проблему в приведенном примере. К сожалению, ни один из них, похоже, не решает проблемы с моим реальным кодом, но приятно знать, что это может быть ошибка компилятора.
@user4815162342 user4815162342 почти уверен, что есть, но я никогда не могу его найти, когда ищу.
Если эта проблема все еще возникает в более сложном коде, убедитесь, что вы не слишком ограничиваете свою жизнь следующим образом: Почему это изменяемое заимствование живет за пределами своей области действия?

Это не ошибка компилятора, компилятор делает именно то, что здесь просили.
g() теоретически может использовать v на протяжении всей своей жизни ('a), поскольку ему дается такая длинная ссылка.
Обычно мы бы дали g() более новое время жизни, более короткое, чем f() у 'a, но здесь мы не можем этого сделать, поскольку известно, что VecMutRef реализует Length только для g() (что требует 'a). В результате мы дважды передаем v с временем жизни 'a, что недопустимо.
Если вам сложно следовать приведенному выше объяснению, вот пример, который, надеюсь, прояснит ситуацию:
use std::sync::Mutex;
struct VecMutRef<'a> {
vec: &'a mut Vec<usize>,
}
trait Length {
fn evil(self);
}
static ALL: Mutex<Vec<&'static mut Vec<usize>>> = Mutex::new(Vec::new());
impl Length for VecMutRef<'static> {
fn evil(self) {
ALL.lock().unwrap().push(self.vec);
}
}
fn f<'a>(v: &'a mut Vec<usize>) -> usize
where
VecMutRef<'a>: Length,
{
let first = g(v);
let second = g(v);
first + second
}
fn g<'a>(v: &'a mut Vec<usize>) -> usize
where
VecMutRef<'a>: Length,
{
VecMutRef { vec: v }.evil();
0
}
fn main() {
f(Box::leak(Box::new(vec![1, 2, 3])));
}
Здесь был изменен код, но важно то, что подписи f() и g() не были изменены вообще. Это означает, что если ваш код скомпилируется, его тоже придется скомпилировать. Но этот код явно недействителен — он создает две дублированные ссылки &mut в ALL.
Правильным решением здесь является продолжительность жизни с более высоким рейтингом (for<'a> VecMutRef<'a>: Length). Если это не сработает, вы можете открыть новый вопрос с вашим конкретным вариантом использования.
Это действительно помогает мне понять, как компилятор может стать косноязычным, но все равно кажется, что он должен быть скомпилирован, поскольку фраза «VecMutRef, как известно, реализует Length только для 'a» неверно в коде OP - оба VecMutRef и Length являются известными типами и таким образом, компилятор должен иметь возможность ссылаться на impl<'a> Length for VecMutRef<'a> и понимать, что любой 'a действителен. Наличие ограничения VecMutRef<'a>: Length не должно заставлять компилятор думать, что 'a — единственный вариант.
@kmdreko Эта часть связана с тем, что компилятор всегда будет отдавать предпочтение ограничениям, см. stackoverflow.com/a/78172859/7884305. Обычно вы можете решить эту проблему, полностью указав, но, поскольку это жизни, вы не можете сделать это здесь.
Я согласен, что это та же проблема, но я называю это ошибкой, поскольку она предотвращает компиляцию действительного кода по произвольным причинам.
Привет. Спасибо за подробное объяснение! Думаю, теперь я понимаю суть проблемы. После некоторых хлопот мне удалось заставить версию с более высоким рейтингом времени жизни работать с моим реальным кодом. Раньше я использовал константные универсальные выражения, которые являются нестабильными функциями. По какой-то причине они не позволяли компилятору проверять границы времени жизни более высокого ранга, но после удаления общих выражений const мой код компилируется нормально.
Это похоже на ошибку компилятора, когда ограничение делает его недальновидным. Кажется, кажется, что, поскольку
'aудовлетворяет ограничению вf, которое также используется вg, он должен использовать исходный'aво вложенных вызовах, даже если более короткое время жизни будет работать. Это очевидно, поскольку удаление полного предложенияwhereизfприводит к его компиляции.