Ошибка проверки займа после добавления универсального параметра в структуру - PullRequest
2 голосов
/ 09 мая 2019

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

Общая часть как рабочего, так и нерабочего кода:

/// Some struct that has references inside
#[derive(Debug)]
struct MyValue<'a> {
    number: &'a u32,
}

/// There are many structs similar to `MyValue` and there is a
/// trait common to them all that can create them. In this
/// example I use the `From` trait.
impl<'a> From<&'a u32> for MyValue<'a> {
    fn from(value: &'a u32) -> Self {
        MyValue { number: value }
    }
}

/// `Producer` makes objects that hold references into it. So
/// the produced object must be first dropped before any new
/// one can be made.
trait Producer<'a, T: 'a> {
    fn make(&'a mut self) -> T;
}

Вот рабочий код:

struct MyProducer {
    number: u32,
}

impl MyProducer {
    fn new() -> Self {
        Self { number: 0 }
    }
}

impl<'a, T: 'a + From<&'a u32>> Producer<'a, T> for MyProducer {
    fn make(&'a mut self) -> T {
        self.number += 1;
        T::from(&self.number)
    }
}

fn main() {
    let mut producer = MyProducer::new();

    println!(
        "made this: {:?}",
        <MyProducer as Producer<MyValue>>::make(&mut producer)
    );
    println!(
        "made this: {:?}",
        <MyProducer as Producer<MyValue>>::make(&mut producer)
    );
}

Компилирует и печатает ожидаемый результат:

made this: MyValue { number: 1 }
made this: MyValue { number: 2 }

Мне не нравится, что MyProducer фактически реализует Producer для каждого T, так как это делает невозможным непосредственный вызов make для него. Я хотел бы иметь тип, который является MyProducer для определенного T (например, для MyValue).

Для этого я хочу добавить универсальный параметр в MyProducer. Поскольку MyProducer на самом деле не использует T, я использую PhantomData, чтобы компилятор не жаловался.

Вот код после изменений:

use std::marker::PhantomData;

struct MyProducer<'a, T: 'a + From<&'a u32>> {
    number: u32,
    _phantom: PhantomData<&'a T>,
}

impl<'a, T: 'a + From<&'a u32>> MyProducer<'a, T> {
    fn new() -> Self {
        Self {
            number: 0,
            _phantom: PhantomData::default(),
        }
    }
}

impl<'a, T: From<&'a u32>> Producer<'a, T> for MyProducer<'a, T> {
    fn make(&'a mut self) -> T {
        self.number += 1;
        T::from(&self.number)
    }
}

fn main() {
    let mut producer = MyProducer::<MyValue>::new();

    println!("made this: {:?}", producer.make());
    println!("made this: {:?}", producer.make());
}

Функция main теперь выглядит именно так, как мне хотелось бы, чтобы она выглядела. Но код не компилируется. Это ошибка:

error[E0499]: cannot borrow `producer` as mutable more than once at a time
  --> src/main.rs:50:33
   |
49 |     println!("made this: {:?}", producer.make());
   |                                 -------- first mutable borrow occurs here
50 |     println!("made this: {:?}", producer.make());
   |                                 ^^^^^^^^
   |                                 |
   |                                 second mutable borrow occurs here
   |                                 first borrow later used here

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

Если я вызываю функцию make только один раз, она компилируется и работает.

Я использую редакцию 2018, поэтому NLL активен.

Rust Playground: рабочая версия до изменения

Rust Playground: сломанная версия после изменения

1 Ответ

0 голосов
/ 09 мая 2019

Я уменьшил шум из кода, поэтому ниже приведена еще более короткая версия разбитого корпуса, которая демонстрирует ту же проблему: ( тест на игровой площадке )

use std::marker::PhantomData;

#[derive(Debug)]
struct MyValue<'a>(&'a u32);

impl<'a> From<&'a u32> for MyValue<'a> {
    fn from(value: &'a u32) -> Self {
        MyValue(value)
    }
}

struct MyProducer<'a, T>(u32, PhantomData<&'a T>);

impl<'a, T> MyProducer<'a, T>
where
    T: From<&'a u32>,
{
    fn new() -> Self {
        Self(0, PhantomData)
    }

    fn make(&'a mut self) -> T {
        self.0 += 1;
        T::from(&self.0)
    }
}

fn main() {
    let mut producer = MyProducer::<MyValue>::new();
    println!("made this: {:?}", producer.make());
    println!("made this: {:?}", producer.make());
}

Основная проблема здесь заключается в том, что время жизни изменяемого заимствования - это время жизни MyProducer, то есть время жизни экземпляра, называемого producer, совпадает с временем жизни изменяемого заимствования, взятого в его методе make. Поскольку экземпляр producer не выходит из области видимости (в противном случае MyValue не сможет содержать ссылку на значение, хранящееся в нем), поэтому изменяемый заем живет до конца main. объем. Первое правило заимствования заключается в том, что в любой момент времени в области может быть только один изменяемый заем заданного значения, следовательно, ошибка компилятора.

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

#[derive(Debug)]
struct MyValue<'a>(&'a u32);

impl<'a> From<&'a u32> for MyValue<'a> {
    fn from(value: &'a u32) -> Self {
        MyValue(value)
    }
}

struct MyProducer(u32);

impl MyProducer {
    fn new() -> Self {
        Self(0)
    }

    fn make<'a, T>(&'a mut self) -> T
    where
        T: From<&'a u32>,
    {
        self.0 += 1;
        T::from(&self.0)
    }
}

fn main() {
    let mut producer = MyProducer::new();
    println!("made this: {:?}", producer.make::<MyValue>());
    println!("made this: {:?}", producer.make::<MyValue>());
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...