Вывод: поскольку *x.lock().unwrap()
выполняет неявное заимствование операнда x.lock().unwrap()
операнд рассматривается как контекст места. Но поскольку наш реальный операнд — это не выражение места, а выражение значения, он присваивается безымянной ячейке памяти (фактически скрытая привязка let
)!
Более подробное объяснение см. ниже.
Место выражения и выражение значения
Прежде чем мы углубимся, первые два важных термина. Выражения в Rust делятся на две основные категории: выражения места и выражения значения.
- Выражения места представляют значение, у которого есть дом (ячейка памяти). Например, если у вас есть
let x = 3;
, то x
— это выражение места. Исторически это называлось выражение lvalue.
- Выражения-значения представляют собой значение, не имеющее дома (мы можем использовать только значение, с ним не связано место в памяти). Например, если у вас есть
fn bar() -> i32
, то bar()
является выражением значения. Литералы вроде 3.14
или "hi"
также являются выражениями-значениями. Исторически они назывались выражениями rvalue.
Существует хорошее эмпирическое правило для проверки того, является ли что-то выражением места или значения: «имеет ли смысл писать это в левой части присваивания?». Если да (например, my_variable = ...;
), то это выражение места, если нет (например, 3 = ...;
), то это выражение значения.
Также существуют контексты мест и контексты значений. Это в основном «слоты», в которые могут быть помещены выражения. Есть только несколько контекстов места, которые (обычно см. ниже) требуют выражения места:
- Левая часть (составного) выражения присваивания (
⟨place context⟩ = ...;
, ⟨place context⟩ += ...;
)
- Операнд выражения заимствования (
&⟨place context⟩
и &mut ⟨place context⟩
)
- ... плюс еще несколько
Обратите внимание, что выражения места строго более «мощны». Их можно без проблем использовать в контексте значения, поскольку они также представляют значение.
(соответствующая глава справочника)
Временные жизни
Давайте создадим небольшой фиктивный пример, чтобы продемонстрировать, что делает Rust:
struct Foo(i32);
fn get_foo() -> Foo {
Foo(0)
}
let x: &Foo = &get_foo();
Это работает!
Мы знаем, что выражение get_foo()
является выражением-значением. И мы знаем, что операндом выражения заимствования является контекст места. Итак, почему это компилируется? Разве контексты размещения не нуждались в выражениях размещения?
Rust создает временные привязки let
! Из ссылки:
При использовании выражения значения в большинстве контекстов выражения места создается временная неименованная ячейка памяти, инициализированная этим значением, и вместо этого выражение вычисляется по этой ячейке [...].
Таким образом, приведенный выше код эквивалентен:
let _compiler_generated = get_foo();
let x: &Foo = &_compiler_generated;
Вот что заставляет ваш пример Mutex
работать: MutexLock
назначается временной безымянной ячейке памяти! Вот где он живет. Посмотрим:
&mut *x.lock().unwrap();
Часть x.lock().unwrap()
является выражением значения: она имеет тип MutexLock
и возвращается функцией (unwrap()
), как и get_foo()
выше. Тогда остается только один последний вопрос: является ли операнд оператора deref *
контекстом места? Я не упомянул это в списке конкурсов мест выше...
Неявные заимствования
Последняя часть головоломки — неявные заимствования. Из ссылки:
Некоторые выражения будут рассматривать выражение как выражение места, неявно заимствуя его.
К ним относится «операнд оператора разыменования (*
)»! И все операнды любого неявного заимствования являются контекстами места!
Итак, поскольку *x.lock().unwrap()
выполняет неявное заимствование, операнд x.lock().unwrap()
является контекстом места, но поскольку наш фактический операнд - это не место, а выражение значения, он назначается безымянной ячейке памяти!
Почему это не работает для deref_mut()
Есть важная деталь «временных жизней». Еще раз обратимся к цитате:
При использовании выражения значения в большинстве контекстов выражения места создается временная неименованная ячейка памяти, инициализированная этим значением, и вместо этого выражение вычисляется по этой ячейке [...].
В зависимости от ситуации Rust выбирает участки памяти с разным временем жизни! В приведенном выше примере &get_foo()
временное безымянное место в памяти имело время жизни окружающего блока. Это эквивалентно скрытой привязке let
, которую я показал выше.
Однако это "временное безымянное место в памяти" не всегда эквивалентно привязке let
! Давайте рассмотрим этот случай:
fn takes_foo_ref(_: &Foo) {}
takes_foo_ref(&get_foo());
Здесь значение Foo
существует только на время вызова takes_foo_ref
, а не дольше!
В общем, если ссылка на временное используется в качестве аргумента для вызова функции, временное существует только для этого вызова функции. Это также включает параметр &self
(и &mut self
). Таким образом, в get_foo().deref_mut()
объект Foo
также будет жить только в течение deref_mut()
. Но поскольку deref_mut()
возвращает ссылку на объект Foo
, мы получим ошибку "не живет достаточно долго".
Это, конечно же, относится и к x.lock().unwrap().deref_mut()
— вот почему мы получаем ошибку.
В случае оператора deref (*
) временное существование окружающего блока (эквивалентно привязке let
). Могу только предположить, что это особый случай в компиляторе: компилятор знает, что вызов deref()
или deref_mut()
всегда возвращает ссылку на получателя self
, поэтому не имеет смысла заимствовать временное только для вызова функции.
person
Lukas Kalbertodt
schedule
14.07.2018