#rust #trait-objects #mutable-reference
Вопрос:
Почему у меня может быть несколько изменяемых ссылок на статический тип в одной и той же области?
static mut CURSOR: Option<B> = None;
struct B {
pub field: u16,
}
impl B {
pub fn new(value: u16) -> B {
B { field: value }
}
}
struct A;
impl A {
pub fn get_b(amp;mut self) -> amp;'static mut B {
unsafe {
match CURSOR {
Some(ref mut cursor) => cursor,
None => {
CURSOR= Some(B::new(10));
self.get_b()
}
}
}
}
}
fn main() {
// first creation of A, get a mutable reference to b and change its field.
let mut a = A {};
let mut b = a.get_b();
b.field = 15;
println!("{}", b.field);
// second creation of A, a the mutable reference to b and change its field.
let mut a_1 = A {};
let mut b_1 = a_1.get_b();
b_1.field = 16;
println!("{}", b_1.field);
// Third creation of A, get a mutable reference to b and change its field.
let mut a_2 = A {};
let b_2 = a_2.get_b();
b_2.field = 17;
println!("{}", b_1.field);
// now I can change them all
b.field = 1;
b_1.field = 2;
b_2.field = 3;
}
Я в курсе правил заимствования
- одна или несколько ссылок (
amp;T
) на ресурс, - ровно одна изменяемая ссылка (
amp;mut T
).
В приведенном выше коде у меня есть структура A
с get_b()
методом возврата изменяемой ссылки B
. С помощью этой ссылки я могу изменять поля структуры B
.
Странно то, что в одной и той же области () может быть создано более одной изменяемой ссылки b, b_1, b_2
(), и я могу использовать их все для изменения B
.
Почему у меня может быть несколько изменяемых ссылок с 'static
показанным временем жизни main()
?
Моя попытка объяснить это поведение заключается в том, что я возвращаю изменяемую ссылку с 'static
продолжительностью жизни. Каждый раз, когда я звоню get_b()
, он возвращает одну и ту же изменяемую ссылку. И в конце концов, это всего лишь одна идентичная ссылка. Верна ли эта мысль? Почему я могу использовать все изменяемые ссылки, полученные по get_b()
отдельности?
Ответ №1:
Для этого есть только одна причина: вы солгали компилятору. Вы злоупотребляете unsafe
кодом и нарушили основной принцип Rust о изменяемом сглаживании. Вы заявляете, что вам известны правила заимствования, но затем вы изо всех сил стараетесь их нарушить!
unsafe
код дает вам небольшой набор дополнительных способностей, но взамен вы теперь несете ответственность за то, чтобы избегать всех возможных видов неопределенного поведения. Несколько изменяемых псевдонимов-это неопределенное поведение.
Тот факт, что в этом есть static
замешан, полностью ортогональен проблеме. Вы можете создать несколько изменяемых ссылок на что угодно (или ничего) с любым временем жизни, которое вас волнует:
fn foo() -> (amp;'static i32, amp;'static i32, amp;'static i32) {
let somewhere = 0x42 as *mut i32;
unsafe { (amp;*somewhere, amp;*somewhere, amp;*somewhere) }
}
В своем исходном коде вы указываете, что звонить get_b
безопасно любому человеку любое количество раз. Это неправда. Вся функция должна быть помечена как небезопасная, вместе с обширной документацией о том, что разрешено, а что нет, чтобы предотвратить запуск небезопасности. Затем в любом unsafe
блоке должны быть соответствующие комментарии, объясняющие, почему это конкретное использование не нарушает необходимые правила. Все это делает создание и использование unsafe
кода более утомительным, чем безопасный код , но по сравнению с C, где каждая строка кода концептуально unsafe
понятна, она все равно намного лучше.
Вы должны использовать unsafe
код только тогда, когда вы знаете лучше, чем компилятор. Для большинства людей в большинстве случаев существует очень мало причин для создания unsafe
кода.
Конкретное напоминание от разработчиков Firefox:
Комментарии:
1. Ладно, я, пожалуй, возьму его. Скажите мне, если я ошибаюсь, но повторяю: поскольку я использую небезопасный блок для возврата ссылки, компилятор rust не будет проверять, есть ли другие изменяемые при возврате одного нового.
2. @TimonPost поскольку вы используете
unsafe
, что по сути означает «Я знаю, что я делаю, поверьте мне», компилятор верит, что вы знаете, что делаете. Затем эти проверки отключаются на время действия блока.