Язык программирования Rust
Скачать 7.02 Mb.
|
Листинг 18-4. Использование шаблона для деструктуризации кортежа и создания трёх переменных одновременно Здесь мы сопоставляем кортеж с шаблоном. Rust сравнивает значение (1, 2, 3) с шаблоном (x, y, z) и видит, что значение соответствует шаблону, поэтому Rust связывает 1 с x , 2 с y и 3 с z . Вы можете думать об этом шаблоне кортежа как о вложении в него трёх отдельных шаблонов переменных. Если количество элементов в шаблоне не совпадает с количеством элементов в кортеже, то весь тип не будет совпадать и мы получим ошибку компилятора. Например, в листинге 18-5 показана попытка деструктурировать кортеж с тремя элементами в две переменные, что не будет работать. Листинг 18-5: Неправильное построение шаблона, переменные не соответствуют количеству элементов в кортеже Попытка скомпилировать этот код приводит к ошибке: Чтобы исправить ошибку, мы могли бы игнорировать одно или несколько значений в кортеже, используя _ или , как вы увидите в разделе “Игнорирование значений в Шаблоне” . Если шаблон содержит слишком много переменных в шаблоне, можно let (x, y, z) = ( 1 , 2 , 3 ); let (x, y) = ( 1 , 2 , 3 ); $ cargo run Compiling patterns v0.1.0 (file:///projects/patterns) error[E0308]: mismatched types --> src/main.rs:2:9 | 2 | let (x, y) = (1, 2, 3); | ^^^^^^ --------- this expression has type `({integer}, {integer}, {integer})` | | | expected a tuple with 3 elements, found one with 2 elements | = note: expected tuple `({integer}, {integer}, {integer})` found tuple `(_, _)` For more information about this error, try `rustc --explain E0308`. error: could not compile `patterns` due to previous error решить проблему, сделав типы совпадающими, удалив некоторые переменные таким образом, чтобы число переменных равнялось числу элементов в кортеже. Параметры функции Параметры функции также могут быть образцами. Код в листинге 18-6 объявляет функцию с именем foo , которая принимает один параметр с именем x типа i32 , к настоящему времени это должно выглядеть знакомым. Листинг 18-6: Сигнатура функции использует образцы в параметрах x это часть шаблона! Как и в случае с let , мы можем сопоставить кортеж в аргументах функции с образцом. Листинг 18-7 разделяет значения в кортеже при его передачи в функцию. Файл: src/main.rs Листинг 18-7: Функция с параметрами, которая разрушает кортеж Этот код печатает текущие координаты: (3, 5) . Значения &(3, 5) соответствуют образцу &(x, y) , поэтому x - это значение 3 , а y - это значение 5 Добавляя к вышесказанному, мы можем использовать шаблоны в списках параметров замыкания таким же образом, как и в списках параметров функции, потому что, как обсуждалось в главе 13, замыкания похожи на функции. На данный момент вы видели несколько способов использования шаблонов, но шаблоны работают не одинаково во всех местах, где их можно использовать. В некоторых местах шаблоны должны быть неопровержимыми; в других обстоятельствах они могут быть опровергнуты. Мы обсудим эти две концепции далее. fn foo (x: i32 ) { // code goes here } fn print_coordinates (&(x, y): &( i32 , i32 )) { println! ( "Current location: ({}, {})" , x, y); } fn main () { let point = ( 3 , 5 ); print_coordinates(&point); } Возможность опровержения: может ли шаблон не совпадать Шаблоны бывают двух форм: опровержимые и неопровержимые. Шаблоны, которые будут соответствовать любому возможному переданному значению, являются неопровержимыми (irrefutable). Примером может быть x в выражении let x = 5; , потому что x соответствует чему-либо и следовательно не может не совпадать. Шаблоны, которые могут не соответствовать некоторому возможному значению, являются опровержимыми (refutable). Примером может быть Some(x) в выражении if let Some(x) = a_value , потому что если значение в переменной a_value равно None , а не Some , то шаблон Some(x) не будет совпадать. Параметры функций, операторы let и for могут принимать только неопровержимые шаблоны, поскольку программа не может сделать ничего значимого, если значения не совпадают. А выражения if let и while let принимают опровержимые и неопровержимые шаблоны, но компилятор предостерегает от неопровержимых шаблонов, поскольку по определению они предназначены для обработки возможного сбоя: функциональность условного выражения заключается в его способности выполнять разный код в зависимости от успеха или неудачи. В общем случае, вам не нужно беспокоиться о разнице между опровержимыми (refutable) и неопровержимыми (irrefutable) образцами; тем не менее, вам необходимо ознакомиться с концепцией возможности опровержения, чтобы вы могли отреагировать на неё, увидев в сообщении об ошибке. В таких случаях вам потребуется изменить либо шаблон, либо конструкцию с которой вы используете шаблон в зависимости от предполагаемого поведения кода. Давайте посмотрим на пример того, что происходит, когда мы пытаемся использовать опровержимый (refutable) шаблон, где Rust требует неопровержимый шаблон и наоборот. В листинге 18-8 показан оператор let , но для образца мы указали Some(x) являющийся шаблоном, который можно опровергнуть. Как и следовало ожидать, этот код не будет компилироваться. Листинг 18-8: Попытка использовать опровержимый шаблон вместе с let Если some_option_value было бы значением None , то оно не соответствовало бы шаблону Some(x) , что означает, что шаблон является опровержимым. Тем не менее, оператор let может принимать только неопровержимый шаблон, потому что нет корректного кода, который может что-то сделать со значением None . Во время компиляции Rust будет жаловаться на то, что мы пытались использовать опровержимый шаблон для которого требуется неопровержимый шаблон: let Some (x) = some_option_value; Поскольку мы не покрыли (и не могли покрыть!) каждое допустимое значение с помощью образца Some(x) , то Rust выдаёт ошибку компиляции. Чтобы исправить проблему наличия опровержимого шаблона, там где нужен неопровержимый шаблон, можно изменить код использующий шаблон: вместо использования let , можно использовать if let . Затем, если шаблон не совпадает, выполнение кода внутри фигурных скобок будет пропущено, что даст возможность продолжить корректное выполнение. В листинге 18-9 показано, как исправить код из листинга 18-8. Листинг 18-9. Использование if let и блока с опровергнутыми шаблонами вместо let Код сделан! Этот код совершенно корректный, хотя это означает, что мы не можем использовать неопровержимый образец без получения ошибки. Если мы используем шаблон if let , который всегда будет совпадать, то для примера x показанного в листинге 18-10, компилятор выдаст предупреждение. Листинг 18-10. Попытка использовать неопровержимый шаблон с if let Rust жалуется, что не имеет смысла использовать, if let с неопровержимым образцом: $ cargo run Compiling patterns v0.1.0 (file:///projects/patterns) error[E0005]: refutable pattern in local binding: `None` not covered --> src/main.rs:3:9 | 3 | let Some(x) = some_option_value; | ^^^^^^^ pattern `None` not covered | = note: `let` bindings require an "irrefutable pattern", like a `struct` or an `enum` with only one variant = note: for more information, visit https://doc.rust-lang.org/book/ch18-02- refutability.html note: `Option = note: the matched value is of type `Option | 3 | let x = if let Some(x) = some_option_value { x } else { todo!() }; | ++++++++++ ++++++++++++++++++++++ For more information about this error, try `rustc --explain E0005`. error: could not compile `patterns` due to previous error if let Some (x) = some_option_value { println! ( "{}" , x); } if let x = 5 { println! ( "{}" , x); }; По этой причине совпадающие рукава должны использовать опровержимые образцы, за исключением последнего, который должен сопоставлять любые оставшиеся значения с неопровержимым образцом. Rust позволяет нам использовать неопровержимый шаблон в match только с одним рукавом, но этот синтаксис не особенно полезен и может быть заменён более простым оператором let Теперь, когда вы знаете, где использовать шаблоны и разницу между опровержимыми и неопровержимыми шаблонами, давайте рассмотрим весь синтаксис, который мы можем использовать для создания шаблонов. $ cargo run Compiling patterns v0.1.0 (file:///projects/patterns) warning: irrefutable `if let` pattern --> src/main.rs:2:8 | 2 | if let x = 5 { | ^^^^^^^^^ | = note: `#[warn(irrefutable_let_patterns)]` on by default = note: this pattern will always match, so the `if let` is useless = help: consider replacing the `if let` with a `let` warning: `patterns` (bin "patterns") generated 1 warning Finished dev [unoptimized + debuginfo] target(s) in 0.39s Running `target/debug/patterns` 5 Синтаксис шаблонов В этом разделе мы рассмотрим все допустимые выражения в шаблонах и обсудим, зачем и когда они могут пригодиться. Сопоставление с литералом Как мы уже видели в главе 6, можно сопоставлять с литералами напрямую. В следующем коде есть несколько примеров: Этот код печатает one , потому что значение в x равно 1. Данный синтаксис полезен, когда вы хотите, чтобы ваш код предпринял действие, если он получает конкретное значение. Сопоставление именованных переменных Именованные переменные - это неопровержимые (irrefutable) шаблоны, которые соответствуют любому значению и мы использовали их много раз в книге. Однако при использовании именованных переменных в выражениях match возникает сложность. Поскольку match начинает новую область видимости, то переменные, объявленные как часть шаблона внутри выражения match , будут затенять переменные с тем же именем вне конструкции match как и в случае со всеми переменными. В листинге 18-11 мы объявляем переменную с именем x со значением Some(5) и переменную y со значением 10 . Затем мы создаём выражение match для значения x . Посмотрите на шаблоны в ветках, println! в конце и попытайтесь выяснить, какой код будет напечатан прежде чем запускать его или читать дальше. Файл: src/main.rs let x = 1 ; match x { 1 => println! ( "one" ), 2 => println! ( "two" ), 3 => println! ( "three" ), _ => println! ( "anything" ), } Листинг 18-11: Выражение match с веткой, которая добавляет затенённую переменную y Давайте рассмотрим, что происходит, когда выполняется выражение match . Шаблон в первой ветке не соответствует определённому значению x , поэтому выполнение продолжается. Шаблон во второй ветке вводит новую переменную с именем y , которая будет соответствовать любому значению в Some . Поскольку мы находимся в новой области видимости внутри выражения match , это новая переменная y , а не y которую мы объявили в начале со значением 10. Эта новая привязка y будет соответствовать любому значению из Some , которое находится в x . Следовательно, эта новая y связывается с внутренним значением Some из переменной x . Этим значением является 5 , поэтому выражение для этой ветки выполняется и печатает Matched, y = 5 Если бы x было значением None вместо Some(5) , то шаблоны в первых двух ветках не совпали бы, поэтому значение соответствовало бы подчёркиванию. Мы не ввели переменную x в шаблоне ветки со знаком подчёркивания, поэтому x в выражении все ещё является внешней переменной x , которая не была затенена. В этом гипотетическом случае совпадение match выведет Default case, x = None Когда выражение match завершается, заканчивается его область видимости как и область действия внутренней переменной y . Последний println! печатает at the end: x = Some(5), y = 10 Чтобы создать выражение match , которое сравнивает значения внешних x и y , вместо введения затенённой переменной нужно использовать условие в сопоставлении образца. Мы поговорим про условие в сопоставлении шаблона позже в разделе “Дополнительные условия в сопоставлении образца” Группа шаблонов В выражениях match можно сравнивать сразу с несколькими шаблонами, используя синтаксис | , который является оператором паттерна or. Например, в следующем примере мы сопоставляем значение x с ветвями match, первая из которых содержит let x = Some ( 5 ); let y = 10 ; match x { Some ( 50 ) => println! ( "Got 50" ), Some (y) => println! ( "Matched, y = {y}" ), _ => println! ( "Default case, x = {:?}" , x), } println! ( "at the end: x = {:?}, y = {y}" , x); оператор or, так что если значение x совпадёт с любым из значений в этой ветви, то будет выполнен её код: Будет напечатано one or two Сопоставление диапазонов с помощью ..= Синтаксис ..= позволяет нам выполнять сравнение с диапазоном значений. В следующем коде, когда в шаблоне найдётся совпадение с любым из значений заданного диапазона, будет выполнена эта ветка: Если x равен 1, 2, 3, 4 или 5, то совпадение будет достигнуто в первой ветке. Этот синтаксис более удобен при указании нескольких значений для сравнения, чем использование оператора | для определения этой же идеи; если бы мы решили использовать | , нам пришлось бы написать 1 | 2 | 3 | 4 | 5 . Указание диапазона намного короче, особенно если мы хотим подобрать, скажем, любое число от 1 до 1 000! Компилятор проверяет, что диапазон не является пустым во время компиляции, и поскольку единственными типами, для которых Rust может определить, пуст диапазон или нет, являются char и числовые значения, диапазоны допускаются только с числовыми или char значениями. Вот пример использования диапазонов значений char : let x = 1 ; match x { 1 | 2 => println! ( "one or two" ), 3 => println! ( "three" ), _ => println! ( "anything" ), } let x = 5 ; match x { 1 ..= 5 => println! ( "one through five" ), _ => println! ( "something else" ), } let x = 'c' ; match x { 'a' ..= 'j' => println! ( "early ASCII letter" ), 'k' ..= 'z' => println! ( "late ASCII letter" ), _ => println! ( "something else" ), } Rust может сообщить, что 'c' находится в диапазоне первого шаблона и напечатать начальную букву ASCII Деструктуризация для получения значений Мы также можем использовать шаблоны для деструктуризации структур, перечислений и кортежей, чтобы использовать разные части этих значений. Давайте пройдёмся по каждому варианту. Деструктуризация структуры В листинге 18-12 показана структура Point с двумя полями x и y , которые мы можем разделить, используя шаблон с выражением let Файл: src/main.rs Листинг 18-12: Разбиение полей структуры в отдельные переменные Этот код создаёт переменные a и b , которые сопоставляются значениям полей x и y структуры p . Этот пример показывает, что имена переменных в шаблоне не обязательно должны совпадать с именами полей структуры. Однако обычно имена переменных сопоставляются с именами полей, чтобы было легче запомнить, какие переменные взяты из каких полей. Из-за этого, а также из-за того, что строчка let Point { x: x, y: y } = p; содержит много дублирования, в Rust ввели специальное сокращение для шаблонов, соответствующих полям структуры: вам нужно только указать имя поля структуры, и тогда переменные, созданные из шаблона, будут иметь те же имена. Код в листинге 18-13 аналогичен коду в Листинге 18-12, но в шаблоне let создаются переменные x и y , вместо a и b Файл: src/main.rs struct Point { x: i32 , y: i32 , } fn main () { let p = Point { x: 0 , y: 7 }; let Point { x: a, y: b } = p; assert_eq! ( 0 , a); assert_eq! ( 7 , b); } Листинг 18-13: Деструктуризация полей структуры с использованием сокращённой записи Этот код создаёт переменные x и y , которые соответствуют полям x и y из переменной p . В результате переменные x и y содержат значения из структуры p Вместо создания переменных для всех полей мы также можем деструктурировать с помощью литеральных значений являющихся частью структуры. Это позволяет проверить некоторые поля на определённые значения при создании переменных для деструктуризации других полей. В листинге 18-14 показано выражение match , которое разделяет значения Point на три случая: точки, которые лежат непосредственно на оси x (что верно, когда y = 0 ), на оси y ( x = 0 ) или ни то, ни другое. Файл: src/main.rs Листинг 18-14: Деструктуризация и сопоставление с литералами в одном шаблоне Первая ветка будет соответствовать любой точке, которая лежит на оси x , указанием того что поле y совпадает, если его значение соответствует литералу равному 0 Шаблон все ещё создаёт переменную x , которую мы можем использовать в коде для этой ветки. Точно так же вторая ветка соответствует любой точке на оси y , указанием того что поле x совпадает, если его значение равно 0 и создаёт переменную y для значения поля y Третья ветка не указывает никаких литералов, поэтому он соответствует любой другой точке Point и создаёт переменные для обоих полей x и y struct Point { x: i32 , y: i32 , } fn main () { let p = Point { x: 0 , y: 7 }; let Point { x, y } = p; assert_eq! ( 0 , x); assert_eq! ( 7 , y); } fn main () { let p = Point { x: 0 , y: 7 }; match p { Point { x, y: 0 } => println! ( "On the x axis at {}" , x), Point { x: 0 , y } => println! ( "On the y axis at {}" , y), Point { x, y } => println! ( "On neither axis: ({}, {})" , x, y), } } В этом примере значение p совпадает по второй ветке, так как x содержит значение 0, поэтому этот код будет печатать On the y axis at 7 Помните, что выражение match перестаёт проверять следующие ветви, как только оно находит первый совпадающий шаблон, поэтому, даже если Point { x: 0, y: 0} находится на оси x и оси y , этот код будет печатать только On the x axis at 0 |