Rust: неправильное использование времени жизни

#rust #lifetime

#Ржавчина #срок службы

Вопрос:

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

 struct Space<'r> {
    v: amp;'r mut usize,
    z: usize,
}

impl<'r> Space<'r> {
    fn new(v: amp;'r mut usize) -> Self {
        Self { v, z: 0 }
    }
    
    fn add(amp;'r mut self) {
        self.z  = 1;
        *self.v = self.z
    }

    fn make_val(amp;'r mut self, v: u32) -> Val<'r> {
        Val::new(self, v)
    }
}

struct Val<'r> {
    space: amp;'r mut Space<'r>,
    v: u32,
}

impl<'r> Val<'r> {
    fn new(space: amp;'r mut Space<'r>, v: u32) -> Self {
        Self { space, v }
    }
}

impl<'r> Clone for Val<'r> {
    fn clone(amp;self) -> Self {
        self.space.add();
        
        Self { space: self.space, v: self.v }
    }
}

fn main() {
    let mut v = 0usize;
    let mut space = Space::new(amp;mut v);
    let mut val1 = space.make_val(1234);
    let mut val2 = val1.clone();
    
    println!("Res: {} - {}/{}", v, val1.v, val2.v);
}
  

(Игровая площадка)

Есть 2 ошибки компиляции, ошибка в строке 36 мне трудно исправить, но первая в строке 34 действительно странная, в ней говорится, что время жизни не может пережить, если вызов метода является внутренним, ничего не выходит наружу.

Не могли бы вы объяснить мне проблему с тезисами и как их решить, пожалуйста?

Комментарии:

1. clone Собираетесь ли вы в своем методе изменить исходное значение?

2. Вы все равно не добьетесь клонирования изменяемых ссылок. Вы можете попытаться поиграть с RefCell задержкой заимствования во время выполнения, но, тем не менее, подход Rust заключается в том, чтобы вообще избегать ссылок. У меня нет какой-либо конкретной причины в вашем примере не использовать собственные значения.

3. Я попробовал здесь: play.rust-lang.org /… , но та же проблема.

Ответ №1:

Просматривая части, которые выделяются для меня, вот потенциальные изменения, которые я бы внес.

Структуры

Просматривая две имеющиеся у вас структуры, первая выглядит разумной.

 struct Space<'r> {
    v: amp;'r mut usize,
    z: usize,
}
  

Однако Val структура не имеет для меня такого большого смысла. Если ссылка на Space имеет тот же срок 'r Space службы, что и сама, то это, по сути, эквивалентно переходу Val<'r> Space<'r> в. Хотя это и не является незаконным, я бы рекомендовал его изменить.

 // Just claim Space<'r> completely
struct Val<'r> {
    space: Space<'r>,
    v: u32,
}
  

Другим вариантом было бы добавить дополнительное время жизни Val<'r> , чтобы make_val его можно было вызывать более одного раза, не вызывая конфликтов времени жизни.

 // life of reference to space 'a <= 'r
struct Val<'a, 'r: 'a> {
    space: amp;'a mut Space<'r>,
    v: u32,
}

// Modify to allow for second lifetime to be used.
fn make_val<'a>(amp;'a mut self, v: u32) -> Val<'a, 'r> {
    Val { space: self, v }
}
  

Space::add

amp;'r mut self Для выполнения функции не требуется явное время жизни. Из двух ошибок, которые вы получаете при попытке запустить свой код, вся первая ошибка — это просто компилятор, сообщающий вам, почему это не сработает. Это чрезмерно ограничивает функцию и излишне запутывает компилятор, поэтому давайте удалим его.

 fn add(amp;mut self) {
    self.z  = 1;
    *self.v = self.z
}
  

Причина, по которой это не удалось решить, заключалась в том amp;mut self , что на самом деле требуется два времени жизни, которые должны быть равными. Это может иметь больше смысла, если мы визуализируем все задействованные жизни:

 // add(amp;mut self) tells the compiler to infer a lifetime for 'a that doesn't exceed 'b
fn add<'a, 'b: 'a>(self: amp;'a mut Space<'b>) {}

// add(amp;'r mut self) tells the compiler that the reference must live as long as the data 
fn add<'b>(self: amp;'b mut Space<'b>) {}
  

Клонирование

Клонирование чего-либо, содержащего изменяемую ссылку, не имеет смысла. Rust очень явно запрещает наличие нескольких изменяемых ссылок на что-либо в любой заданной точке памяти. Клонирование ссылки технически возможно, но для этого потребуется небезопасный код. Вы никогда не должны этого делать, но я все равно написал это ниже.

 impl<'r> Clone for Val<'r> {
    fn clone(amp;self) -> Val<'r> {
        unsafe {
            // Make self mutable so we can access self.space
            let my_self: amp;mut Self = amp;mut *(self as *const _ as *mut _);

            // Requires a mutable reference to self to run space::add
            my_self.space.add();
        
            Self {
                // Duplicate reference via unsafe code
                space: amp;mut *my_self.space,
                v: self.v
            }
        }
    }
}
  

Печать

Наконец, строка печати в main используется v , несмотря на изменяемую ссылку на нее, которая все еще используется. Это не требует пояснений, поэтому я не буду вдаваться в подробности.

Безопасная ржавчина

Безопасный способ решить эту проблему — использовать Rc<Refcell<T>> . Rc Позволяет использовать несколько принадлежащих ссылок в одном потоке и RefCell допускает внутреннюю изменчивость. RefCell может сделать это, проверяя, существуют ли какие-либо другие ссылки во время выполнения, а не во время компиляции. Вот версия playground, которая работает с одним RefCell файлом, но также можно использовать второй, чтобы лучше соответствовать вашему исходному коду.

Комментарии:

1. Спасибо за ваши объяснения и решения.

2. «Клонирование ссылки технически возможно, но» всегда будет insta-UB, AFAIK.