ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 11.01.2024
Просмотров: 1127
Скачиваний: 5
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
СОДЕРЖАНИЕ
Суть ошибки заключается в наличии несовпадающих типов. У Rust строгая, статическая система типов. Однако он также имеет вывод типов. Когда мы написали let mut guess =
String::new()
, Rust смог сделать вывод, что guess должна быть
String и не заставил указывать тип. С другой стороны, secret_number
- это числовой тип. Несколько типов чисел в Rust могут иметь значение от 1 до 100: i32
, 32-битное число; u32
, беззнаковое
32-битное число; i64
, 64-битное число, а также другие. Если не указано иное, Rust по умолчанию использует i32
, который будет типом secret_number
, если не добавлять информацию о типе в другом месте, которая заставит Rust вывести другой числовой тип.
Причина ошибки заключается в том, что Rust не может сравнить строку и числовой тип.
В конечном итоге, необходимо преобразовать
String
, считываемую программой в качестве входных данных, в реальный числовой тип, чтобы иметь возможность числового сравнения с секретным числом. Для этого добавьте эту строку в тело функции main
:
Имя файла: src/main.rs
$
cargo build
Compiling libc v0.2.86
Compiling getrandom v0.2.2
Compiling cfg-if v1.0.0
Compiling ppv-lite86 v0.2.10
Compiling rand_core v0.6.2
Compiling rand_chacha v0.3.0
Compiling rand v0.8.3
Compiling guessing_game v0.1.0 (file:///projects/guessing_game) error[E0308]: mismatched types
-->
src/main.rs:22:21
|
22 | match guess.cmp(&secret_number) {
| ^^^^^^^^^^^^^^ expected struct `String`, found integer
|
= note: expected reference `&String` found reference `&{integer}`
For more information about this error, try `rustc --explain E0308`. error: could not compile `guessing_game` due to previous error
Вот эта строка:
Мы создаём переменную с именем guess
. Но подождите, разве в программе уже нет переменной с этим именем guess
? Так и есть, но Rust позволяет нам затенять
предыдущее значение guess новым. Затенение позволяет нам повторно использовать имя переменной guess
, чтобы избежать создания двух уникальных переменных, таких как guess_str и guess
, например. Мы рассмотрим это более подробно в главе 3, а пока знайте, что эта функция часто используется, когда необходимо преобразовать значение из одного типа в другой.
Мы связываем эту новую переменную с выражением guess.trim().parse()
. Переменная guess в этом выражении относится к исходной переменной guess
, которая содержала входные данные в виде строки. Метод trim на экземпляре
String удалит любые пробельные символы в начале и конце строки для того, чтобы мы могли сопоставить строку с u32
, которая содержит только числовые данные. Пользователь должен нажать enter, чтобы выполнить read_line и ввести свою догадку, при этом в строку добавится символ новой строки. Например, если пользователь набирает 5 и нажимает enter, guess будет выглядеть так:
5\n
. Символ
\n означает "новая строка". (В Windows нажатие enter сопровождается возвратом каретки и новой строкой,
\r\n
). Метод trim убирает
\n или
\r\n
, оставляя только
5
Метод parse строк преобразует строку в другой тип. Здесь мы используем его для преобразования строки в число. Нам нужно сообщить Rust точный числовой тип,
который мы хотим, используя let guess: u32
. Двоеточие (
:
) после guess говорит Rust,
что мы аннотируем тип переменной. В Rust есть несколько встроенных числовых типов; u32
, показанный здесь, представляет собой 32-битное целое число без знака. Это хороший выбор по умолчанию для небольшого положительного числа. Вы узнаете о других типах чисел в Главе 3. Кроме того, аннотация u32
в этом примере программы и
// --snip-- let mut guess =
String
::new(); io::stdin()
.read_line(&
mut guess)
.expect(
"Failed to read line"
); let guess: u32
= guess.trim().parse().expect(
"Please type a number!"
); println!
(
"You guessed: {guess}"
); match guess.cmp(&secret_number) {
Ordering::Less => println!
(
"Too small!"
),
Ordering::Greater => println!
(
"Too big!"
),
Ordering::Equal => println!
(
"You win!"
),
} let guess: u32
= guess.trim().parse().expect(
"Please type a number!"
);
сравнение с secret_number означает, что Rust сделает вывод, что secret_number должен быть u32
. Итак, теперь сравнение будет между двумя значениями одного типа!
Метод parse будет работать только с символами, которые логически могут быть преобразованы в числа, и поэтому легко может вызвать ошибки. Если, например, строка содержит
A???? %
, преобразовать её в число невозможно. Так как метод parse может потерпеть неудачу, возвращается тип
Result
, так же как и метод read_line
(обсуждалось ранее в разделе "Обработка потенциальной неудачи с помощью
Result
Type"
). Мы будем точно так же обрабатывать данный
Result
, вновь используя метод expect
. Если parse вернёт вариант
Result
Err
, так как не смог создать число из строки,
вызов expect аварийно завершит игру и распечатает переданное ему сообщение. Если parse сможет успешно преобразовать строку в число, он вернёт вариант
Result
Ok
, а expect вернёт число, полученное из значения
Ok
Давайте запустим программу теперь!
Хорошо! Несмотря на то, что были добавлены пробелы перед догадкой 76, программа все равно вывела пользовательскую догадку 76. Запустите программу несколько раз,
чтобы проверить разное поведение при различных типах ввода: задайте число правильно, задайте слишком большое число и задайте слишком маленькое число.
Сейчас у нас работает большая часть игры, но пользователь может сделать только одно предположение. Давайте изменим это, добавив цикл!
Возможность нескольких догадок с помощью циклов
Ключевое слово loop создаёт бесконечный цикл. Мы добавляем цикл, чтобы дать пользователям больше шансов угадать число:
Имя файла: src/main.rs
$
cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 0.43s
Running `target/debug/guessing_game`
Guess the number!
The secret number is: 58
Please input your guess.
76
You guessed: 76
Too big!
. Итак, теперь сравнение будет между двумя значениями одного типа!
Метод parse будет работать только с символами, которые логически могут быть преобразованы в числа, и поэтому легко может вызвать ошибки. Если, например, строка содержит
A???? %
, преобразовать её в число невозможно. Так как метод parse может потерпеть неудачу, возвращается тип
Result
, так же как и метод read_line
(обсуждалось ранее в разделе "Обработка потенциальной неудачи с помощью
Result
Type"
). Мы будем точно так же обрабатывать данный
Result
, вновь используя метод expect
. Если parse вернёт вариант
Result
Err
, так как не смог создать число из строки,
вызов expect аварийно завершит игру и распечатает переданное ему сообщение. Если parse сможет успешно преобразовать строку в число, он вернёт вариант
Result
Ok
, а expect вернёт число, полученное из значения
Ok
Давайте запустим программу теперь!
Хорошо! Несмотря на то, что были добавлены пробелы перед догадкой 76, программа все равно вывела пользовательскую догадку 76. Запустите программу несколько раз,
чтобы проверить разное поведение при различных типах ввода: задайте число правильно, задайте слишком большое число и задайте слишком маленькое число.
Сейчас у нас работает большая часть игры, но пользователь может сделать только одно предположение. Давайте изменим это, добавив цикл!
Возможность нескольких догадок с помощью циклов
Ключевое слово loop создаёт бесконечный цикл. Мы добавляем цикл, чтобы дать пользователям больше шансов угадать число:
Имя файла: src/main.rs
$
cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 0.43s
Running `target/debug/guessing_game`
Guess the number!
The secret number is: 58
Please input your guess.
76
You guessed: 76
Too big!
Как видите, мы переместили все, начиная с подсказки ввода догадки, в цикл. Не забудьте добавить ещё по четыре пробела на отступы строк внутри цикла и запустите программу снова. Теперь программа будет бесконечно запрашивать ещё одну догадку, что фактически создаёт новую проблему. Похоже пользователь не сможет выйти из игры!
Пользователь может прервать выполнение программы с помощью сочетания клавиш ctrl-c. Но есть и другой способ спастись от этого ненасытного монстра, о котором говорилось при обсуждении parse в "Сравнение догадки с секретным числом"
: если пользователь введёт нечисловой ответ, программа завершится аварийно. Мы можем воспользоваться этим, чтобы позволить пользователю выйти из игры, как показано здесь:
// --snip-- println!
(
"The secret number is: {secret_number}"
); loop
{ println!
(
"Please input your guess."
);
// --snip-- match guess.cmp(&secret_number) {
Ordering::Less => println!
(
"Too small!"
),
Ordering::Greater => println!
(
"Too big!"
),
Ordering::Equal => println!
(
"You win!"
),
}
}
}
$
cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 1.50s
Running `target/debug/guessing_game`
Guess the number!
The secret number is: 59
Please input your guess.
45
You guessed: 45
Too small!
Please input your guess.
60
You guessed: 60
Too big!
Please input your guess.
59
You guessed: 59
You win!
Please input your guess. quit thread 'main' panicked at 'Please type a number!: ParseIntError { kind:
InvalidDigit }', src/main.rs:28:47 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Ввод quit приведёт к выходу из игры, но, как вы заметите, так же будет и при любом другом нечисловом вводе. Однако это, мягко говоря, не оптимально. Мы хотим, чтобы игра автоматически остановилась, когда будет угадано правильное число.
Выход после правильной догадки
Давайте запрограммируем игру на выход при выигрыше пользователя, добавив оператор break
:
Файл: src/main.rs
Добавление строки break после
You win!
заставляет программу выйти из цикла, когда пользователь правильно угадает секретное число. Выход из цикла также означает выход из программы, так как цикл является последней частью main
1 2 3 4 5 6 7 8 9 ... 62
Обработка недопустимого ввода
Чтобы ещё улучшить поведение игры, вместо аварийного завершения программы, когда пользователь вводит не число, давайте заставим игру проигрывать этот случай, позволяя пользователю продолжить угадывание. Для этого необходимо изменить строку, в которой guess преобразуется из
String в u32
, как показано в Листинге 2-5.
Файл: src/main.rs
// --snip-- match guess.cmp(&secret_number) {
Ordering::Less => println!
(
"Too small!"
),
Ordering::Greater => println!
(
"Too big!"
),
Ordering::Equal => { println!
(
"You win!"
); break
;
}
}
}
}
Листинг 2-5. Игнорирование предположения, отличного от числа, и запрос другого предположения вместо
сбоя программы
Мы переключаем вызов expect на выражение match
, чтобы перейти от аварийного завершения при ошибке к обработке ошибки. Помните, что parse возвращает тип
Result
, а
Result
- это перечисление, которое имеет варианты
Ok и
Err
. Здесь мы используем выражение match
, как и в случае с результатом
Ordering метода cmp
Если parse может успешно преобразовать строку в число, он вернёт значение
Ok
,
содержащее полученное число. Это значение
Ok будет соответствовать шаблону первой ветки, а выражение match просто вернёт значение num
, созданное parse
, и поместит его внутрь значения
Ok
. Это число окажется именно там, где мы хотим, в новой переменной guess
Если метод parse
не способен превратить строку в число, он вернёт значение
Err
,
которое содержит более подробную информацию об ошибке. Значение
Err не совпадает с шаблоном
Ok(num)
в первой ветке match
, но совпадает с шаблоном
Err(_)
второй ветки. Подчёркивание
_
является всеохватывающим выражением. В этой ветке мы говорим, что хотим обработать совпадение всех значений
Err
, независимо от того,
какая информация находится внутри
Err
. Поэтому программа выполнит код второй ветки, continue
, который сообщает программе перейти к следующей итерации loop и
запросить ещё одну догадку. В этом случае программа эффективно игнорирует все ошибки, с которыми может столкнуться parse
!
Все в программе теперь должно работать как положено. Давайте попробуем:
// --snip-- io::stdin()
.read_line(&
mut guess)
.expect(
"Failed to read line"
); let guess: u32
= match guess.trim().parse() {
Ok
(num) => num,
Err
(_) => continue
,
}; println!
(
"You guessed: {guess}"
);
// --snip--
Потрясающе! С помощью одной маленькой последней правки мы закончим игру в угадывание. Напомним, что программа все ещё печатает секретное число. Это хорошо подходило для тестирования, но это портит игру. Давайте удалим println!
, который выводит секретное число. В Листинге 2-6 показан окончательный вариант кода.
Файл: src/main.rs
$
cargo run
Compiling guessing_game v0.1.0 (file:///projects/guessing_game)
Finished dev [unoptimized + debuginfo] target(s) in 4.45s
Running `target/debug/guessing_game`
Guess the number!
The secret number is: 61
Please input your guess.
10
You guessed: 10
Too small!
Please input your guess.
99
You guessed: 99
Too big!
Please input your guess. foo
Please input your guess.
61
You guessed: 61
You win!
Листинг 2-6: Полный код игры в угадывание
Итоги
На данный момент вы успешно создали игру в угадывание. Поздравляем!
Этот проект - практический способ познакомить вас со многими новыми концепциями
Rust: let
, match
, функции, использование внешних пакетов и многое другое. В
следующих нескольких главах вы изучите эти концепции более подробно. Глава 3
охватывает понятия, которые есть в большинстве языков программирования, такие как переменные, типы данных и функции, и показывает, как использовать их в Rust. В главе 4
рассматривается владение, особенность, которая отличает Rust от других языков. В главе
5 обсуждаются структуры и синтаксис методов, а в главе 6 объясняется, как работают перечисления.
use rand::Rng; use std::cmp::Ordering; use std::io; fn main
() { println!
(
"Guess the number!"
); let secret_number = rand::thread_rng().gen_range(
1
..=
100
); loop
{ println!
(
"Please input your guess."
); let mut guess =
String
::new(); io::stdin()
.read_line(&
mut guess)
.expect(
"Failed to read line"
); let guess: u32
= match guess.trim().parse() {
Ok
(num) => num,
Err
(_) => continue
,
}; println!
(
"You guessed: {guess}"
); match guess.cmp(&secret_number) {
Ordering::Less => println!
(
"Too small!"
),
Ordering::Greater => println!
(
"Too big!"
),
Ordering::Equal => { println!
(
"You win!"
); break
;
}
}
}
}
Общие концепции программирования
В этой главе рассматриваются концепции, присутствующие почти в каждом языке программирования, и то, как они работают в Rust. В основе большинства языков программирования есть много общего. Все концепции, представленные в этой главе, не являются уникальными для Rust, но мы обсудим их в контексте Rust и разъясним правила использования этих концепций.
В частности, вы узнаете о переменных, базовых типах, функциях, комментариях и потоке управления. Эти основы будут встречаться потом в каждой программе Rust, и их раннее освоение даст вам прочную основу для старта.
Ключевые слова
Как и в других языках, язык Rust содержит набор ключевых слов, которые зарезервированы для использования непосредственно в языке. Учтите, вы не можете использовать эти слова в качестве имён переменных или функций.
Большинство ключевых слов имеют специальные назначения, и вы будете использовать их для решения различных задач в ваших программах Rust.
Некоторые из них не имеют текущей функциональности, но были зарезервированы для функциональности, которая может быть добавлена в Rust в будущем. Список ключевых слов можно найти в
Приложении A