Странный тип при сопоставлении ссылок с образцом

Я столкнулся с этим странным поведением при чтении этого сообщение, и основной вопрос этого поста — при сопоставлении (&k, &v) = &(&String, &String) , k и v будет получен тип String.

Чтобы понять, что происходит, я написал следующий тестовый код, и результат оказался для меня гораздо более шокирующим и запутанным:

Playground Link

fn main() {
    let x: &(&String, &String) = &(&String::new(), &String::new());
    let ref_to_x: &&(&String, &String) = &x;
    let ref_ref_to_x: &&&(&String, &String) = &&x;
    let ref_ref_ref_to_x: &&&&(&String, &String) = &&&x;
    
    // code snippet 1
    let (a, b) = x;                // type of a: &&String, type of b: &&String
    let (a, b) = ref_to_x;         // type of a: &&String, type of b: &&String
    let (a, b) = ref_ref_to_x;     // type of a: &&String, type of b: &&String
    let (a, b) = ref_ref_ref_to_x; // type of a: &&String, type of b: &&String

    // code snippet 2
    let &(a, b) = x;                // type of a: &String, type of b: &String
    let &(a, b) = ref_to_x;        // type of a: &&String, type of b: &&String
    let &(a, b) = ref_ref_to_x;    // type of a: &&String, type of b: &&String
    let &(a, b) = ref_ref_ref_to_x;// type of a: &&String, type of b: &&String

    // code snippet 3
    let (&a, &b) = x;               // type of a: String, type of b: String
    let (&a, &b) = ref_to_x;        // type of a: String, type of b: String
    let (&a, &b) = ref_ref_to_x;    // type of a: String, type of b: String
    let (&a, &b) = ref_ref_ref_to_x;// type of a: String, type of b: String
}

Аннотации типа a и b, которые я добавил в конец строки, обозначаются rust-analyzer.

Пожалуйста, ПРИМЕЧАНИЕ, code snippet 3не будет компилироваться из-за ошибки can not move out of xx because it's borrowrd/can not move out of xx which is behind a shared reference, но я думаю, что это не имеет значения (Возможно, я ошибаюсь, если это так, укажите мне, спасибо), потому что мы концентрируемся на типе a и b.

Мои вопросы:

  1. почему a и b всегда имеют один и тот же тип, даже если RHS имеют разные типы (x/ref_to_x/ref_ref_to_x..) во фрагменте кода 1/2/3?
  2. как происходит это сопоставление (был бы полезен пошаговый процесс сопоставления)?
  3. Как я могу получить точно такое же определение типа, как rust-analyzer/rustc, при написании кода?

Кстати, это относится к rfc 2005 match-эргономика? Я много гуглил и нашел, что многие люди упомянули об этом в своем ответе.

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

Ответы 2

Это называется «деструктуризация». Он обычно используется при сопоставлении с образцом, например, с if let Some(val) = option.

По сути, это:

let x: (&String, &String) = (&String::new(), &String::new());
let (&a, &b) = x; // a: String, b: String

Эквивалентно этому:

let (&a, &b) = (&String::new(), &String::new()); // a: String, b: String

Что эквивалентно этому:

let (a, b) = (String::new(), String::new());

Чтобы объяснить больше, я буду использовать более простой пример.

let value: String = String::new();
let &also_value = &value; // also_value: String

Деструктуризация работает как бы «упрощая» обе стороны. В этом случае будут разыменованы обе стороны, что приведет к

let also_value = value;

Это также работает через косвенность. Если мы добавим промежуточный шаг:

let value: String = String::new();
let ref_to_value: &String = &value;
let &also_value = ref_to_value; // also_value: String

Он может видеть через косвенность и теперь будет разыменовывать ref_to_value, чтобы снова получить String.

Эргономика сопоставления также может быть задействована, но только для разыменования всего кортежа.

Примечание: чтобы получить ссылку при назначении деструктурирования, вы можете вместо этого использовать ref:

let value: String = String::new();
let ref ref_to_value = value; // ref_to_value: &String

Обычно это используется только при сопоставлении с образцом, например:

if let Some(ref x) = option { ... }

Спасибо за Ваш ответ! Но кажется, что этот ответ не отвечает на мой вопрос, пример, который вы привели, довольно прост. Что меня озадачило, так это то, почему a и b имеют одинаковый тип во фрагменте кода 1/2/3, даже если RHS имеет разные типы (я обновлю свой вопрос, чтобы сделать его более ясным, извините за это)

Steve Lau 04.04.2022 06:38
Ответ принят как подходящий

Да. То, что вы видите, соответствует эргономике в действии, и их поведение может быть не таким, как вы ожидаете.

Эргономика соответствия работает с использованием режимы привязки. Доступны три режима вязки, и их можно использовать даже без спичечной эргономики:

  • Переехать. Это был режим привязки по умолчанию до того, как была введена эргономика сопоставления, и он всегда пытается переместить (или скопировать) значение.
  • ref. Это то, что вы получаете, когда применяете оператор ref к привязке (что удивительно), и он добавляет ссылку один. Например, в match e { ref r => ... }r есть &e.
  • ref mut. Аналогичен ref, но использует изменяемое заимствование (и указывается с помощью оператора ref mut).

Процесс работает следующим образом: компилятор обрабатывает шаблон снаружи внутрь. Процесс начинается с move в качестве режима привязки.

Каждый раз, когда компилятору необходимо сопоставить нереференсный образец (литерал, структуру, кортеж, срез) со ссылкой, он автоматически разыменовывает ссылку и обновляет режим привязки: когда ссылка & сопоставляется, мы получаем режим привязки ref, а для &mut ссылки мы получим ref если текущий режим привязки ref или иначе ref mut. Затем этот процесс повторяется до тех пор, пока у нас больше не будет ссылки.

Если мы сопоставляемся с эталонный образец (привязка, подстановочный знак, consts ссылочных типов или шаблоны &/&mut), режим привязки по умолчанию сбрасывается обратно на move.

Когда переменная привязывается, компилятор смотрит на текущий режим привязки: для move он будет соответствовать типу как есть. Для ref и ref mut будет добавлено & или &mut соответственно. Но только один.

Давайте следовать вашим примерам построчно.

let (a, b) = x;                // type of a: &&String, type of b: &&String

Мы сопоставляем нереференсный шаблон (шаблон кортежа) со ссылкой (типа &(&String, &String)). Поэтому мы разыменовываем ссылку и устанавливаем режим привязки ref.

Теперь у нас есть шаблон кортежа для сопоставления с кортежем типа (&String, &String) и режимом привязки ref. Мы сопоставляем a с &String: это эталонный шаблон (привязка), поэтому мы не меняем режим привязки. Однако у нас уже есть режим привязки ref. Мы сопоставляем тип &String, а ref означает, что мы добавляем ссылку, поэтому мы заканчиваем на &&String. Точно то же самое происходит с b.

let (a, b) = ref_to_x;         // type of a: &&String, type of b: &&String

Здесь, как и в предыдущем примере, мы сопоставляем нереференсный шаблон (шаблон кортежа) со ссылкой (&&(&String, &String)). Итак, мы разыменовываем и устанавливаем режим привязки ref. Но у нас все еще есть ссылка: &(&String, &String). Итак, мы снова разыменовываем. Режим привязки уже есть ref, поэтому трогать его не нужно. Мы заканчиваем сопоставлением (a, b) с (&String, &String). Это означает a = &String, b = &String. Но помните, что мы используем режим привязки ref, поэтому мы должны добавить ссылку. Добавляем ссылку единственный, даже несмотря на то, что мы сошлись против двух! В конце имеем a = &&String, b = &&String.

Остальные примеры в этом фрагменте кода работают так же.

let &(a, b) = ref_to_x;        // type of a: &&String, type of b: &&String

Здесь мы сначала сопоставляем шаблон & со ссылкой типа &&(&String, &String). Это удаляет обе ссылки, заставляя нас сопоставлять (a, b) с &(&String, &String). С этого момента мы продолжаем так же, как в первом примере.

Остальные примеры в этом фрагменте аналогичны.

let (&a, &b) = x;               // type of a: String, type of b: String

Это самое интересное. Помните, как мы говорили об эталонных и нереференсных шаблонах? В данном примере этот факт играет решающую роль.

Сначала мы сопоставляем шаблон кортежа с типом &(&String, &String). Мы разыменовываем кортеж и устанавливаем binding_mode = ref. Теперь мы сопоставляем кортеж: мы должны сопоставить &a и &b каждый с &String, с установленным режимом привязки ref.

Что произойдет, если мы сопоставим &a с &String? Ну, помните, что & — это эталонный шаблон, и при сопоставлении эталонных шаблонов мы полностью игнорировать режим привязки. Итак, мы сопоставляем &a с &String, сбрасывая режим привязки на move. Это удаляет ссылку с обеих сторон, оставляя нам a = String. То же самое для &b.

Следующие примеры в этом фрагменте кода такие же.

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