Почему я не могу хранить значение и ссылку на это значение в одной структуре?
у меня есть значение и я хочу сохранить это значение и ссылку на что-то внутри этого значения в моем собственном типе:
struct Thing {
count: u32,
}
struct Combined<'a>(Thing, &'a u32);
fn make_combined<'a>() -> Combined<'a> {
let thing = Thing { count: 42 };
Combined(thing, &thing.count)
}
иногда, у меня есть значение и я хочу сохранить и ссылку на это значение в той же структуре:
struct Combined<'a>(Thing, &'a Thing);
fn make_combined<'a>() -> Combined<'a> {
let thing = Thing::new();
Combined(thing, &thing)
}
иногда я даже не беру ссылку на значение, и я получаю та же ошибка:
struct Combined<'a>(Parent, Child<'a>);
fn make_combined<'a>() -> Combined<'a> {
let parent = Parent::new();
let child = parent.child();
Combined(parent, child)
}
в каждом из этих случаев я получаю ошибку, что одно из значений " делает не живут достаточно долго". Что эта ошибка означает?
2 ответа:
давайте посмотрим на простая реализация этой:
struct Parent { count: u32, } struct Child<'a> { parent: &'a Parent, } struct Combined<'a> { parent: Parent, child: Child<'a>, } impl<'a> Combined<'a> { fn new() -> Self { let p = Parent { count: 42 }; let c = Child { parent: &p }; Combined { parent: p, child: c } } } fn main() {}
это не удастся с немного очищенной ошибкой:
error: `p` does not live long enough --> src/main.rs:17:34 | 17 | let c = Child { parent: &p }; | ^ | note: reference must be valid for the lifetime 'a as defined on the block at 15:21... --> src/main.rs:15:22 | 15 | fn new() -> Self { | ^ note: ...but borrowed value is only valid for the block suffix following statement 0 at 16:37 --> src/main.rs:16:38 | 16 | let p = Parent { count: 42 }; | ^
чтобы полностью понять эту ошибку, вы должны думать о том, как значения представлены в памяти и что происходит, когда вы движение та ценность. Давайте аннотировать
Combined::new
С какой-то гипотетический адреса памяти, которые показывают, где находятся значения:let p = Parent { count: 42 }; // `p` lives at address 0x1000 and takes up 4 bytes // The value of `p` is 42 let c = Child { parent: &p }; // `c` lives at address 0x1010 and takes up 4 bytes // The value of `c` is 0x1000 Combined { parent: p, child: c } // The return value lives at address 0x2000 and takes up 8 bytes // `p` is moved to 0x2000 // `c` is ... ?
что должно произойти к
c
? Если значение было просто перемещено какp
был, потом он будет ссылаться на память, которая больше не гарантируется есть допустимое значение в нем. Любой другой фрагмент кода разрешается хранить значения по адресу 0x1000 памяти. Доступ к памяти, что это было целое число может привести к сбоям и / или ошибкам безопасности, и является одним из основные категории ошибок, которые предотвращает ржавчина.это именно та проблема, что жизни предотвратить. Жизнь - это ... немного метаданные, что позволяет компилятору знать, как долго значение будет действительным при его текущая ячейка памяти. Это важное отличие, так как это распространенная ошибка новичков ржавчины. Время жизни ржавчины не период времени, когда объект создан и когда он разрушен!
в качестве аналогии, подумайте об этом так: в течение жизни человека, они будут проживайте в разных местах, каждое из которых имеет свой адрес. Один Продолжительность жизни ржавчины беспокоит адрес вы в настоящее время проживают в, не о том, когда вы умрете в будущем (хотя умирая также изменяет ваш адрес). Каждый раз, когда вы двигаетесь, это актуально, потому что ваш адрес больше не действителен.
также важно отметить, что продолжительность жизни не изменить код; ваш код управляет жизнями, ваши жизни не управляют кодом. Этот содержательное высказывание: "жизни описательны, а не предписывающий."
давайтеCombined::new
С некоторыми номерами строк, которые мы будем использовать чтобы выделить время жизни:{ // 0 let p = Parent { count: 42 }; // 1 let c = Child { parent: &p }; // 2 // 3 Combined { parent: p, child: c } // 4 } // 5
The срок службы бетона на
p
от 1 до 4 включительно (что я буду представляю как[1,4]
). Конкретный срок службыc
и[2,4]
, и конкретный срок службы возвращаемого значения[4,5]
. Это возможно иметь конкретные жизни, которые начинаются с нуля - это было бы представление времени жизни параметра в a функция или что-то такое существовал за пределами квартала.обратите внимание, что время жизни и
[2,4]
, но это относится к значение со сроком жизни[1,4]
. Это нормально до тех пор, пока ссылающееся значение становится недействительным до того, как это сделает указанное значение. Этот проблема возникает, когда мы пытаемся вернутьc
из блока. Это "сверх-расширьте" продолжительность жизни за своей естественной длиной.это новое знание должно объяснить первые два примера. Третий один требует смотреть на реализацию
Parent::child
. Шансы это будет выглядеть примерно так:impl Parent { fn child(&self) -> Child { ... } }
использует lifetime elision чтобы не писать явную generic параметры времени жизни. Это эквивалентно:
impl Parent { fn child<'a>(&'a self) -> Child<'a> { ... } }
в обоих случаях метод говорит, что a
Child
структура будет возвращается, который был параметризован с конкретным сроком службыself
. Сказал по-другому:Child
экземпляр содержит ссылку кParent
который создал его, и поэтому не может жить дольше, чем этоParent
экземпляра.это также позволяет нам понять, что что-то действительно не так с нашим функция создания:
fn make_combined<'a>() -> Combined<'a> { ... }
хотя вы, скорее всего, увидите это написано в другой форме:
impl<'a> Combined<'a> { fn new() -> Combined<'a> { ... } }
в обоих случаях, никакой параметр продолжительности жизни будучи обеспеченным через аргумент. Это означает, что время жизни, что
Combined
будет быть параметризованный с ничем не ограничен - это может быть что угодно абонент хочет, чтобы это было. Это бессмысленно, потому что звонящий можно указать'static
срок службы и нет никакого способа, чтобы удовлетворить это состояние.как это исправить?
самое простое и наиболее рекомендуемое решение-не пытаться поставить эти элементы в одной структуре вместе. Делая это, ваш вложенность структуры будет имитировать время жизни вашего кода. Типы мест что собственные данные в структурируйте вместе, а затем предоставьте методы, которые позволяет получать ссылки или объекты, содержащие ссылки по мере необходимости.
существует особый случай, когда отслеживание жизни чрезмерно усердно: когда у вас есть что-то, помещенное в кучу. Это происходит при использовании
Box<T>
, например. В этом случае, структура, которая перемещается содержит указатель на кучу. Заостренное значение останется стабильно, но адрес самого указателя будет двигаться. На практике, это не дело в том, что вы всегда следуете указателю.The аренда обрешеткой или owning_ref crate are способы представления этого дела, но они требуется, чтобы базовый адрес никогда не двигаться. Это исключает мутацию векторы, которые могут вызвать перераспределение и перемещение в куче значений.
дополнительная информация
после переезда
p
в структуру, почему компилятор не может получить новая ссылка наp
и назначитьc
в struct?хотя теоретически это возможно сделать, это приведет к большой сложности и накладным расходам. Каждый раз, когда объект перемещается, компилятор должен будет вставить код, чтобы "исправить" ссылку. Это означало бы, что копирование структуры больше не является очень дешевой операцией, которая просто перемещает некоторые биты. Это может даже означать, что такой код является дорогим, в зависимости от того, как хорошим гипотетическим оптимизатором было бы:
let a = Object::new(); let b = a; let c = b;
вместо того, чтобы заставить это произойти для каждый двигайтесь, программист добирается до выбрать когда это произойдет путем создания методов, которые будут принимать соответствующие ссылки только при их вызове.
есть один конкретный случай, когда вы можете создать тип со ссылкой на себя. Вам нужно использовать что-то вроде
Option
сделать это в два шага хотя:#[derive(Debug)] struct WhatAboutThis<'a> { name: String, nickname: Option<&'a str>, } fn main() { let mut tricky = WhatAboutThis { name: "Annabelle".to_string(), nickname: None, }; tricky.nickname = Some(&tricky.name[..4]); println!("{:?}", tricky); }
это действительно работает, в некотором смысле, но созданное значение сильно ограничено-оно может никогда быть перемещены. Примечательно, что это означает, что он не может быть возвращен из функции или передан по значению ни к чему. Функция конструктора показывает ту же проблему со временем жизни, что и выше:
fn creator<'a>() -> WhatAboutThis<'a> { // ... }
немного другая проблема, которая вызывает очень похожие сообщения компилятора, - это зависимость времени жизни объекта, а не сохранение явной ссылки. Примером этого является ssh2 библиотека. При разработке чего-то большего, чем тестовый проект, заманчиво попытаться поставить
Session
иChannel
полученные из этого сеанса вместе друг с другом в структуру, скрывая детали реализации от пользователя. Однако, обратите внимание, чтоChannel
определение имеет'sess
время жизни в аннотации типа, в то время какSession
нет.это вызывает аналогичные ошибки компилятора, связанные со временем жизни.
один из способов решить это очень простым способом-объявить
Session
снаружи в вызывающем элементе, а затем для аннотирования ссылки внутри структуры с временем жизни, подобным ответу в это сообщение на форуме пользователя Rust говоря об одной и той же проблеме при инкапсуляции SFTP. Это не будет выглядеть элегантный и не всегда может применяться - потому что теперь у вас есть два объекта для работы, а не тот, который вы хотели!получается аренда обрешеткой или owning_ref crate из другого ответа решения этой проблемы тоже. Рассмотрим owning_ref, который имеет специальный объект именно для этой цели:
OwningHandle
. Чтобы избежать перемещения базового объекта, мы выделяем его в куче с помощьюBox
, что дает нам следующее возможное решение:use ssh2::{Channel, Error, Session}; use std::net::TcpStream; use owning_ref::OwningHandle; struct DeviceSSHConnection { tcp: TcpStream, channel: OwningHandle<Box<Session>, Box<Channel<'static>>>, } impl DeviceSSHConnection { fn new(targ: &str, c_user: &str, c_pass: &str) -> Self { use std::net::TcpStream; let mut session = Session::new().unwrap(); let mut tcp = TcpStream::connect(targ).unwrap(); session.handshake(&tcp).unwrap(); session.set_timeout(5000); session.userauth_password(c_user, c_pass).unwrap(); let mut sess = Box::new(session); let mut oref = OwningHandle::new_with_fn( sess, unsafe { |x| Box::new((*x).channel_session().unwrap()) }, ); oref.shell().unwrap(); let ret = DeviceSSHConnection { tcp: tcp, channel: oref, }; ret } }
результатом этого кода является то, что мы не можем использовать
Session
больше, но он хранится вместе сChannel
, который мы будем использовать. Потому чтоOwningHandle
разыменование объектов наBox
, который разыменовывает кChannel
, при хранении его в структуре, мы называем его таковым. Примечание: это только мое понимание. У меня есть подозрение, что это может быть не так, так как это, кажется, довольно близко к обсуждениеOwningHandle
необеспеченность.одна любопытная деталь здесь состоит в том, что
Session
логически имеет аналогичную связь сTcpStream
какChannel
доSession
, но его собственность не принимается, и вокруг этого нет аннотаций типа. Вместо этого, это до пользователя, чтобы заботиться об этом, как документация рукопожатие метод говорит:этот сеанс не берет на себя ответственность за предоставленный сокет, это рекомендуется для обеспечения что сокет сохраняет время жизни этого сеанс, чтобы убедиться, что связь выполняется правильно.
также настоятельно рекомендуется, чтобы предоставленный поток не использовался одновременно в других местах в течение этой сессии, как это может быть вмешаться в протокол.
С
TcpStream
использование, полностью зависит от программиста, чтобы обеспечить правильность кода. С помощьюOwningHandle
, внимание к тому, где " опасная магия" бывает рисуется с помощьюunsafe {}
блок.дальнейшее и более высокое обсуждение этого вопроса находится в этом поток форума пользователя Rust - который включает в себя другой пример и его решение с использованием арендного ящика, который не содержит небезопасных блоков.