Где находится MutexGuard, если я никогда не назначу его переменной?
Я не понимаю "где" MutexGuard
во внутреннем блоке кода есть. Мьютекс блокируется и разворачивается, давая MutexGuard
, Каким-то образом этот код удается разыменовать MutexGuard
а затем покорно заимствовать этот объект. Где сделал MutexGuard
идти? Кроме того, путаница, эта разыменование не может быть заменено deref_mut
, Зачем?
use std::sync::Mutex;
fn main() {
let x = Mutex::new(Vec::new());
{
let y: &mut Vec<_> = &mut *x.lock().unwrap();
y.push(3);
println!("{:?}, {:?}", x, y);
}
let z = &mut *x.lock().unwrap();
println!("{:?}, {:?}", x, z);
}
2 ответа
Резюме: потому что *x.lock().unwrap()
выполняет неявное заимствование операнда x.lock().unwrap()
, операнд обрабатывается как контекст места. Но так как наш фактический операнд является не выражением места, а выражением значения, он присваивается неназванной ячейке памяти (в основном скрытой let
связывание)!
Смотрите ниже для более подробного объяснения.
Поместите выражения и выражения значения
Прежде чем мы погрузимся, сначала два важных условия. Выражения в Rust подразделяются на две основные категории: выражения мест и выражения значений.
- Выражения места представляют значение, которое имеет дом (место в памяти). Например, если у вас есть
let x = 3;
затемx
это выражение места. Исторически это называлось lvalue выражением. - Выражения значения представляют значение, которое не имеет дома (мы можем использовать только значение, с ним не связано местоположение в памяти). Например, если у вас есть
fn bar() -> i32
затемbar()
это выражение значения. Литералы как3.14
или же"hi"
выражения значения тоже. Исторически они назывались выражениями.
Существует хорошее правило, чтобы проверить, является ли что-то выражением места или значения: "имеет ли смысл писать это слева от присваивания?". Если это так 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()
это выражение значения. И мы знаем, что операнд выражения заимствования является контекстом места. Так почему же это компилируется? Разве для контекстов места не нужны выражения мест?
Руст создает временные let
переплеты! Из ссылки:
При использовании выражения значения в большинстве контекстов выражения места создается временная безымянная область памяти, инициализированная для этого значения, и вместо этого выражение оценивается в этом месте [...].
Таким образом, приведенный выше код эквивалентен:
let _compiler_generated = get_foo();
let x: &Foo = &_compiler_generated;
Это то, что делает ваш Mutex
пример работы: MutexLock
назначается на временную безымянную ячейку памяти! Вот где он живет. Посмотрим:
&mut *x.lock().unwrap();
x.lock().unwrap()
часть является выражением значения: она имеет тип MutexLock
и возвращается функцией (unwrap()
) как get_foo()
выше. Тогда остался только один последний вопрос: является ли операнд дерефа *
оператор контекст места? Я не упомянул об этом в списке мест конкурсов выше...
Неявные заимствования
Последний кусок в загадке являются неявными заимствованиями. Из ссылки:
Некоторые выражения будут обрабатывать выражение как выражение места, неявно заимствуя его.
К ним относятся "операнд оператора разыменования (*
) "! И все операнды любого неявного заимствования являются контекстами места!"
Потому, что *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
приемник, поэтому не имеет смысла заимствовать временное только для вызова функции.
Вот мои мысли:
let y: &mut Vec<_> = &mut *x.lock().unwrap();
Пара вещей, происходящих под поверхностью для вашего текущего кода:
- Ваш
.lock()
даетLockResult<MutexGuard<Vec>>
- Вы назвали
unwrap()
наLockResult
и получитьMutexGuard<Vec>
- Так как
MutexGuard<T>
реализуетDerefMut
Интерфейс, Rust выполняет дереф-принуждение. Это разыменовывается*
оператор, и дает&mut Vec
,
В Rust я верю, что ты не звонишь deref_mut
по собственному желанию, компилятор сделает Deref
принуждение для вас.
Если вы хотите получить свой MutexGuard
Вы не должны разыменовывать это:
let mut y = x.lock().unwrap();
(*y).push(3);
println!("{:?}, {:?}", x, y);
//Output: Mutex { data: <locked> }, MutexGuard { lock: Mutex { data: <locked> } }
Из того, что я видел в Интернете, люди обычно делают MutexGuard
явным путем сохранения его в переменную и разыменования его, когда он используется, как мой модифицированный код выше. Я не думаю, что есть официальная модель по этому поводу. Иногда это также избавит вас от создания временной переменной.