ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 11.01.2024
Просмотров: 1125
Скачиваний: 5
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
означает, что операция не удалась, а
Err содержит информацию о том, как и почему операция не удалась.
Значения типа
Result
, как и значения любого типа, имеют определённые для них методы. Экземпляр
Result имеет expect метод
, который можно вызвать. Если этот экземпляр
Result является значением
Err
, expect вызовет сбой программы и отобразит сообщение, которое вы передали в качестве аргумента. Если метод read_line возвращает
Err
, это, скорее всего, результат ошибки базовой операционной системы.
Если экземпляр
Result является значением
Ok
, expect возьмёт возвращаемое значение, которое
Ok удерживает, и вернёт вам только это значение, чтобы вы могли его использовать. В данном случае это значение представляет собой количество байтов,
введённых пользователем.
Если не вызвать expect
, программа скомпилируется, но будет получено предупреждение:
Rust предупреждает о не использовании значения
Result
, возвращаемого из read_line
,
показывая, что программа не учла возможность возникновения ошибки.
Правильный способ убрать предупреждение - это написать обработку ошибок, но в нашем случае мы просто хотим аварийно завершить программу при возникновении проблемы, поэтому используем expect
. О способах восстановления после ошибок вы узнаете в главе 9
Напечатать значений с помощью заполнителей println!
Кроме закрывающей фигурной скобки, в коде на данный момент есть ещё только одна строка для обсуждения:
Эта строка печатает строку, которая теперь содержит ввод пользователя. Набор фигурных скобок
{}
является заполнителем: думайте о
{}
как о маленьких крабовых
$
cargo build
Compiling guessing_game v0.1.0 (file:///projects/guessing_game) warning: unused `Result` that must be used
-->
src/main.rs:10:5
|
10 | io::stdin().read_line(&mut guess);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_must_use)]` on by default
= note: this `Result` may be an `Err` variant, which should be handled warning: `guessing_game` (bin "guessing_game") generated 1 warning
Finished dev [unoptimized + debuginfo] target(s) in 0.59s println!
(
"You guessed: {guess}"
);
Err содержит информацию о том, как и почему операция не удалась.
Значения типа
Result
, как и значения любого типа, имеют определённые для них методы. Экземпляр
Result имеет expect метод
, который можно вызвать. Если этот экземпляр
Result является значением
Err
, expect вызовет сбой программы и отобразит сообщение, которое вы передали в качестве аргумента. Если метод read_line возвращает
Err
, это, скорее всего, результат ошибки базовой операционной системы.
Если экземпляр
Result является значением
Ok
, expect возьмёт возвращаемое значение, которое
Ok удерживает, и вернёт вам только это значение, чтобы вы могли его использовать. В данном случае это значение представляет собой количество байтов,
введённых пользователем.
Если не вызвать expect
, программа скомпилируется, но будет получено предупреждение:
Rust предупреждает о не использовании значения
Result
, возвращаемого из read_line
,
показывая, что программа не учла возможность возникновения ошибки.
Правильный способ убрать предупреждение - это написать обработку ошибок, но в нашем случае мы просто хотим аварийно завершить программу при возникновении проблемы, поэтому используем expect
. О способах восстановления после ошибок вы узнаете в главе 9
Напечатать значений с помощью заполнителей println!
Кроме закрывающей фигурной скобки, в коде на данный момент есть ещё только одна строка для обсуждения:
Эта строка печатает строку, которая теперь содержит ввод пользователя. Набор фигурных скобок
{}
является заполнителем: думайте о
{}
как о маленьких крабовых
$
cargo build
Compiling guessing_game v0.1.0 (file:///projects/guessing_game) warning: unused `Result` that must be used
-->
src/main.rs:10:5
|
10 | io::stdin().read_line(&mut guess);
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_must_use)]` on by default
= note: this `Result` may be an `Err` variant, which should be handled warning: `guessing_game` (bin "guessing_game") generated 1 warning
Finished dev [unoptimized + debuginfo] target(s) in 0.59s println!
(
"You guessed: {guess}"
);
клешнях, удерживающих значение на месте. С помощью фигурных скобок можно вывести более одного значения: первый набор фигурных скобок содержит первое значение, указанное после форматирующей строки, второй набор - второе значение и так далее. Печать нескольких значений за один вызов println!
будет выглядеть следующим образом:
Этот код напечатает x = 5 and y = 10
Тестирование первой части
Давайте протестирует первую часть игры. Запустите её используя cargo run
:
На данном этапе первая часть игры завершена: мы получаем ввод с клавиатуры и затем печатаем его.
Генерация секретного числа
Далее нам нужно сгенерировать секретное число, которое пользователь попытается угадать. Секретное число должно быть каждый раз разным, чтобы в игру можно было играть несколько раз. Мы будем использовать случайное число в диапазоне от 1 до 100,
чтобы игра не была слишком сложной. Rust пока не включает функциональность случайных чисел в свою стандартную библиотеку. Однако команда Rust предоставляет rand crate с подобной функциональностью.
Использование пакета для получения дополнительной
функциональности
Запомните, что крейт — это набор файлов с исходным кодом Rust. Проект, который мы создавали, представляет собой двоичный крейт, являющийся исполняемым файлом. let x =
5
; let y =
10
; println!
(
"x = {} and y = {}"
, x, y);
$
cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 6.44s
Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
6
You guessed: 6
будет выглядеть следующим образом:
Этот код напечатает x = 5 and y = 10
Тестирование первой части
Давайте протестирует первую часть игры. Запустите её используя cargo run
:
На данном этапе первая часть игры завершена: мы получаем ввод с клавиатуры и затем печатаем его.
Генерация секретного числа
Далее нам нужно сгенерировать секретное число, которое пользователь попытается угадать. Секретное число должно быть каждый раз разным, чтобы в игру можно было играть несколько раз. Мы будем использовать случайное число в диапазоне от 1 до 100,
чтобы игра не была слишком сложной. Rust пока не включает функциональность случайных чисел в свою стандартную библиотеку. Однако команда Rust предоставляет rand crate с подобной функциональностью.
Использование пакета для получения дополнительной
функциональности
Запомните, что крейт — это набор файлов с исходным кодом Rust. Проект, который мы создавали, представляет собой двоичный крейт, являющийся исполняемым файлом. let x =
5
; let y =
10
; println!
(
"x = {} and y = {}"
, x, y);
$
cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 6.44s
Running `target/debug/guessing_game`
Guess the number!
Please input your guess.
6
You guessed: 6
rand крейт — это библиотечный крейт, содержащий код, предназначенный для использования в других программах, и не может быть выполнен сам по себе.
Координация работы внешних пакетов является тем местом, где Cargo действительно блистает. Чтобы начать писать код, использующий rand
, необходимо изменить файл
Cargo.toml, включив в него в качестве зависимости пакет rand
. Итак, откройте этот файл и добавьте следующую строку внизу под заголовком секции
[dependencies]
, созданным для вас Cargo. Обязательно укажите rand в точности как здесь, с таким же номером версии, иначе примеры кода из этого урока могут не заработать.
Имя файла: Cargo.toml
В файле Cargo.toml всё, что следует за заголовком, является частью этой секции, которая продолжается до тех пор, пока не начнётся следующая. В
[dependencies]
вы сообщаете
Cargo, от каких внешних крейтов зависит ваш проект и какие версии этих крейтов вам нужны. В этом случае мы указываем крейт rand со спецификатором семантической версии
0.8.3
. Cargo понимает семантическое версионирование
(иногда называемое
SemVer), которое является стандартом для описания версий. Число
0.8.3
на самом деле является сокращением от
^0.8.3
, что означает любую версию не ниже
0.8.3
, но ниже
0.9.0
Cargo рассчитывает, что эти версии имеют общедоступное API, совместимое с версией
0.8.3
, и вы получите последние версии исправлений, которые по-прежнему будут компилироваться с кодом из этой главы. Не гарантируется, что версия
0.9.0
или выше будет иметь тот же API, что и в следующих примерах.
Теперь, ничего не меняя в коде, давайте создадим проект, как показано в Листинге 2-2.
Листинг 2-2: Результат выполнения
cargo build
после добавления крейта rand в качестве зависимости
rand =
"0.8.3"
$
cargo build
Updating crates.io index
Downloaded rand v0.8.3
Downloaded libc v0.2.86
Downloaded getrandom v0.2.2
Downloaded cfg-if v1.0.0
Downloaded ppv-lite86 v0.2.10
Downloaded rand_chacha v0.3.0
Downloaded rand_core v0.6.2
Compiling rand_core v0.6.2
Compiling libc v0.2.86
Compiling getrandom v0.2.2
Compiling cfg-if v1.0.0
Compiling ppv-lite86 v0.2.10
Compiling rand_chacha v0.3.0
Compiling rand v0.8.3
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 2.53s
Координация работы внешних пакетов является тем местом, где Cargo действительно блистает. Чтобы начать писать код, использующий rand
, необходимо изменить файл
Cargo.toml, включив в него в качестве зависимости пакет rand
. Итак, откройте этот файл и добавьте следующую строку внизу под заголовком секции
[dependencies]
, созданным для вас Cargo. Обязательно укажите rand в точности как здесь, с таким же номером версии, иначе примеры кода из этого урока могут не заработать.
Имя файла: Cargo.toml
В файле Cargo.toml всё, что следует за заголовком, является частью этой секции, которая продолжается до тех пор, пока не начнётся следующая. В
[dependencies]
вы сообщаете
Cargo, от каких внешних крейтов зависит ваш проект и какие версии этих крейтов вам нужны. В этом случае мы указываем крейт rand со спецификатором семантической версии
0.8.3
. Cargo понимает семантическое версионирование
(иногда называемое
SemVer), которое является стандартом для описания версий. Число
0.8.3
на самом деле является сокращением от
^0.8.3
, что означает любую версию не ниже
0.8.3
, но ниже
0.9.0
Cargo рассчитывает, что эти версии имеют общедоступное API, совместимое с версией
0.8.3
, и вы получите последние версии исправлений, которые по-прежнему будут компилироваться с кодом из этой главы. Не гарантируется, что версия
0.9.0
или выше будет иметь тот же API, что и в следующих примерах.
Теперь, ничего не меняя в коде, давайте создадим проект, как показано в Листинге 2-2.
Листинг 2-2: Результат выполнения
cargo build
после добавления крейта rand в качестве зависимости
rand =
"0.8.3"
$
cargo build
Updating crates.io index
Downloaded rand v0.8.3
Downloaded libc v0.2.86
Downloaded getrandom v0.2.2
Downloaded cfg-if v1.0.0
Downloaded ppv-lite86 v0.2.10
Downloaded rand_chacha v0.3.0
Downloaded rand_core v0.6.2
Compiling rand_core v0.6.2
Compiling libc v0.2.86
Compiling getrandom v0.2.2
Compiling cfg-if v1.0.0
Compiling ppv-lite86 v0.2.10
Compiling rand_chacha v0.3.0
Compiling rand v0.8.3
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 2.53s
Вы можете увидеть другие номера версий (но все они будут совместимы с кодом,
благодаря SemVer!), другие строки (в зависимости от операционной системы), а также строки могут быть расположены в другом порядке.
Когда мы включаем внешнюю зависимость, Cargo берет последние версии всего, что нужно этой зависимости, из реестра (registry), который является копией данных с
Crates.io
. Crates.io - это место, где участники экосистемы Rust размещают свои проекты
Rust с открытым исходным кодом для использования другими.
После обновления реестра Cargo проверяет раздел
[dependencies]
и загружает все указанные в списке пакеты, которые ещё не были загружены. В нашем случае, хотя мы указали только rand в качестве зависимости, Cargo также захватил другие пакеты, от которых зависит работа rand
. После загрузки пакетов Rust компилирует их, а затем компилирует проект с имеющимися зависимостями.
Если вы немедленно снова запустите cargo build без внесения каких-либо изменений,
вы не получите никакого вывода, кроме строки
Finished
. Cargo знает, что он уже выгрузил и скомпилировал зависимости, и вы ничего не изменили в файле Cargo.toml.
Cargo также знает, что вы ничего не меняли в своём коде, поэтому он также не станет перекомпилировать его. Ввиду отсутствия задач он просто выходит.
Если открыть файл src/main.rs, внести незначительные изменения, а затем сохранить его и снова произвести сборку, то вы увидите только две строки вывода:
Эти строки показывают, что Cargo обновляет сборку только на основании вашего крошечного изменения в файле src/main.rs. Поскольку зависимости не изменились, Cargo знает, что может повторно использовать ранее загруженные и скомпилированные зависимости.
1 2 3 4 5 6 7 8 9 ... 62
Обеспечение воспроизводимых сборок с помощью файла Cargo.lock
В Cargo есть механизм, обеспечивающий возможность пересобрать все тот же артефакт каждый раз, когда вы или кто-либо другой собирает ваш код. Пока вы не укажете обратное, Cargo будет использовать только те версии зависимостей, которые были заданы ранее. Например, допустим, что на следующей неделе выходит версия 0.8.4
пакета rand
, и эта версия содержит важное исправление ошибки, но также содержит регрессию, которая может сломать ваш код. Чтобы справиться с этим, Rust создаёт файл
Cargo.lock при первом запуске cargo build
, поэтому теперь он есть в каталоге
guessing_game.
Когда вы создаёте проект в первый раз, Cargo определяет все версии зависимостей,
которые соответствуют критериям, а затем записывает их в файл Cargo.lock. Когда вы
$
cargo build
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 2.53 secs
будете собирать свой проект в будущем, Cargo увидит, что файл Cargo.lock существует, и будет использовать указанные там версии, а не выполнять всю работу по выяснению версий заново. Это позволяет автоматически создавать воспроизводимую сборку.
Другими словами, ваш проект останется на
0.8.3
до тех пор, пока вы явно не обновите его благодаря файлу Cargo.lock. Поскольку файл Cargo.lock важен для воспроизводимых сборок, он часто хранится в системе управления версиями вместе с остальным кодом проекта.
Обновление пакета для получения новой версии
Если вы захотите обновить пакет, Cargo предоставляет команду update
, которая игнорирует файл Cargo.lock и определяет последние версии, соответствующие вашим спецификациям из файла Cargo.toml. После этого Cargo запишет эти версии в файл
Cargo.lock. Иначе, по умолчанию, Cargo будет искать только версии больше
0.8.3
, но при этом меньше
0.9.0
. Если пакет rand имеет две новые версии
0.8.4
и
0.9.0
, то при запуске cargo update вы увидите следующее:
Cargo игнорирует релиз
0.9.0
. В этот момент также появится изменение в файле
Cargo.lock, указывающее на то, что версия rand
, которая теперь используется, равна
0.8.4
. Чтобы использовать rand версии
0.9.0
или любой другой версии из серии
0.9.x
, необходимо обновить файл Cargo.toml следующим образом:
В следующий раз, когда вы запустите Cargo cargo build
, Cargo обновит реестр доступных крейтов и пересмотрит ваши требования к rand в соответствии с новой версией, которую вы указали.
Ещё многое возможно сказать о
Cargo и его экосистеме
, которые мы обсудим в Главе 14,
а пока это всё, что вам нужно знать. Cargo упрощает повторное использование библиотек, поэтому Rustaceans могут создавать проекты меньшего размера, собранные из нескольких пакетов.
Генерация случайного числа
Давайте начнём использовать rand чтобы сгенерировать число для угадывания.
Следующим шагом будет обновление src/main.rs, как показано в Листинге 2-3.
Имя файла: src/main.rs
$
cargo update
Updating crates.io index
Updating rand v0.8.3 -> v0.8.4
[dependencies]
rand =
"0.9.0"
Другими словами, ваш проект останется на
0.8.3
до тех пор, пока вы явно не обновите его благодаря файлу Cargo.lock. Поскольку файл Cargo.lock важен для воспроизводимых сборок, он часто хранится в системе управления версиями вместе с остальным кодом проекта.
Обновление пакета для получения новой версии
Если вы захотите обновить пакет, Cargo предоставляет команду update
, которая игнорирует файл Cargo.lock и определяет последние версии, соответствующие вашим спецификациям из файла Cargo.toml. После этого Cargo запишет эти версии в файл
Cargo.lock. Иначе, по умолчанию, Cargo будет искать только версии больше
0.8.3
, но при этом меньше
0.9.0
. Если пакет rand имеет две новые версии
0.8.4
и
0.9.0
, то при запуске cargo update вы увидите следующее:
Cargo игнорирует релиз
0.9.0
. В этот момент также появится изменение в файле
Cargo.lock, указывающее на то, что версия rand
, которая теперь используется, равна
0.8.4
. Чтобы использовать rand версии
0.9.0
или любой другой версии из серии
0.9.x
, необходимо обновить файл Cargo.toml следующим образом:
В следующий раз, когда вы запустите Cargo cargo build
, Cargo обновит реестр доступных крейтов и пересмотрит ваши требования к rand в соответствии с новой версией, которую вы указали.
Ещё многое возможно сказать о
Cargo и его экосистеме
, которые мы обсудим в Главе 14,
а пока это всё, что вам нужно знать. Cargo упрощает повторное использование библиотек, поэтому Rustaceans могут создавать проекты меньшего размера, собранные из нескольких пакетов.
Генерация случайного числа
Давайте начнём использовать rand чтобы сгенерировать число для угадывания.
Следующим шагом будет обновление src/main.rs, как показано в Листинге 2-3.
Имя файла: src/main.rs
$
cargo update
Updating crates.io index
Updating rand v0.8.3 -> v0.8.4
[dependencies]
rand =
"0.9.0"
Листинг 2-3: Добавление кода для генерации случайного числа
Сначала мы добавляем строку use rand::Rng
. Типаж
Rng определяет методы,
реализующие генераторы случайных чисел, и этот типаж должен быть в области видимости, чтобы можно было использовать эти методы. В главе 10 мы подробно рассмотрим типажи.
Затем мы добавляем две строки посередине. В первой строке мы вызываем функцию rand::thread_rng
, дающую нам генератор случайных чисел, который мы собираемся использовать: тот самый, который является локальным для текущего потока выполнения и запускается операционной системой. Затем мы вызываем метод gen_range генератора случайных чисел. Этот метод определяется
Rng
, который мы включили в область видимости с помощью оператора use rand::Rng
. Метод gen_range принимает в качестве аргумента выражение диапазона и генерирует случайное число в этом диапазоне. Тип используемого выражения диапазона принимает форму start..=end и
включает нижнюю и верхнюю границы, поэтому, чтобы запросить число от 1 до 100, нам нужно указать
1..=100
Примечание: Не просто сразу разобраться, какие типажи использовать, какие методы и функции вызывать из пакета, поэтому каждый пакет имеет документацию с инструкциями по его использованию. Ещё одной замечательной особенностью
Cargo является выполнение команды cargo doc --open
, которая локально собирает документацию, предоставляемую всеми вашими зависимостями, и открывает её в браузере. К примеру, если интересна другая функциональность из пакета rand
,
запустите cargo doc --open и нажмите rand в боковой панели слева.
use std::io; use rand::Rng; fn main
() { println!
(
"Guess the number!"
); let secret_number = rand::thread_rng().gen_range(
1
..=
100
); println!
(
"The secret number is: {secret_number}"
); println!
(
"Please input your guess."
); let mut guess =
String
::new(); io::stdin()
.read_line(&
mut guess)
.expect(
"Failed to read line"
); println!
(
"You guessed: {guess}"
);
}
Во второй новой строке печатается секретный номер. Полезно, пока разрабатывается программа, иметь возможность тестировать её, но в финальной версии мы это удалим.
Конечно это не похоже на игру, если программа печатает ответ сразу после запуска!
Попробуйте запустить программу несколько раз:
Вы должны получить разные случайные числа, и все они должны быть числами от 1 до
100. Отличная работа!
Сравнение догадки с секретным числом
Теперь, когда у нас есть пользовательский ввод и случайное число, мы можем их сравнить. Этот шаг показан в Листинге 2-4. Обратите внимание, что этот код ещё не удастся скомпилировать.
Имя файла: src/main.rs
$
cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 2.53s
Running `target/debug/guessing_game`
Guess the number!
The secret number is: 7
Please input your guess.
4
You guessed: 4
$
cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/guessing_game`
Guess the number!
The secret number is: 83
Please input your guess.
5
You guessed: 5 use rand::Rng; use std::cmp::Ordering; use std::io; fn main
() {
// --snip-- println!
(
"You guessed: {guess}"
); match guess.cmp(&secret_number) {
Ordering::Less => println!
(
"Too small!"
),
Ordering::Greater => println!
(
"Too big!"
),
Ordering::Equal => println!
(
"You win!"
),
}
}
Листинг 2-4: Обработка возможных возвращаемых значений сравнения двух чисел
Сначала добавим ещё один оператор use
, который вводит тип с именем std::cmp::Ordering в область видимости из стандартной библиотеки. Тип
Ordering является ещё одним перечислением и имеет варианты
Less
,
Greater и
Equal
. Это три возможных исхода, при сравнении двух величин.
После чего ниже добавляем пять новых строк, использующих тип
Ordering
. Метод cmp сравнивает два значения и может вызываться для всего, что можно сравнить. Он принимает ссылку на все, что требуется сравнить: здесь сравнивается guess с secret_number
. В результате возвращается вариант перечисления
Ordering
, которое мы ввели в область видимости с помощью оператора use
. Для принятия решения о том, что делать дальше, мы используем выражение match
, определяющее, какой вариант
Ordering был возвращён из вызова cmp со значениями guess и secret_number
Выражение match состоит из веток (arms). Ветка состоит из шаблона для сопоставления и кода, который будет запущен, если значение, переданное в match
, соответствует шаблону этой ветки. Rust принимает значение, заданное match
, и по очереди просматривает шаблон каждой ветки. Шаблоны и конструкция match
- это мощные возможности Rust, позволяющие выразить множество ситуаций, с которыми может столкнуться ваш код, и гарантировать их обработку. Эти возможности будут подробно раскрыты в Главе 6 и Главе 18 соответственно.
Давайте рассмотрим пример с выражением match
, которое мы используем здесь.
Предположим, что пользователь угадал 50, а случайно сгенерированное секретное число на этот раз равно 38. Когда код сравнивает 50 с 38, метод cmp вернёт
Ordering::Greater
,
поскольку 50 больше 38. Выражение match получает
Ordering::Greater значение и начинает проверку шаблона каждой ветки. Оно просматривает шаблон первой ветви,
Ordering::Less
, и видит, что значение
Ordering::Greater не соответствует
Ordering::Less
, поэтому игнорирует код в этой ветви и переходит к следующей.
Следующий образец ветки —
Ordering::Greater
, который соответствует Ordering
Ordering::Greater
! Связанный код в этой ветке будет выполняться и выводить
Too big!
на экран. Выражение match заканчивается после первого успешного совпадения,
поэтому в этом сценарии оно не будет рассматривать последнюю ветку.
Однако, код в листинге 2-4 все ещё не скомпилируется. Давайте попробуем: