Проблемы с Rust Borrow Checker

Мне нужна помощь в настройке средства проверки заимствований в 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 только для того, чтобы получить ее длину. Пример приведен только для иллюстративных целей.

Это похоже на ошибку компилятора, когда ограничение делает его недальновидным. Кажется, кажется, что, поскольку 'a удовлетворяет ограничению в f, которое также используется в g, он должен использовать исходный 'a во вложенных вызовах, даже если более короткое время жизни будет работать. Это очевидно, поскольку удаление полного предложения where из f приводит к его компиляции.

kmdreko 03.06.2024 12:42

Использование for<'b> VecMutRef<'b>: Length также исправляет ошибку.

cdhowie 03.06.2024 14:10

@kmdreko Следует ли сообщать об этой ошибке (или о ней уже сообщалось)?

user4815162342 03.06.2024 14:26

@kmdreko @cdhowie Спасибо вам за комментарии. Я подтвердил, что удаление предложения where и использование привязки признака более высокого ранга устраняют проблему в приведенном примере. К сожалению, ни один из них, похоже, не решает проблемы с моим реальным кодом, но приятно знать, что это может быть ошибка компилятора.

user3127171 03.06.2024 14:52

@user4815162342 user4815162342 почти уверен, что есть, но я никогда не могу его найти, когда ищу.

kmdreko 03.06.2024 16:23

Если эта проблема все еще возникает в более сложном коде, убедитесь, что вы не слишком ограничиваете свою жизнь следующим образом: Почему это изменяемое заимствование живет за пределами своей области действия?

kmdreko 03.06.2024 16:28
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
6
76
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это не ошибка компилятора, компилятор делает именно то, что здесь просили.

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 06.06.2024 17:35

@kmdreko Эта часть связана с тем, что компилятор всегда будет отдавать предпочтение ограничениям, см. stackoverflow.com/a/78172859/7884305. Обычно вы можете решить эту проблему, полностью указав, но, поскольку это жизни, вы не можете сделать это здесь.

Chayim Friedman 06.06.2024 17:56

Я согласен, что это та же проблема, но я называю это ошибкой, поскольку она предотвращает компиляцию действительного кода по произвольным причинам.

kmdreko 06.06.2024 18:03

Привет. Спасибо за подробное объяснение! Думаю, теперь я понимаю суть проблемы. После некоторых хлопот мне удалось заставить версию с более высоким рейтингом времени жизни работать с моим реальным кодом. Раньше я использовал константные универсальные выражения, которые являются нестабильными функциями. По какой-то причине они не позволяли компилятору проверять границы времени жизни более высокого ранга, но после удаления общих выражений const мой код компилируется нормально.

user3127171 08.06.2024 04:14

Другие вопросы по теме

Присвоение параметра метода RefCell локальной переменной приводит к ошибке компиляции
Как исправить ошибку «невозможно вернуть значение, ссылающееся на локальную переменную»?
Плохой дизайн для программы Rust со временем жизни и множественными ссылками
Странная ошибка заимствования во время компиляции при использовании `RefCell<T>`
Почему это неизменяемое поле ссылки содержит изменяемый заимствование
Как инициализировать две переменные в операторе сопоставления, не вызывая раздражения средства проверки заимствований?
Перенос кода C с изменяемыми заимствованиями — ошибка во время выполнения (пример nappgui)
Не могу заимствовать *self как неизменяемый, но не могу найти обходной путь
Возвращает принадлежащее значение и ссылку на значение
Заимствовать некоторое изменяемое значение дважды, если известно, что изменяемое значение является неизменяемым