#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.