Почему у меня может быть две ссылки на mut, если я использую черту From?

Код выглядит следующим образом: RefInner имеет два mut ref Inner и Inner.a, используйте черту From, код не содержит ошибок, но cp() покажет cannot borrow inner as mutable more than once at a time. деталь ошибки:

error[E0499]: cannot borrow `inner` as mutable more than once at a time
  --> src/main.rs:40:12
   |
37 |       let b: RefInner = RefInner {
   |  _______________________-
38 | |         // b: a.geta().into(), //no error
39 | |         b: cp(inner.geta()),
   | |               ------------ first mutable borrow occurs here
40 | |         a: &mut inner,
   | |            ^^^^^^^^^^ second mutable borrow occurs here
41 | |     };
   | |_____- first borrow later used here

For more information about this error, try `rustc --explain E0499`.
warning: `hello` (bin "hello") generated 1 warning
error: could not compile `hello` due to previous error; 1 warning emitted
#[derive(Debug)]
pub struct Inner {
    pub a: u64,
    pub b: u64,
}

pub struct RefInner<'a> {
    pub a: &'a mut Inner,
    pub b: &'a mut ResponseContext,
}

impl Inner {
    fn geta(&mut self) -> &mut u64 {
        &mut self.a
    }
}

#[repr(C)]
#[derive(Debug)]
pub struct ResponseContext {
    seq_id: u8,
    _ignore: [u8; 7],
}

impl From<&mut u64> for &mut ResponseContext {
    fn from(value: &mut u64) -> Self {
        unsafe { std::mem::transmute(value) }
    }
}

fn cp(n: &mut u64) -> &mut ResponseContext {
    unsafe { std::mem::transmute(n) }
}

fn main() {
    let mut inner: Inner = Inner { a: 1, b: 2 };
    let b: RefInner = RefInner {
        // b: a.geta().into(), //no error
        b: cp(inner.geta()), //cannot borrow `inner` as mutable more than once at a time
        a: &mut inner,
    };
}

Кто-нибудь может объяснить разницу?

Обратите внимание, что этот код имеет UB — он уже запускает Miri, как в сообщении, поэтому вопрос гораздо менее значим, чем может показаться.

Cerberus 22.02.2023 05:33

да,_ignore: [u8; 9] должно быть _ignore: [u8; 7], но не влияет на вопрос

ViciOs 22.02.2023 07:32

Это все еще UB с [u8; 7] — проблема не только в несоответствии размеров, проблема в алиасинге изменяемых ссылок.

Cerberus 22.02.2023 08:20

Можете объяснить, что такое УБ? Я этого не вижу.

ViciOs 22.02.2023 12:07

Обе ссылки внутри RefInner указывают на одну и ту же память. Поскольку они изменяемы, согласно правилам Rust, это UB.

Cerberus 22.02.2023 13:20

Кодер (я) будет нести ответственность за это, так как используется небезопасный блок.

ViciOs 23.02.2023 04:03

Да, это то, на что я указываю - вы несете ответственность за то, чтобы не запускать этот код в производство, поскольку вы не знаете, во что он будет скомпилирован.

Cerberus 23.02.2023 04:23

Я знаю, что есть две ссылки, и ржавчина не позволяет этого, поэтому я задаю вопрос здесь. Этот код будет запущен в производство, и я думаю, что это нормально, даже если я не вижу ответа. Знать, что есть две ссылки на одну и ту же память, достаточно для написания кода C.

ViciOs 23.02.2023 04:40

Я имею в виду, что псевдоним изменяемой ссылки — это UB в Rust по определению. C его нет, да.

Cerberus 23.02.2023 04:46

Давайте продолжим обсуждение в чате.

ViciOs 23.02.2023 04:47
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
10
56
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ сводится к правилам исключения.

Когда у вас есть impl From<&mut A> for &mut B, этот заголовок реализации обесценивается до impl<'from, 'to> From<&'from mut A> for &'to mut B, то есть он позволяет преобразовывать любую ссылку (пусть даже короткую) в любую ссылку (пусть даже длинную). В общем, эти два времени жизни должны быть связаны, так как иначе тело метода не скомпилировалось бы, а здесь вы заставили проверку заимствования принять эту общую реализацию с помощью transmute.

Однако, когда у вас есть fn(&mut A) -> &mut B, это обесценивается более конкретным образом, а именно, до fn<'lt>(&'lt mut A) -> &'lt mut B, т. е. время жизни ввода и вывода эквивалентно, поэтому исходная изменяемая ссылка «заблокирована», пока существует ссылка вывода, предотвращая любое другое использование. .

Ответ принят как подходящий

С вашей реализацией черты здесь с ellision времени жизни, время жизни ввода и вывода не пересекаются.

то есть в вашем случае это impl<'a, 'b> From<&'a mut u64> for &'b mut ResponseContext.

С помощью transmute() вы говорите компилятору игнорировать его.

Что касается функции с одним входом и одним выходом, компилятор назначает одинаковое время жизни.

fn cp(n: &mut u64) -> &mut ResponseContext в основном fn cp<'a>(n: &'a mut64) -> &'a mut ResponseContext

Если вы сделаете impl<'a> From<&'a mut u64> for &'a mut ResponseContext, она будет вести себя как ваша функция cp()

Любой документ об этом? Какое 'b время жизни у реализации трейта? 'static?

ViciOs 22.02.2023 07:43

Наряду с 'a это «некоторая» жизнь, которая также может быть 'static, но не связана

vikram2784 22.02.2023 14:15
doc.rust-lang.org/nomicon/lifetime-elision.html
vikram2784 22.02.2023 16:56

Другие вопросы по теме