Где находится 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();

Пара вещей, происходящих под поверхностью для вашего текущего кода:

  1. Ваш .lock() дает LockResult<MutexGuard<Vec>>
  2. Вы назвали unwrap() на LockResult и получить MutexGuard<Vec>
  3. Так как 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 явным путем сохранения его в переменную и разыменования его, когда он используется, как мой модифицированный код выше. Я не думаю, что есть официальная модель по этому поводу. Иногда это также избавит вас от создания временной переменной.

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