Главная страница
Навигация по странице:

  • Обновление пакета для получения новой версии

  • Генерация случайного числа

  • Листинг 2-3: Добавление кода для генерации случайного числа

  • Сравнение догадки с секретным числом

  • Листинг 2-4: Обработка возможных возвращаемых значений сравнения двух чисел

  • Возможность нескольких догадок с помощью циклов

  • Выход после правильной догадки

  • Язык программирования Rust


    Скачать 7.02 Mb.
    НазваниеЯзык программирования Rust
    Дата12.04.2023
    Размер7.02 Mb.
    Формат файлаpdf
    Имя файлаThe Rust Programming Language_ru.pdf
    ТипУчебник
    #1056301
    страница4 из 62
    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"

    Листинг 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 все ещё не скомпилируется. Давайте попробуем:

    Суть ошибки заключается в наличии несовпадающих типов. У 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!

    Как видите, мы переместили все, начиная с подсказки ввода догадки, в цикл. Не забудьте добавить ещё по четыре пробела на отступы строк внутри цикла и запустите программу снова. Теперь программа будет бесконечно запрашивать ещё одну догадку, что фактически создаёт новую проблему. Похоже пользователь не сможет выйти из игры!
    Пользователь может прервать выполнение программы с помощью сочетания клавиш 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


    написать администратору сайта