ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 11.01.2024
Просмотров: 1143
Скачиваний: 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