Разница между &mut и ref mut для трейт-объектов

Прежде всего, я не спрашиваю, в чем разница между &mut и ref mut как таковыми.

Спрашиваю, потому что подумал:

let ref mut a = MyStruct

такой же как

let a = &mut MyStruct

Рассмотрим возврат трейт-объекта из функции. Вы можете вернуть Box<Trait> или &Trait. Если вы хотите иметь изменяемый доступ к его методам, возможно ли вернуть &mut Trait?

Учитывая этот пример:

trait Hello {
    fn hello(&mut self);
}

struct English;
struct Spanish;

impl Hello for English {
    fn hello(&mut self) {
        println!("Hello!");
    }
}

impl Hello for Spanish {
    fn hello(&mut self) {
        println!("Hola!");
    }
}

Метод получает изменяемую ссылку для демонстрационных целей.

Это не будет компилироваться:

fn make_hello<'a>() -> &'a mut Hello {
    &mut English
}

ни это:

fn make_hello<'a>() -> &'a mut Hello {
    let b = &mut English;
    b
}

Но это будет компилироваться и работать:

fn make_hello<'a>() -> &'a mut Hello {
    let ref mut b = English;
    b
}

Моя теория

Этот пример будет работать из коробки с неизменяемыми ссылками (не обязательно присваивать его переменной, просто верните &English), но не с изменяемыми ссылками. Я думаю, это связано с правилом, что может быть только одна изменяемая ссылка или столько неизменяемых, сколько вы хотите.

В случае неизменяемых ссылок вы создаете объект и заимствуете его как возвращаемое выражение; его ссылка не умрет, потому что она заимствована.

В случае изменяемых ссылок, если вы пытаетесь создать объект и заимствовать его изменяемым образом в качестве возвращаемого выражения, у вас есть две изменяемые ссылки (созданный объект и его изменяемая ссылка). Поскольку у вас не может быть двух изменяемых ссылок на один и тот же объект, он не будет выполнять вторую, поэтому переменная не будет жить достаточно долго. Я думаю, что когда вы пишете let mut ref b = English и возвращаете b, вы перемещаете изменяемую ссылку, потому что она была захвачена шаблоном.

Все вышеизложенное — слабая попытка объяснить себе, почему это работает, но у меня нет основ, чтобы это доказать.

Почему это происходит?

Я также запостил этот вопрос на Reddit.


person Rodolfo    schedule 06.12.2017    source источник
comment
Я посмотрел на МИР и совершенно ноль понятия не имею, что происходит.   -  person ljedrz    schedule 06.12.2017
comment
@ljedrz разве это не должно произойти?   -  person Rodolfo    schedule 06.12.2017
comment
Я не знаю, хотя обычно вы это делаете не так; Меня интересует логика, стоящая за этим, поскольку промежуточное представление очень запутано.   -  person ljedrz    schedule 06.12.2017
comment
Я тоже очень заинтересован в этом. Я понимаю, что должен использовать Box‹Hello› (или в nightly: impl Hello). Это нормальный способ сделать это?   -  person Rodolfo    schedule 06.12.2017
comment
Да, это был бы обычный способ сделать это.   -  person ljedrz    schedule 06.12.2017
comment
Кто-то указал мне на Reddit, что это работает только для типов нулевого размера. Доказательство: play.rust-lang.org/   -  person Rodolfo    schedule 06.12.2017
comment
Нулевой размер @RodolfoCastilloMateluna неверен. Однако это значение должно быть постоянным. String включает распределение.   -  person Shepmaster    schedule 06.12.2017
comment
@Shepmaster Я пытался избежать использования постоянных значений, но понял идею.   -  person Rodolfo    schedule 06.12.2017


Ответы (1)


Это ошибка. Мой первоначальный анализ ниже полностью игнорировал тот факт, что он возвращал изменяемую ссылку. Информация о продвижении имеет смысл только в контексте неизменяемых значений.


Это допустимо из-за нюансов правил, регулирующих временные файлы. (выделено мной):

При использовании rvalue в большинстве контекстов lvalue вместо этого создается и используется временное безымянное lvalue, если оно не повышено до 'static.

Ссылка продолжается:

Повышение значения выражения rvalue до слота 'static происходит, когда выражение может быть записано в константу, заимствовано и разыменовано это заимствование там, где выражение было первоначально записано, без изменения поведения во время выполнения. То есть расширенное выражение может быть оценено во время компиляции, а результирующее значение не содержит внутренней изменчивости или деструкторов (эти свойства определяются на основе значения, где это возможно, например, &None всегда имеет тип &'static Option<_>, так как не содержит ничего запрещенного) .

Ваш третий случай можно переписать так, чтобы «доказать», что происходит продвижение 'static:

fn make_hello_3<'a>() -> &'a mut Hello {
    let ref mut b = English;
    let c: &'static mut Hello = b;
    c
}

Что касается того, почему ref mut разрешает это, а &mut нет, я думаю, что продвижение 'static осуществляется на основе максимальных усилий, а &mut просто не попадает под какие-либо проверки. Вероятно, вы могли бы найти или зарегистрировать проблему с описанием ситуации.

person Shepmaster    schedule 06.12.2017
comment
Спасибо за ссылку. Интересное поведение! - person Rodolfo; 06.12.2017
comment
@RodolfoCastilloMateluna К вашему сведению, я ошибся — это ошибка! - person Shepmaster; 07.12.2017
comment
@RodolfoCastilloMateluna да, я связал проблему в верхней части ответа. Честно говоря, я держу пари, что человек, который зарегистрировал проблему, действительно видел ваш оригинальный пост на Reddit! - person Shepmaster; 07.12.2017