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

  • Листинг 2-5. Игнорирование предположения, отличного от числа, и запрос другого предположения вместо сбоя программы

  • Листинг 2-6: Полный код игры в угадывание Итоги

  • Общие концепции программирования

  • Переменные и понятие изменяемости

  • Таблица 3-1: целочисленные типы в Rust Длина Со знаком Без знака

  • Таблица 3-2: Целочисленные литералы в Rust Числовые литералы Пример Десятичный98_222Шестнадцатеричный0xff Числовые литералы

  • Переполнение целых чисел

  • Числа с плавающей запятой

  • Сложные типы данных Сложные типы могут группировать несколько значений в один тип. В Rust есть два примитивных сложных (комбинированных) типа: кортежи и массивы.Кортежи

  • Доступ к элементам массива

  • Некорректный доступ к элементу массива

  • Листинг 3-1: Объявление функции main, содержащей один оператор

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


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

    Переменные и понятие изменяемости
    Как упоминалось в разделе
    “Сохранение значений в переменных”
    , по умолчанию переменные являются неизменяемыми. Это одна из многих подсказок, которые Rust даёт вам для написания кода таким образом, чтобы использовать преимущества безопасности и простого параллелизма, которые предлагает Rust. Однако у вас есть возможность сделать ваши переменные изменяемыми. Давайте рассмотрим, как и почему Rust поощряет неизменность, и почему иногда вы можете отказаться от этого.
    Когда переменная неизменяемая, то её значение нельзя менять, как только значение привязано к её имени. Приведём пример использования этого типа переменной. Для этого создадим новый проект variables в каталоге projects при помощи команды: cargo new variables
    Потом в созданной папке проекта variables откройте исходный файл src/main.rs и замените содержимое следующим кодом, который пока не будет компилироваться:
    Файл: src/main.rs
    Сохраните код программы и выполните команду cargo run
    . В командной строке вы увидите сообщение об ошибке:
    Данный пример показывает, как компилятор помогает вам находить ошибки в ваших программах. Ошибки компилятора могут вызывать разочарование, но на самом деле они лишь означают, что ваша программа ещё не делает то, что вы от неё хотите. Они не
    fn main
    () { let x =
    5
    ; println!
    (
    "The value of x is: {x}"
    ); x =
    6
    ; println!
    (
    "The value of x is: {x}"
    );
    }
    $
    cargo run
    Compiling variables v0.1.0 (file:///projects/variables) error[E0384]: cannot assign twice to immutable variable `x`
    -->
    src/main.rs:4:5
    |
    2 | let x = 5;
    | -
    | |
    | first assignment to `x`
    | help: consider making this binding mutable: `mut x`
    3 | println!("The value of x is: {x}");
    4 | x = 6;
    | ^^^^^ cannot assign twice to immutable variable
    For more information about this error, try `rustc --explain E0384`. error: could not compile `variables` due to previous error

    означают, что вы не являетесь хорошим программистом! Опытные разработчики Rust также получают ошибки компиляции.
    Сообщение об ошибке указывает, что причиной ошибки является то, что вы cannot assign twice to immutable variable `x`
    (не можете присвоить неизменяемой переменной новое значение), потому что вы пытались присвоить второе значение неизменяемой переменной x
    Важно, что мы получаем ошибку времени компиляции, при попытке изменить значение,
    обозначенное как неизменяемое, потому что такая ситуация может привести к ошибкам.
    Если одна часть нашего кода исходит из предположения, что значение никогда не изменится, а другая часть кода изменяет это значение, вполне возможно, что первая часть кода не будет делать то, для чего она была предназначена. Причину такого рода ошибок может быть трудно отследить постфактум, особенно когда второй фрагмент кода изменяет значение только иногда. Компилятор Rust гарантирует, что если вы заявите,
    что значение не изменится, оно действительно не изменится, поэтому вам не нужно следить за ним самостоятельно. Таким образом, ваш код легче понять.
    Но изменяемость может быть очень полезной и может сделать код более удобным для написания. Хотя переменные неизменяемы по умолчанию, вы можете сделать их изменяемыми, добавив mut перед именем переменной, как вы делали это в Главе 2.
    Добавление mut также передаёт будущим читателям кода информацию о том, что другие части кода будут изменять значение этой переменной.
    Например, изменим src/main.rs на следующий код:
    Файл: src/main.rs
    Запустив программу, мы получим результат:
    Когда используется mut
    , мы можем изменять значение, привязанное к x
    , с
    5
    до
    6
    . В
    конечном счёте, решение о том, использовать изменяемость или нет, зависит от вас и от того, что вы считаете наиболее приемлемым в данной конкретной ситуации.
    fn main
    () { let mut x =
    5
    ; println!
    (
    "The value of x is: {x}"
    ); x =
    6
    ; println!
    (
    "The value of x is: {x}"
    );
    }
    $
    cargo run
    Compiling variables v0.1.0 (file:///projects/variables)
    Finished dev [unoptimized + debuginfo] target(s) in 0.30s
    Running `target/debug/variables`
    The value of x is: 5
    The value of x is: 6

    Константы
    Подобно неизменяемым переменным, константы — это значения, которые связаны с именем и не могут изменяться, но между константами и переменными есть несколько различий.
    Во-первых, не разрешается использовать mut с константами. Константы не просто неизменны по умолчанию — они неизменны всегда. Вы объявляете константы,
    используя ключевое слово const вместо ключевого слова let
    , и тип должен быть явно указан. Мы собираемся рассмотреть типы и аннотации типов в следующем разделе
    “Типы данных”
    , так что не беспокойтесь о деталях сейчас. Просто знайте, что вы всегда должны явно указывать тип.
    Константы можно объявить в любой области видимости, включая глобальную. Это делает их удобными для значений, про которые должны знать многие другие части кода.
    Последней разницей является то, что константы можно установить только в константное выражение, а не в результат значения, которое можно посчитать только во время выполнения.
    Вот пример объявления константы:
    Имя константы
    THREE_HOURS_IN_SECONDS
    и её значение устанавливается в результате умножения числа 60 (количество секунд в минуте) на 60 (количество минут в часе) на 3
    (количество часов, которое мы хотим подсчитать в этой программе). Соглашение об именах констант в Rust состоит в том, чтобы использовать все символы в верхнем регистре с символами подчёркивания между словами. Компилятор способен вычислить ограниченный набор операций во время компиляции, что позволяет нам записать это значение так, чтобы его было легче понять и проверить, вместо того, чтобы устанавливать для этой константы значение 10800. См раздел справочника Rust,
    посвящённый вычислению констант для получения дополнительной информации о том,
    какие операции можно использовать при объявлении констант.
    Константы являются корректными для всего времени выполнения программы внутри той области видимости, где они были объявлены. Это свойство делает константы полезными для значений приложения, о которых может потребоваться знать нескольким частям программы. Например, максимальное количество очков, которое может заработать любой игрок в игре, или скорость света.
    Именование жёстко заданных значений, используемых в вашей программе, в качестве констант является удобным способом передать их смысл тем, кто будет сопровождать этот код в будущем. Это также помогает иметь только одно место в коде, которое вам нужно будет изменить, если заданное значение потребует обновления.
    const
    THREE_HOURS_IN_SECONDS: u32
    =
    60
    *
    60
    *
    3
    ;

    Затенение (переменных)
    Как вы видели в руководстве по игре Угадайка в
    Главе 2
    , вы можете объявить новую переменную с тем же именем, что и предыдущая переменная. Rustaceans говорят, что первая переменная затенена второй, а это значит, что компилятор увидит вторую переменную, когда вы воспользуетесь именем переменной. По сути, вторая переменная затеняет первую, присваивая себе любое использование имени переменной до тех пор,
    пока либо она сама не будет затенена, либо область действия не закончится. Мы можем затенить переменную, используя то же имя переменной и повторив использование ключевого слова let следующим образом:
    Файл: src/main.rs
    Эта программа сначала привязывает x
    к значению
    5
    . Затем она создаёт новую переменную x
    , повторяя let x =
    , беря исходное значение и добавляя
    1
    , чтобы значение x
    стало равным
    6
    . Затем во внутренней области видимости, созданной с помощью фигурных скобок, третий оператор let также затеняет x
    и создаёт новую переменную, умножая предыдущее значение на
    2
    , чтобы дать x
    значение
    12
    . Когда эта область заканчивается, внутреннее затенение заканчивается, и x
    возвращается к значению
    6
    . Запустив эту программу, она выведет следующее:
    Затенение отличается от объявления переменной с помощью mut
    , так как мы получим ошибку компиляции, если случайно попробуем переназначить значение без использования ключевого слова let
    . Используя let
    , можно выполнить несколько превращений над значением, при этом оставляя переменную неизменяемой, после того как все эти превращения завершены.
    Другой разницей между mut и затенением является то, что мы создаём совершенно новую переменную, когда снова используем слово let
    (ещё одну). Мы можем даже fn main
    () { let x =
    5
    ; let x = x +
    1
    ;
    { let x = x *
    2
    ; println!
    (
    "The value of x in the inner scope is: {x}"
    );
    } println!
    (
    "The value of x is: {x}"
    );
    }
    $
    cargo run
    Compiling variables v0.1.0 (file:///projects/variables)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
    Running `target/debug/variables`
    The value of x in the inner scope is: 12
    The value of x is: 6
    изменить тип значения, но снова использовать предыдущее имя. К примеру, наша программа спрашивает пользователя, сколько пробелов он хочет разместить между некоторым текстом, запрашивая символы пробела, но мы на самом деле хотим сохранить данный ввод как число:
    Первая переменная spaces
    — является строковым типом, а вторая переменная spaces
    — числовым типом. Таким образом, затенение избавляет нас от необходимости придумывать разные имена, такие как spaces_str и spaces_num
    ; вместо этого мы можем повторно использовать более простое имя spaces
    . Однако, если мы попытаемся использовать для этого mut
    , как здесь показано, то мы получим ошибку времени компиляции:
    Ошибка говорит, что не разрешается менять тип переменной:
    Теперь, когда вы имеете представление о работе с переменными, посмотрим на большее количество типов данных, которые они могут иметь.
    let spaces =
    " "
    ; let spaces = spaces.len(); let mut spaces =
    " "
    ; spaces = spaces.len();
    $
    cargo run
    Compiling variables v0.1.0 (file:///projects/variables) error[E0308]: mismatched types
    -->
    src/main.rs:3:14
    |
    2 | let mut spaces = " ";
    | ----- expected due to this value
    3 | spaces = spaces.len();
    | ^^^^^^^^^^^^ expected `&str`, found `usize`
    For more information about this error, try `rustc --explain E0308`. error: could not compile `variables` due to previous error

    Типы данных
    Каждое значение в Rust имеет определённый тип данных, сообщающий Rust, какие данные используются и как с ними работать. Мы рассмотрим два подмножества типов данных: скалярные и сложные.
    Не забывайте, что Rust является статически типизированным (statically typed) языком.
    Это означает, что он должен знать типы всех переменных во время компиляции. Обычно компилятор может предположить, какой тип используется (вывести его), основываясь на значении и на том, как мы с ним работаем. В случаях, когда может быть выведено несколько типов, необходимо добавлять аннотацию типа вручную. Например, когда мы конвертировали
    String в число с помощью вызова parse в разделе
    «Сравнение предположения с загаданным номером»
    главы 2, мы должны добавить такую аннотацию:
    Если мы не добавим аннотацию типа
    : u32
    выше, Rust отобразит ошибку, потому что компилятору нужно больше информации от нас, чтобы узнать, какой тип мы хотим использовать:
    В будущем вы увидите различные аннотации для разных типов данных.
    Скалярные типы данных
    Скалярный тип представляет единственное значение. В Rust есть четыре скалярных типа:
    целые и вещественные числа, логический тип и символы. Вы можете узнать эти типы по другим языкам программирования. Посмотрим, как они работают в Rust.
    Целочисленные типы
    Целое число (integer) — это число без дробной части. В главе 2 мы использовали один целочисленный тип — u32
    . Это объявление типа указывает, что значение, с которым оно связано, должно быть целым числом без знака (типы целых чисел со знаком начинаются с i
    вместо u
    ), которое занимает 32 бита памяти. В таблице 3-1 показаны let guess: u32
    =
    "42"
    .parse().expect(
    "Not a number!"
    );
    $
    cargo build
    Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations) error[E0282]: type annotations needed
    -->
    src/main.rs:2:9
    |
    2 | let guess = "42".parse().expect("Not a number!");
    | ^^^^^ consider giving `guess` a type
    For more information about this error, try `rustc --explain E0282`. error: could not compile `no_type_annotations` due to previous error
    встроенные целочисленные типы в Rust. Мы можем использовать любой из этих вариантов для объявления типа целочисленного значения.
    Таблица 3-1: целочисленные типы в Rust
    Длина
    Со знаком
    Без знака
    8 бит i8
    u8 16-bit i16
    u16 32 бит i32
    u32 64 бит i64
    u64 128-bit i128
    u128
    arch isize usize
    Каждый вариант может быть как со знаком, так и без знака и имеет явный размер. Такая характеристика типа как знаковый и беззнаковый определяет возможность числа быть отрицательным. Другими словами, должно ли число иметь знак (знаковое) или оно всегда будет только положительным и, следовательно, может быть представлено без знака (беззнаковое). Это похоже на написание чисел на бумаге: когда знак имеет значение, число отображается со знаком плюс или со знаком минус; однако, когда можно с уверенностью предположить, что число положительное, оно отображается без знака.
    Числа со знаком хранятся с использованием дополнительного кода
    Каждый вариант со знаком может хранить числа от -(2 n - 1
    ) до 2 n - 1
    - 1 включительно,
    где n — количество битов, которые использует этот вариант. Таким образом, i8
    может хранить числа от -(2 7
    ) до 2 7
    - 1, что равно значениям от -128 до 127. Варианты без знака могут хранить числа от 0 до 2 n
    - 1, поэтому u8
    может хранить числа от 0 до 2 8
    - 1, что равно значениям от 0 до 255.
    Кроме того, isize и usize зависят от архитектуры компьютера, на котором запускается ваша программа, что отражено в таблице как «arch»: 64-битный, если вы используете 64- битную архитектуру, и 32-битный, если вы на 32-битной архитектуре.
    Целочисленные литералы можно записывать в любой из форм, показанных в таблице 3-
    2. Обратите внимание, что числовые литералы, которые могут быть несколькими числовыми типами, позволяют использовать суффикс типа, например
    57u8
    , для обозначения типа. Числовые литералы также могут использовать
    _
    в качестве визуального разделителя, чтобы число было легче читать, например
    1_000
    , которое будет иметь то же значение, как если бы вы указали
    1000
    Таблица 3-2: Целочисленные литералы в Rust
    Числовые литералы
    Пример
    Десятичный
    98_222
    Шестнадцатеричный
    0xff

    Числовые литералы
    Пример
    Восьмеричный
    0o77
    Двоичный
    0b1111_0000
    Байт (только u8
    )
    b'A'
    Как же узнать, какой тип целого числа использовать? Если вы не уверены, значения по умолчанию в Rust, как правило, подходят для начала: целочисленные типы по умолчанию i32
    . Основной случай, в котором вы должны использовать isize или usize
    , — это индексация какой-либо коллекции.
    Переполнение целых чисел
    Допустим, у вас есть переменная типа u8
    , которая может хранить значения от 0 до
    255. Если вы попытаетесь присвоить переменной значение вне этого диапазона,
    например 256, произойдёт переполнение целого числа, что может привести к одному из двух вариантов поведения. Когда вы выполняете компиляцию в режиме отладки,
    Rust содержит проверки на переполнение целых чисел, которые заставят вашу программу паниковать во время выполнения, если такое произойдёт. Rust использует термин паника, когда программа завершается с ошибкой; мы обсудим панику более подробно в разделе " Непоправимые ошибки в случае panic!
    "
    в главе
    9.
    Когда вы выполняете компиляцию в режиме release с флагом
    --release
    , Rust не
    включает проверки на переполнение целых чисел, которые вызывают панику.
    Вместо этого, если происходит переполнение, Rust выполняет свёртку с двойным
    дополнением. Короче говоря, значения, превышающие максимальное значение,
    которое может хранить тип, "сворачиваются" к минимальному из значений,
    которые может хранить тип. В случае u8
    , значение 256 становится 0, значение 257
    становится 1 и так далее. Программа не запаникует, но переменная будет иметь значение, которое, вероятно, не соответствует вашим ожиданиям. Полагаться на поведение свёртки при переполнении целых чисел считается ошибкой.
    Чтобы явно обработать возможность переполнения, вы можете использовать следующие группы методов, предоставляемые стандартной библиотекой для примитивных числовых типов:
    Обёртывание во всех режимах с помощью методов wrapping_*
    , например wrapping_add
    Верните значение
    None
    , если произошло переполнение при использовании методов checked_*
    Верните число и логическое значение, указывающее, имело ли место переполнение с помощью методов overflowing_*
    Считать корректным установку минимального или максимального значения,
    используя метод saturating_*

    Числа с плавающей запятой
    В Rust также есть два примитивных типа для чисел с плавающей запятой, которые представляют собой числа с десятичными точками. Типы чисел с плавающей запятой в
    Rust — это f32
    и f64
    , имеющие размер 32 и 64 бита соответственно. Тип по умолчанию
    — f64
    , потому что на современных процессорах он примерно такой же скорости, как f32
    , но обеспечивает большую точность. Все типы с плавающей запятой обладают знаком.
    Вот пример, демонстрирующий числа с плавающей запятой в действии:
    Файл : src/main.rs
    Числа с плавающей точкой представлены согласно стандарту IEEE-754. Тип f32
    является числом с плавающей точкой одинарной точности, а f64
    имеет двойную точность.
    Числовые операции
    Rust поддерживает основные математические операции, которые ожидаются для всех типов чисел: сложение, вычитание, умножение, деление и остаток. Целочисленное деление округляется до ближайшего целого числа. В следующем коде показано, как использовать каждую числовую операцию в выражении let
    :
    Файл : src/main.rs fn main
    () { let x =
    2.0
    ;
    // f64
    let y: f32
    =
    3.0
    ;
    // f32
    } fn main
    () {
    // addition let sum =
    5
    +
    10
    ;
    // subtraction let difference =
    95.5
    -
    4.3
    ;
    // multiplication let product =
    4
    *
    30
    ;
    // division let quotient =
    56.7
    /
    32.2
    ; let floored =
    2
    /
    3
    ;
    // Results in 0
    // remainder let remainder =
    43
    %
    5
    ;
    }

    Каждое из этих выражений использует математические операции и вычисляет значение,
    которое затем присваивается переменной.
    Приложение Б
    содержит список всех операторов, имеющихся в Rust.
    Логический тип данных
    Как и в большинстве языков программирования, логический тип в Rust может иметь два значения: true и false и занимает в памяти один байт. Логический тип в Rust аннотируется при помощи bool
    . Например:
    Файл : src/main.rs
    Основной способ использования значений логического типа — условные конструкции,
    такие как выражение if
    . Мы расскажем про работу выражения if в разделе
    «Условные конструкции»
    Символьный тип данных
    Тип char в Rust — самый примитивный алфавитный тип языка. Вот несколько примеров объявления значений char
    :
    Файл : src/main.rs
    Обратите внимание, что мы указываем литералы char в одинарных кавычках, в отличие от строковых литералов, которые используют двойные кавычки. Тип char в Rust имеет размер четыре байта и представляет собой скалярное значение Unicode. Это значит, что он может представлять гораздо больше, чем просто ASCII. Буквы с ударением; китайские,
    японские и корейские иероглифы; эмодзи и пробелы нулевой ширины являются допустимыми значениями char в Rust. Скалярные значения Unicode находятся в диапазоне от
    U+0000
    до
    U+D7FF
    и от
    U+E000
    до
    U+10FFFF
    включительно. Однако
    «символ» на самом деле не является концепцией в Unicode, поэтому интуитивно может не совпадать с тем, что такое char в Rust. Мы подробно обсудим эту тему в разделе
    «Сохранение текста в кодировке UTF-8 со строками»
    в главе 8.
    fn main
    () { let t = true
    ; let f: bool
    = false
    ;
    // with explicit type annotation
    } fn main
    () { let c =
    'z'
    ; let z: char
    =
    'ℤ'
    ;
    // with explicit type annotation let heart_eyed_cat = '😻 ';
    }

    Сложные типы данных
    Сложные типы могут группировать несколько значений в один тип. В Rust есть два примитивных сложных (комбинированных) типа: кортежи и массивы.
    Кортежи
    Кортеж является общим способом совместной группировки нескольких значений различного типа в единый комбинированный тип. Кортежи имеют фиксированную длину: после объявления они не могут расти или уменьшаться в размере.
    Кортеж создаётся при помощи записи списка значений, перечисленных через запятую внутри круглых скобок. Каждая позиция в кортеже имеет тип. Типы различных значений в кортеже могут не быть одинаковыми. В примере мы добавили необязательные аннотации типов:
    Файл : src/main.rs
    К переменной с именем tup привязывается весь кортеж, потому что кортеж является единым комбинированным элементом. Чтобы получить отдельные значения из кортежа,
    можно использовать сопоставление с образцом для деструктурирования значений кортежа, как в примере:
    Файл : src/main.rs
    Программа создаёт кортеж, привязывает его к переменной tup
    . Затем в let используется шаблон для превращения tup в три отдельные переменные: x
    , y
    и z
    Такого рода операция называется деструктуризацией (destructuring), потому что она разбирает один кортеж на три части. В конце программа печатает значение y
    , которое равно
    6.4
    Мы также можем напрямую обращаться к элементу кортежа, используя точку (
    ), за которой следует индекс значения, к которому мы хотим получить доступ. Например:
    Файл : src/main.rs fn main
    () { let tup: (
    i32
    , f64
    , u8
    ) = (
    500
    ,
    6.4
    ,
    1
    );
    } fn main
    () { let tup = (
    500
    ,
    6.4
    ,
    1
    ); let
    (x, y, z) = tup; println!
    (
    "The value of y is: {y}"
    );
    }

    Эта программа создаёт кортеж x
    , а затем обращается к каждому элементу кортежа,
    используя соответствующие индексы. Как и в большинстве языков программирования,
    первый индекс в кортеже равен 0.
    Кортеж без каких-либо значений имеет специальное имя unit. Это значение и соответствующий ему тип записываются как
    ()
    и представляют собой пустое значение или пустой возвращаемый тип. Выражения неявно возвращают unit, если они не возвращают никакого другого значения.
    Массивы
    Другой способ получить набор из нескольких значений — это массив. В отличие от кортежа, каждый элемент массива должен иметь один и тот же тип. В отличие от массивов в некоторых других языках, массивы в Rust имеют фиксированную длину.
    Мы записываем значения в массиве в виде списка, разделённого запятыми, внутри квадратных скобок:
    Файл : src/main.rs
    Массивы полезны, когда вы хотите, чтобы ваши данные размещались в стеке, а не в куче
    (мы более подробно обсудим стек и кучу в главе 4
    ), или когда вы хотите, чтобы у вас всегда было фиксированное количество элементов. Однако массив не такой гибкий, как векторный тип. Вектор — это аналогичный тип коллекции, предоставляемый стандартной библиотекой, размер которого может увеличиваться или уменьшаться.
    Если вы не уверены, использовать массив или вектор, скорее всего, вам следует использовать вектор.
    Глава 8
    раскрывает векторы более подробно.
    Однако массивы более полезны, когда вы знаете, что количество элементов не нужно будет изменять. Например, если бы вы использовали названия месяцев в программе, вы,
    вероятно, использовали бы массив, а не вектор, потому что вы знаете, что он всегда будет содержать 12 элементов:
    fn main
    () { let x: (
    i32
    , f64
    , u8
    ) = (
    500
    ,
    6.4
    ,
    1
    ); let five_hundred = x.
    0
    ; let six_point_four = x.
    1
    ; let one = x.
    2
    ;
    } fn main
    () { let a = [
    1
    ,
    2
    ,
    3
    ,
    4
    ,
    5
    ];
    }

    Тип массива записывается следующим образом: в квадратных скобках обозначается тип элементов массива, а затем, через точку с запятой, количество элементов. Например:
    Здесь i32
    является типом каждого элемента массива. После точки с запятой указано число
    5
    , показывающее, что массив содержит 5 элементов.
    Вы также можете инициализировать массив, содержащий одно и то же значение для каждого элемента, указав это значение вместо типа. Следом за этим так же следует точка с запятой, а затем — длина массива в квадратных скобках, как показано здесь:
    Массив в переменной a
    будет включать
    5
    элементов, значение которых будет равно
    3
    Данная запись аналогична коду let a = [3, 3, 3, 3, 3];
    , но является более краткой.
    Доступ к элементам массива
    Массив — это единый фрагмент памяти известного фиксированного размера, который может быть размещён в стеке. Вы можете получить доступ к элементам массива с помощью индексации, например:
    Файл : src/main.rs
    В этом примере переменная с именем first получит значение
    1
    , потому что это значение с индексом
    [0]
    в массиве. Переменная с именем second получит значение
    2
    из индекса
    [1]
    в массиве.
    Некорректный доступ к элементу массива
    Давайте посмотрим, что произойдёт, если вы попытаетесь получить доступ к элементу массива, находящемуся за его пределами. Допустим, вы запускаете код, похожий на игру в угадывание из главы 2, чтобы получить индекс массива от пользователя:
    let months = [
    "January"
    ,
    "February"
    ,
    "March"
    ,
    "April"
    ,
    "May"
    ,
    "June"
    ,
    "July"
    ,
    "August"
    ,
    "September"
    ,
    "October"
    ,
    "November"
    ,
    "December"
    ]; let a: [
    i32
    ;
    5
    ] = [
    1
    ,
    2
    ,
    3
    ,
    4
    ,
    5
    ]; let a = [
    3
    ;
    5
    ]; fn main
    () { let a = [
    1
    ,
    2
    ,
    3
    ,
    4
    ,
    5
    ]; let first = a[
    0
    ]; let second = a[
    1
    ];
    }

    Файл : src/main.rs
    Этот код успешно компилируется. Если вы запустите его, используя команду cargo run
    , и введёте 0, 1, 2, 3 или 4, программа выведет соответствующее значение по этому индексу в массиве. Если вместо этого вы введёте число, указывающее на индекс за пределами массива, например 10, вы увидите следующий вывод:
    Программа привела к ошибке во время выполнения в момент использования недопустимого значения в операции индексирования. Программа завершилась с сообщением об ошибке и не выполнила println!
    . Когда вы пытаетесь получить доступ к элементу с помощью индексации, Rust сам проверит, что указанный вами индекс меньше длины массива. Если индекс больше или равен длине, Rust запаникует. Но в данном случае эта проверка должна выполняться и во время работы программы, потому что компилятор не может знать, какое значение введёт пользователь, когда код будет запущен.
    Это пример принципов безопасности памяти Rust в действии. Во многих низкоуровневых языках такая проверка не выполняется, и когда вы указываете неправильный индекс, доступ к памяти может быть некорректным. Rust защищает вас от такого рода ошибок, немедленно закрываясь вместо того, чтобы разрешать доступ к памяти и продолжать работу. В главе 9 подробнее обсуждается обработка ошибок в Rust и то, как вы можете написать читаемый, безопасный код, который не вызывает панику и не разрешает некорректный доступ к памяти.
    use std::io; fn main
    () { let a = [
    1
    ,
    2
    ,
    3
    ,
    4
    ,
    5
    ]; println!
    (
    "Please enter an array index."
    ); let mut index =
    String
    ::new(); io::stdin()
    .read_line(&
    mut index)
    .expect(
    "Failed to read line"
    ); let index: usize
    = index
    .trim()
    .parse()
    .expect(
    "Index entered was not a number"
    ); let element = a[index]; println!
    (
    "The value of the element at index {index} is: {element}"
    );
    } thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:19:19 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

    Функции
    Функции широко распространены в коде Rust. Вы уже познакомились с одной из самых важных функций в языке: функцией main
    , которая является точкой входа большинства программ. Вы также видели ключевое слово fn
    , позволяющее объявлять новые функции.
    Код Rust использует змеиный регистр (snake case) как основной стиль для имён функций и переменных, в котором все буквы строчные, а символ подчёркивания разделяет слова.
    Вот программа, содержащая пример определения функции:
    Имя файла: src/main.rs
    Для определения функции в Rust необходимо указать fn
    , за которым следует имя функции и набор круглых скобок. Фигурные скобки указывают компилятору, где начинается и заканчивается тело функции.
    Мы можем вызвать любую функцию, которую мы определили ранее, введя её имя и набор скобок следом. Поскольку в программе определена another_function
    , её можно вызвать из функции main
    . Обратите внимание, что another_function определена после
    функции main в исходном коде; мы могли бы определить её и раньше. Rust не важно, где вы определяете свои функции, главное, чтобы они были определены где-то в той области видимости, которую может видеть вызывающий их код.
    Создадим новый бинарный проект с названием functions для дальнейшего изучения функций. Поместите пример another_function в файл src/main.rs и запустите его. Вы должны увидеть следующий вывод:
    Строки выполняются в том порядке, в котором они расположены в функции main
    Сначала печатается сообщение "Hello, world!", а затем вызывается another_function
    ,
    которая также печатает сообщение.
    fn main
    () { println!
    (
    "Hello, world!"
    ); another_function();
    } fn another_function
    () { println!
    (
    "Another function."
    );
    }
    $
    cargo run
    Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.28s
    Running `target/debug/functions`
    Hello, world!
    Another function.

    Параметры функции
    Можно определить функции с параметрами, которые являются специальными переменными, входящими в сигнатуру функции. Когда функция имеет параметры, вы можете предоставить ей конкретные значения этих параметров. С технической точки зрения конкретные значения называются аргументами, но в неформальной беседе люди обычно используют слова параметр и аргумент как взаимозаменяемые применительно к переменным в определении функции или конкретным значениям, передаваемым при вызове функции.
    В этой версии another_function мы добавляем параметр:
    Имя файла: src/main.rs
    Попробуйте запустить эту программу. Должны получить следующий результат:
    Объявление another_function имеет один параметр с именем x
    . Тип x
    указан как i32
    Когда мы передаём
    5
    в another_function
    , println!
    макрос помещает
    5
    в пару фигурных скобок формата строки.
    Вы обязаны объявить тип каждого параметра сигнатуры функции. Это преднамеренное решение в дизайне Rust: требование аннотаций типов в определениях функций означает, что компилятору почти никогда не нужно, чтобы вы использовали их где-либо ещё в коде для уточнения, какой тип вы имеете в виду. Компилятор также может выдавать более полезные сообщения об ошибках, если он знает, какие типы ожидает функция.
    При определении нескольких параметров, разделяйте объявления параметров запятыми, как показано ниже:
    Имя файла: src/main.rs fn main
    () { another_function(
    5
    );
    } fn another_function
    (x: i32
    ) { println!
    (
    "The value of x is: {x}"
    );
    }
    $
    cargo run
    Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 1.21s
    Running `target/debug/functions`
    The value of x is: 5

    Этот пример создаёт функцию под именем print_labeled_measurement с двумя параметрами. Первый параметр называется value с типом i32
    . Второй называется unit_label и имеет тип char
    . Затем функция печатает текст, содержащий value и unit_label
    Попробуем запустить этот код. Замените текущую программу проекта functions в файле
    src/main.rs на предыдущий пример и запустите его с помощью cargo run
    :
    Поскольку мы вызвали функцию с
    5
    в качестве значения для value и 'h'
    в качестве значения для unit_label
    , вывод программы содержит эти значения.
    Операторы и выражения
    Тела функций состоят из ряда операторов, необязательно заканчивающихся выражением. До сих пор функции, которые мы рассматривали, не включали завершающее выражение, но вы видели выражение как часть оператора. Поскольку Rust является языком, основанным на выражениях, это важное различие необходимо понимать. В других языках таких различий нет, поэтому давайте рассмотрим, что такое операторы и выражения, и как их различия влияют на тела функций.
    Операторы - это инструкции, которые выполняют какое-либо действие и не возвращают значение. Выражения вычисляют результирующее значение. Давайте посмотрим на несколько примеров.
    Фактически мы уже использовали операторы и выражения. Создание переменной и присвоение ей значения с помощью let
    - это оператор. В листинге 3-1 let y = 6;
    это оператор.
    Имя файла: src/main.rs fn main
    () { print_labeled_measurement(
    5
    ,
    'h'
    );
    } fn print_labeled_measurement
    (value: i32
    , unit_label: char
    ) { println!
    (
    "The measurement is: {value}{unit_label}"
    );
    }
    $
    cargo run
    Compiling functions v0.1.0 (file:///projects/functions)
    Finished dev [unoptimized + debuginfo] target(s) in 0.31s
    Running `target/debug/functions`
    The measurement is: 5h fn main
    () { let y =
    6
    ;
    }

    Листинг 3-1: Объявление функции
    main
    , содержащей один оператор
    Определения функций также являются операторами. Весь предыдущий пример сам по себе является оператором.
    Поэтому нельзя присвоить значение оператора let другой переменной, как это сделано в следующем коде. Вы получите ошибку:
    Имя файла: src/main.rs
    Если вы запустите эту программу, то ошибка будет выглядеть так:
    Оператор let y = 6
    не возвращает значение, поэтому не с чем связать переменную x
    Это отличается от поведения в других языках, таких как C и Ruby, где операция fn main
    () { let x = (
    let y =
    6
    );
    }
    $
    cargo run
    Compiling functions v0.1.0 (file:///projects/functions) error: expected expression, found statement (`let`)
    -->
    src/main.rs:2:14
    |
    2 | let x = (let y = 6);
    | ^^^^^^^^^
    |
    = note: variable declaration using `let` is a statement error[E0658]: `let` expressions in this position are unstable
    -->
    src/main.rs:2:14
    |
    2 | let x = (let y = 6);
    | ^^^^^^^^^
    |
    = note: see issue #53667
    1   2   3   4   5   6   7   8   9   ...   62


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