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

  • Программируем игру Угадайка

  • Настройка нового проекта

  • Листинг 2-1: Код, который получает отгадку от пользователя и печатает её

  • Хранение значений с помощью переменных

  • Получение пользовательского ввода

  • Обработка потенциального сбоя с помощью типа

  • Напечатать значений с помощью заполнителей

  • Тестирование первой части

  • Генерация секретного числа

  • Использование пакета для получения дополнительной функциональности

  • Листинг 2-2: Результат выполнения cargo build после добавления крейта rand в качестве зависимости

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


    Скачать 7.02 Mb.
    НазваниеЯзык программирования Rust
    Дата12.04.2023
    Размер7.02 Mb.
    Формат файлаpdf
    Имя файлаThe Rust Programming Language_ru.pdf
    ТипУчебник
    #1056301
    страница3 из 62
    1   2   3   4   5   6   7   8   9   ...   62
    Cargo как Конвенция
    В простых проектах Cargo не даёт больших преимуществ по сравнению с использованием rustc
    , но он проявит себя, когда ваши программы станут более сложными. Когда программы вырастают до нескольких файлов или нуждаются в зависимостях, гораздо проще позволить Cargo координировать сборку.
    Не смотря на то, что проект hello_cargo простой, теперь он использует большую часть реального инструментария, который вы будете повседневно использовать в вашей карьере, связанной с Rust. Когда потребуется работать над проектами размещёнными в сети, вы сможете просто использовать следующую последовательность команд для получения кода с помощью Git, перехода в каталог проекта, сборку проекта:
    Для получения дополнительной информации о Cargo ознакомьтесь с его документацией
    Итоги
    Теперь вы готовы начать своё Rust путешествие! В данной главе вы изучили как:
    установить последнюю стабильную версию Rust, используя rustup
    ,
    обновить Rust до последней версии,
    открыть локально установленную документацию,
    написать и запустить программу типа "Hello, world!", используя напрямую компилятор rustc
    ,
    создать и запустить новый проект, используя соглашения и команды Cargo.
    $
    git clone example.org/someproject
    $
    cd someproject
    $
    cargo build

    Это отличное время для создания более существенной программы, чтобы привыкнуть читать и писать код на языке Rust. Итак, в главе 2 мы построим программу для игры в угадай число. Если вы предпочитаете начать с изучения того, как работают общие концепции программирования в Rust, обратитесь к главе 3, а затем вернитесь к главе 2.

    Программируем игру Угадайка
    Давайте погрузимся в Rust, вместе выполнив практический проект! Эта глава познакомит с несколькими распространёнными концепциями Rust, показав, как использовать их в реальной программе. Вы узнаете о let
    , match
    , методах, ассоциированных функциях,
    использовании внешних пакетов и многом другом! В следующих главах рассмотрим эти идеи более подробно. В этой главе вы на практике познакомитесь с основами.
    Мы реализуем классическую для начинающих программистов задачу: игру в угадывание.
    Вот как это работает: программа генерирует случайное целое число в диапазоне от 1 до
    100. Затем она предлагает игроку ввести отгадку. После ввода отгадки программа укажет,
    является ли отгадка слишком заниженной или слишком завышенной. Если отгадка верна,
    игра напечатает поздравительное сообщение и завершится.
    Настройка нового проекта
    Для настройки нового проекта перейдите в каталог projects, который вы создали в главе
    1, и создайте новый проект с использованием Cargo, как показано ниже:
    Первая команда, cargo new
    , принимает в качестве первого аргумента имя проекта
    (
    guessing_game
    ). Вторая команда изменяет каталог на новый каталог проекта.
    Загляните в созданный файл Cargo.toml:
    Файл: Cargo.toml
    Как вы уже видели в Главе 1, cargo new создаёт программу "Hello, world!". Посмотрите файл src/main.rs:
    Файл: src/main.rs
    $
    cargo new guessing_game
    $
    cd guessing_game
    [package]
    name =
    "guessing_game"
    version =
    "0.1.0"
    edition =
    "2021"
    # See more keys and their definitions at https://doc.rust- lang.org/cargo/reference/manifest.html
    [dependencies]

    Теперь давайте скомпилируем программу "Hello, world!" и сразу на этом же этапе запустим её с помощью команды cargo run
    :
    Команда run пригодится, когда необходимо ускоренно выполнить итерацию проекта,
    мы так собираемся сделать в этом проекте, быстро тестируя каждую итерацию, прежде чем перейти к следующей.
    Снова откройте файл src/main.rs. Весь код вы будете писать в этом файле.
    Обработка отгадки
    Первая часть программы игры угадывания запрашивает ввод данных пользователем,
    обрабатывает их и проверяет, что вводимые данные имеют ожидаемую форму. Для начала мы позволим игроку ввести отгадку. Введите код из Листинга 2-1 в src/main.rs.
    Файл: src/main.rs
    Листинг 2-1: Код, который получает отгадку от пользователя и печатает её
    Этот код содержит много информации, поэтому давайте рассмотрим его построчно.
    Чтобы получить пользовательский ввод и затем вывести результат в качестве вывода,
    fn main
    () { println!
    (
    "Hello, world!"
    );
    }
    $
    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`
    Hello, world! use std::io; fn main
    () { println!
    (
    "Guess the 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}"
    );
    }
    нам нужно включить в область видимости библиотеку ввода/вывода io
    . Библиотека io является частью стандартной библиотеки, известной как std
    :
    По умолчанию в Rust есть набор элементов, определённых в стандартной библиотеке,
    которые он добавляет в область видимости каждой программы. Этот набор называется
    прелюдией, и вы можете изучить его содержание в документации стандартной библиотеки
    Если тип, который требуется использовать, отсутствует в прелюдии, его нужно явно ввести в область видимости с помощью оператора use
    . Использование библиотеки std::io предоставляет ряд полезных функциональных возможностей, включая способность принимать пользовательский ввод.
    Как уже отмечалось в главе 1, функция main является точкой входа в программу:
    Синтаксис fn объявляет новую функцию, круглые скобки
    ()
    указывают на отсутствие параметров, а фигурная скобка
    {
    обозначает начало тела функции.
    Также в главе 1 упоминалось, что println!
    - это макрос, который печатает строку на экран:
    Этот код печатает подсказку об игре и запрашивает пользовательский ввод.
    Хранение значений с помощью переменных
    Далее мы создаём переменную для хранения пользовательского ввода, как показано ниже:
    Вот теперь программа становится интересней! Очень многое происходит в этой маленькой строке. Для создания переменной мы используем оператор let
    . Вот ещё
    один пример:
    Эта строка создаёт новую переменную с именем apples и привязывает её к значению 5.
    В Rust переменные неизменяемы по умолчанию, то есть, как только мы присвоим use std::io; fn main
    () { println!
    (
    "Guess the number!"
    ); println!
    (
    "Please input your guess."
    ); let mut guess =
    String
    ::new(); let apples =
    5
    ;
    переменной значение, значение не изменится. Мы подробно обсудим эту концепцию в разделе
    «Переменные и изменчивость».
    в Главе 3. Чтобы сделать переменную изменяемой, мы добавляем mut перед её именем:
    Примечание: Синтаксис
    //
    означает начало комментария, который продолжается до конца строки. Rust игнорирует все содержимое комментариев. Подробнее о комментариях мы поговорим в главе 3
    Возвращаясь к программе игры Угадайка, теперь вы знаете, что let mut guess предоставит изменяемую переменную с именем guess
    . Знак равенства (
    =
    ) сообщает
    Rust, что сейчас нужно связать что-то с этой переменной. Справа от знака равенства находится значение, связанное с guess
    , которое является результатом вызова функции
    String::new
    , возвращающей новый экземпляр
    String
    String
    - это тип строки,
    предоставляемый стандартной библиотекой, который является расширяемым фрагментом текста в кодировке UTF-8.
    Синтаксис
    ::
    в строке
    ::new указывает, что new является ассоциированной функцией
    String типа. Ассоциированная функция - это функция, реализованная для типа, в данном случае
    String
    . Функция new создаёт новую, пустую строку. Функцию new можно встретить во многих типах, это типичное название для функции, которая создаёт новое значение какого-либо типа.
    В целом, строка let mut guess = String::new();
    создала изменяемую переменную,
    которая связывается с новым, пустым экземпляром
    String
    . Фух!
    Получение пользовательского ввода
    Напомним, мы подключили функциональность ввода/вывода из стандартной библиотеки с помощью use std::io;
    в первой строке программы. Теперь мы вызовем функцию stdin из модуля io
    , которая позволит нам обрабатывать пользовательский ввод:
    Если бы мы не импортировали библиотеку io с помощью use std::io в начале программы, мы все равно могли бы использовать эту функцию, записав вызов этой функции как std::io::stdin
    . Функция stdin возвращает экземпляр std::io::Stdin
    ,
    который является типом, представляющим дескриптор стандартного ввода для вашего терминала.
    let apples =
    5
    ;
    // неизменяемая let mut bananas =
    5
    ;
    // изменяемая io::stdin()
    .read_line(&
    mut guess)

    Далее строка
    .read_line(&mut guess)
    вызывает метод read_line на дескрипторе стандартного ввода для получения ввода от пользователя. Мы также передаём
    &mut guess в качестве аргумента read_line
    , сообщая ему, в какой строке хранить пользовательский ввод. Главная задача read_line
    - принять все, что пользователь вводит в стандартный ввод, и сложить это в строку (не переписывая её содержимое),
    поэтому мы передаём эту строку в качестве аргумента. Строковый аргумент должен быть изменяемым, чтобы метод мог изменить содержимое строки.
    Символ
    &
    указывает, что этот аргумент является ссылкой, который предоставляет возможность нескольким частям вашего кода получить доступ к одному фрагменту данных без необходимости копировать эти данные в память несколько раз. Ссылки - это сложная функциональная возможность, а одним из главных преимуществ Rust является безопасность и простота использования ссылок. Чтобы дописать эту программу, вам не понадобится знать много таких подробностей. Пока вам достаточно знать, что ссылки,
    как и переменные, по умолчанию неизменяемы. Соответственно, чтобы сделать её
    изменяемой, нужно написать
    &mut guess
    , а не
    &guess
    . (В главе 4 ссылки будут описаны более подробно).
    Обработка потенциального сбоя с помощью типа Result
    Мы все ещё работаем над этой строкой кода. Сейчас мы обсуждаем третью строку, но обратите внимание, что она по-прежнему является частью одной логической строки кода. Следующая часть - метод:
    Мы могли бы написать этот код так:
    Однако одну длинную строку трудно читать, поэтому лучше разделить её. При вызове метода с помощью синтаксиса
    .method_name()
    часто целесообразно вводить новую строку и другие пробельные символы, чтобы разбить длинные строки. Теперь давайте обсудим, что делает эта строка.
    Как упоминалось ранее, read_line помещает всё, что вводит пользователь, в строку,
    которую мы ему передаём, но также возвращает значение
    Result
    Result это
    перечисление
    , часто называемый enum, тип, который может находиться в одном из нескольких возможных состояний. Мы называем каждое такое состояние вариантом.
    В главе 6 перечисления будут рассмотрены более подробно. Назначение всех типов
    Result заключается в передаче информации для обработки ошибок.
    Варианты
    Result
    :
    Ok и
    Err
    . Вариант
    Ok указывает на то, что операция прошла успешно, а внутри
    Ok находится успешно сгенерированное значение. Вариант
    Err
    .expect(
    "Failed to read line"
    ); io::stdin().read_line(&
    mut guess).expect(
    "Failed to read line"
    );
    означает, что операция не удалась, а
    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
    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

    Вы можете увидеть другие номера версий (но все они будут совместимы с кодом,
    благодаря 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


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