ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 11.01.2024
Просмотров: 1147
Скачиваний: 5
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Здесь мы получаем предупреждение о том, что не используем переменную y
, но мы не получаем предупреждения о неиспользовании переменной
_x
Обратите внимание, что есть небольшая разница между использованием только
_
и использованием имени, начинающегося с подчёркивания. Синтаксис
_x по-прежнему привязывает значение к переменной, тогда как
_
не привязывает ничего. В листинге 18-
21 представлена ошибка, показывающая, в каком случае это различие имеет значение.
Листинг 18-21: Неиспользуемая переменная, начинающаяся с подчёркивания, по-прежнему привязывает
значение, что может привести к смене владельца значения
Мы получим сообщение об ошибке, поскольку значение s
по-прежнему будет перемещено в переменную
_s
, что не позволяет нам снова использовать s
. Однако использование только подчёркивания никогда не привязывает к себе значение. Листинг
18-22 будет компилироваться без каких-либо ошибок, потому что s
не перемещено в
_
Листинг 18-22. Использование подчёркивания не привязывает значение
Этот код работает нормально, потому что мы никогда не привязываем s
к чему либо;
оно не перемещается.
Игнорирование оставшихся частей значения с помощью ..
Со значениями, которые имеют много частей, можно использовать синтаксис
, чтобы использовать только некоторые части и игнорировать остальные, избегая необходимости перечислять подчёркивания для каждого игнорируемого значения.
Шаблон игнорирует любые части значения, которые мы явно не сопоставили в остальной частью шаблона. В листинге 18-23 мы имеем структуру
Point
, которая содержит координату в трёхмерном пространстве. В выражении match мы хотим работать только с координатой x
и игнорировать значения полей y
и z
let s =
Some
(
String
::from(
"Hello!"
)); if let
Some
(_s) = s { println!
(
"found a string"
);
} println!
(
"{:?}"
, s); let s =
Some
(
String
::from(
"Hello!"
)); if let
Some
(_) = s { println!
(
"found a string"
);
} println!
(
"{:?}"
, s);
Листинг 18-21: Игнорирование полей структуры
Point
кроме поля
x
с помощью
Мы перечисляем значение x
и затем просто включаем шаблон
. Это быстрее, чем перечислять y: _
и z: _
, особенно когда мы работаем со структурами, которые имеют много полей, в ситуациях, когда только одно или два поля представляют для нас интерес.
Синтаксис раскроется до необходимого количества значений. В листинге 18-24
показано, как использовать с кортежем.
Файл: src/main.rs
Листинг 18-24: Сопоставление только первого и последнего значений в кортеже и игнорирование всех
других значений
В этом коде первое и последнее значение соответствуют first и last
. Конструкция будет соответствовать и игнорировать всё, что находится между ними.
Однако использование должно быть однозначным. Если неясно, какие значения предназначены для сопоставления, а какие следует игнорировать, Rust выдаст ошибку. В
листинге 18-25 показан пример неоднозначного использования
, поэтому он не будет компилироваться.
Файл: src/main.rs struct
Point
{ x: i32
, y: i32
, z: i32
,
} let origin = Point { x:
0
, y:
0
, z:
0
}; match origin {
Point { x, .. } => println!
(
"x is {}"
, x),
} fn main
() { let numbers = (
2
,
4
,
8
,
16
,
32
); match numbers {
(first, .., last) => { println!
(
"Some numbers: {first}, {last}"
);
}
}
}
Листинг 18-25: Попытка использовать
неоднозначным способом
При компиляции примера, мы получаем эту ошибку:
Rust не может определить, сколько значений в кортеже нужно игнорировать, прежде чем сопоставить значение с second
, и сколько следующих значений проигнорировать после этого. Этот код может означать, что мы хотим игнорировать
2
, связать second с
4
, а затем игнорировать
8
,
16
и
32
; или что мы хотим игнорировать
2
и
4
, связать second с
8
, а затем игнорировать
16
и
32
; и так далее. Имя переменной second не означает ничего особенного для Rust, поэтому мы получаем ошибку компилятора, так как использование в двух местах как здесь, является неоднозначным.
Дополнительные условия оператора сопоставления (Match Guards)
Условие сопоставления (match guard) является дополнительным условием if
, указанным после шаблона в ветке match
, которое также должно быть выполнено, чтобы ветка была выбрана. Условия сопоставления полезны для выражения более сложных идей, чем позволяет только шаблон.
Условие может использовать переменные, созданные в шаблоне. В листинге 18-26
показан match
, в котором первая ветка имеет шаблон
Some(x)
, а также имеет условие сопоставления, if x % 2 == 0
(которое будет истинным, если число чётное).
fn main
() { let numbers = (
2
,
4
,
8
,
16
,
32
); match numbers {
(.., second, ..) => { println!
(
"Some numbers: {}"
, second)
},
}
}
$
cargo run
Compiling patterns v0.1.0 (file:///projects/patterns) error: `..` can only be used once per tuple pattern
-->
src/main.rs:5:22
|
5 | (.., second, ..) => {
| -- ^^ can only be used once per tuple pattern
| |
| previously used here error: could not compile `patterns` due to previous error
Листинг 18-26: Добавление условия сопоставления в шаблон
В этом примере будет напечатано
The number 4 is even
. Когда num сравнивается с шаблоном в первой ветке, он совпадает, потому что
Some(4)
соответствует
Some(x)
Затем условие сопоставления проверяет, равен ли 0 остаток от деления x
на 2 и если это так, то выбирается первая ветка.
Если бы num вместо этого было
Some(5)
, условие в сопоставлении первой ветки было бы ложным, потому что остаток от 5 делённый на 2, равен 1, что не равно 0. Rust тогда перешёл бы ко второй ветке, которое совпадает, потому что вторая ветка не имеет условия сопоставления и, следовательно, соответствует любому варианту
Some
Невозможно выразить условие if x % 2 == 0
внутри шаблона, поэтому условие в сопоставлении даёт нам возможность выразить эту логику. Недостатком этой дополнительной выразительности является то, что компилятор не пытается проверять полноту, когда задействованы выражения с условием в сопоставлении.
В листинге 18-11 мы упомянули, что можно использовать условия сопоставления для решения нашей проблемы затенения шаблона. Напомним, что внутри шаблона в выражении match была создана новая переменная, вместо использования внешней к match переменной. Эта новая переменная означала, что мы не могли выполнить сравнение с помощью значения внешней переменной. В листинге 18-27 показано, как мы можем использовать условие сопоставления для решения этой проблемы.
Файл: src/main.rs
Листинг 18-27. Использование условия сопоставления для проверки на равенство со значением внешней
переменной
let num =
Some
(
4
); match num {
Some
(x) if x %
2
==
0
=> println!
(
"The number {} is even"
, x),
Some
(x) => println!
(
"The number {} is odd"
, x),
None
=> (),
} fn main
() { let x =
Some
(
5
); let y =
10
; match x {
Some
(
50
) => println!
(
"Got 50"
),
Some
(n) if n == y => println!
(
"Matched, n = {n}"
),
_ => println!
(
"Default case, x = {:?}"
, x),
} println!
(
"at the end: x = {:?}, y = {y}"
, x);
}
Этот код теперь напечатает
Default case, x = Some(5)
. Шаблон во второй ветке не вводит новую переменную y
, которая будет затенять внешнюю y
, это означает, что теперь можно использовать внешнюю переменную y
в условии сопоставления. Вместо указания шаблона как
Some(y)
, который бы затенял бы внешнюю y
, мы указываем
Some(n)
. Это создаёт новую переменную n
, которая ничего не затеняет, так как переменной n
нет вне конструкции match
Условие сопоставления if n == y не является шаблоном и следовательно, не вводит новые переменные. Переменная y
и есть внешняя y
, а не новая затенённая y
, и теперь мы можем искать элемент, который будет иметь то же значение, что и внешняя y
, путём сравнения n
и y
Вы также можете использовать оператор или
|
в условии сопоставления, чтобы указать несколько шаблонов; условие сопоставления будет применяться ко всем шаблонам. В
листинге 18-28 показан приоритет комбинирования условия сопоставления с шаблоном,
который использует
|
. Важной частью этого примера является то, что условие сопоставления if y применяется к
4
,
5
, и к
6
, хотя это может выглядеть как будто if y
относится только к
6
1 ... 47 48 49 50 51 52 53 54 ... 62
Листинг 18-28: Комбинирование нескольких шаблонов с условием сопоставления
Условие сопоставления гласит, что ветка совпадает, только если значение x
равно
4
,
5
или
6
, и если y
равно true
. Когда этот код выполняется, шаблон первой ветки совпадает, потому что x
равно
4
, но условие сопоставления if y равно false, поэтому первая ветка не выбрана. Код переходит ко второй ветке, которая совпадает, и эта программа печатает no
. Причина в том, что условие if применяется ко всему шаблону
4 | 5 | 6
, а не только к последнему значению
6
. Другими словами, приоритет условия сопоставления по отношению к шаблону ведёт себя так:
а не так:
После запуска кода, старшинство в поведении становится очевидным: если условие сопоставления применялось бы только к конечному значению в списке, указанном с помощью оператора
|
, то ветка бы совпала и программа напечатала бы yes let x =
4
; let y = false
; match x {
4
|
5
|
6
if y => println!
(
"yes"
),
_ => println!
(
"no"
),
}
(4 | 5 | 6) if y => ...
4 | 5 | (6 if y) => ...
Связывание @
Оператор at (
@
) позволяет создать переменную, которая содержит значение,
одновременно с тем, как мы проверяем, соответствует ли это значение шаблону. В
листинге 18-29 показан пример, в котором мы хотим проверить, что перечисление
Message::Hello со значением поля id находится в диапазоне
3..=7
. Но мы также хотим привязать такое значение к переменной id_variable
, чтобы использовать его внутри кода данной ветки. Мы могли бы назвать эту переменную id
, так же как поле, но для этого примера мы будем использовать другое имя.
Листинг 18-29: Использование
@
для привязывания значения в шаблоне, с одновременной его проверкой
В этом примере будет напечатано
Found an id in range: 5
. Указывая id_variable @
перед диапазоном
3..=7
, мы захватываем любое значение, попадающее в диапазон,
одновременно проверяя, что это значение соответствует диапазону в шаблоне.
Во второй ветке, где у нас в шаблоне указан только диапазон, код этой ветки не имеет переменной, которая содержит фактическое значение поля id
. Значение поля id могло бы быть 10, 11 или 12, но код, соответствующий этому шаблону, не знает, чему оно равно. Код шаблона не может использовать значение из поля id
, потому что мы не сохранили значение id в переменной.
В последней ветке, где мы указали переменную без диапазона, у нас есть значение,
доступное для использования в коде ветки, в переменной с именем id
. Причина в том,
что мы использовали упрощённый синтаксис полей структуры. Но мы не применяли никакого сравнения со значением в поле id в этой ветке, как мы это делали в первых двух ветках: любое значение будет соответствовать этому шаблону.
Использование
@
позволяет проверять значение и сохранять его в переменной в пределах одного шаблона.
enum
Message
{
Hello { id: i32
},
} let msg = Message::Hello { id:
5
}; match msg {
Message::Hello { id: id_variable @
3
..=
7
,
} => println!
(
"Found an id in range: {}"
, id_variable),
Message::Hello { id:
10
..=
12
} => { println!
(
"Found an id in another range"
)
}
Message::Hello { id } => println!
(
"Found some other id: {}"
, id),
}
Итоги
Шаблоны Rust очень помогают различать разные виды данных. При использовании их в выражениях match
, Rust гарантирует, что ваши шаблоны охватывают все возможные значения, потому что иначе ваша программа не скомпилируется. Шаблоны в операторах let и параметрах функций делают такие конструкции более полезными, позволяя разбивать элементы на более мелкие части, одновременно присваивая их значения переменным. Мы можем создавать простые или сложные шаблоны в соответствии с нашими потребностями.
Далее, в предпоследней главе книги, мы рассмотрим некоторые продвинутые аспекты различных возможностей Rust.
Расширенные возможности
К настоящему времени вы изучили наиболее часто используемые части языка программирования Rust. Прежде чем мы сделаем ещё один проект в главе 20, мы рассмотрим несколько аспектов языка с которыми вы можете сталкиваться время от времени. Можете использовать эту главу в качестве справки, когда столкнётесь с неизвестными возможностями Rust. Возможности, которые вы научитесь использовать в этой главе, полезны в специальных ситуациях. Хотя возможно вы не часто их встретите,
мы хотим быть уверены, что у вас есть понимание всех возможностей, которые может предложить Rust.
В этой главе мы рассмотрим:
Небезопасный Rust: как отказаться от некоторых гарантий Rust и взять на себя ответственность за их ручное соблюдение
Продвинутые типажи: ассоциированные типы, параметры типа по умолчанию,
полностью квалифицированный синтаксис, супер-типажи и шаблон создания
(newtype) по отношению к типажам
Расширенные типы: больше о шаблоне newtype, псевдонимах типа, тип never и типы динамических размеров
Расширенные функции и замыкания: указатели функций и возврат замыканий
Макросы: способы определения кода, который определяет большую часть кода во время компиляции
Это набор возможностей Rust для всех! Давайте погрузимся в него!
Unsafe Rust
Во всех предыдущих главах этой книги мы обсуждали код на Rust, безопасность памяти в котором гарантируется во время компиляции. Однако внутри Rust скрывается другой язык - небезопасный Rust, который не обеспечивает безопасной работы с памятью. Этот язык называется unsafe Rust и работает также как и первый, но предоставляет вам дополнительные возможности.
Небезопасный Rust существует, потому что по своей природе статический анализ является консервативным. Когда компилятор пытается определить, поддерживает ли код некоторые гарантии или нет, то лучше отклонить некоторые действительные программы, которые корректны, чем принимать некоторые программы, которые ошибочны. Бывают случаи, когда ваш код может быть правильным, но Rust считает, что это не так. В этих случаях вы можете использовать небезопасный код, чтобы сообщить компилятору: «поверь мне, я знаю, что делаю». Недостатком является то, что вы используете его на свой страх и риск. Если вы используете небезопасный код неправильно, то могут появиться проблемы из-за небезопасной работы с памятью, такие как разыменование нулевого указателя.
Другая причина, по которой у Rust есть небезопасное альтер эго, заключается в том, что по существу аппаратное обеспечение компьютера небезопасно. Если Rust не позволял бы вам выполнять небезопасные операции, вы не могли бы выполнять определённые задачи. Rust должен позволить вам использовать системное, низкоуровневое программирование, такое как прямое взаимодействие с операционной системой, или даже написание вашей собственной операционной системы. Возможность написания низкоуровневого, системного кода является одной из целей языка. Давайте рассмотрим,
что и как можно делать с небезопасным Rust.
Небезопасные сверхспособности
Чтобы переключиться на небезопасный Rust, используйте ключевое слово unsafe и
начните новый блок, содержащий небезопасный код. Вы можете совершить пять действий в небезопасном Rust коде, которые называются небезопасными
сверхспособностями и которые вы не можете выполнить в безопасном Rust. Эти сверхспособности включают в себя следующие возможности:
Разыменование сырого указателя
Вызов небезопасной функции или небезопасного метода
Доступ или изменение изменяемой статической переменной
Реализация небезопасного типажа
Доступ к полям в union
Важно понимать, что unsafe не отключает проверку заимствования или любые другие проверки безопасности Rust: если вы используете ссылку в небезопасном коде, она всё
равно будет проверена. Единственное, что делает ключевое слово unsafe
- даёт вам