Санкт-Петербург
«БХВ-Петербург»
2023
УДК 004.4
ББК 32.973.26-02
М15
Макнамара Т.
М15 Rust в действии: Пер. с англ. — СПб.: БХВ-Петербург, 2023. — 528 с.: ил.
ISBN 978-5-9775-1166-7
Книга о прикладных аспектах языка программирования Rust, описывающая
внутреннее устройство языка и сферы его использования. Rust рассматривается как
современное дополнение для С при программировании ядра ОС и при системном
программировании, а также как низкоуровневый скоростной язык, обеспечиваю-
щий максимальную производительность. Объяснены тонкости работы с процессо-
ром, многопоточное программирование, работа с памятью, а также взаимодействие
с Linux. Изложенный материал позволяет как писать современные приложения на
Rust с нуля, так и внедрять Rust в сложившуюся базу кода.
Книга ориентирована на специалистов по C, Linux, системному программи-
рованию и на всех, кто желает освоить Rust и сразу приступить к работе с ним.
УДК 004.4
ББК 32.973.26-02
Группа подготовки издания:
Руководитель проекта Олег Сивченко
Зав. редакцией Людмила Гауль
Перевод с английского Николая Вильчинского
Редактор Дарья Кустовская
Компьютерная верстка Натальи Смирновой
Оформление обложки Зои Канторович
Original English language edition published by Manning Publications.
Copyright (c) 2021 by Manning Publications.
Russian-language edition copyright (c) 2022 by BHV. All rights reserved.
Оригинальное издание на английском языке опубликовано Manning Publications.
© 2021 Manning Publications.
Издание на русском языке © 2022 ООО «БХВ». Все права защищены.
Подписано в печать 03.11.22.
Формат 701001/16. Печать офсетная. Усл. печ. л. 42,57.
Тираж 1500 экз. Заказ №
"БХВ-Петербург", 191036, Санкт-Петербург, Гончарная ул., 20.
Отпечатано с готового оригинал-макета
ООО "Принт-М", 142300, М.О., г. Чехов, ул. Полиграфистов, д. 1
ISBN 978-1-61729-455-6 (англ.) © Manning Publications, 2021
ISBN 978-5-9775-1166-7 (рус.) © Перевод на русский язык, оформление.
ООО "БХВ-Петербург", ООО "БХВ", 2022
Оглавление
Предисловие ................................................................................................................... 15
Благодарности ............................................................................................................... 17
О книге ............................................................................................................................ 19
Кому следует прочитать эту книгу........................................................................... 19
Организация книги: дорожная карта........................................................................ 19
О программном коде.................................................................................................. 21
Дискуссионный форум liveBook .............................................................................. 21
Другие онлайн-ресурсы............................................................................................. 22
Об авторе .................................................................................................................... 22
Об иллюстрации на обложке книги ......................................................................... 22
Глава 1. Введение в Rust.............................................................................................. 23
1.1. Где используется Rust?............................................................................................ 24
1.2. С какой целью была написана книга «Rust в действии» ...................................... 25
1.3. Вкус языка ................................................................................................................ 26
1.3.1. Хитрый путь к «Hello, world!»...................................................................... 27
1.3.2. Ваша первая программа на Rust ................................................................... 29
1.4. Загрузка исходного кода книги............................................................................... 30
1.5. На что похож Rust? .................................................................................................. 31
1.6. В чем заключается особенность Rust? ................................................................... 34
1.6.1. Цель создания Rust: безопасность................................................................ 36
1.6.2. Цель создания Rust: производительность.................................................... 41
1.6.3. Цель создания Rust: управляемость ............................................................. 43
1.7. Особые возможности Rust....................................................................................... 45
1.7.1. Достижение высокой производительности ................................................. 45
1.7.2. Многопоточное выполнение программ ....................................................... 46
1.7.3. Достижение эффективной работы с памятью ............................................. 46
6 Оглавление
1.8. Недостатки Rust........................................................................................................ 46
1.8.1. Циклические структуры данных .................................................................. 46
1.8.2. Время, затрачиваемое на компиляцию ........................................................ 46
1.8.3. Строгость ........................................................................................................ 47
1.8.4. Объем языка ................................................................................................... 47
1.8.5. Излишний ажиотаж ....................................................................................... 47
1.9. Примеры использования TLS-безопасности ......................................................... 47
1.9.1. Heartbleed ........................................................................................................ 48
1.9.2. Goto fail; .......................................................................................................... 48
1.10. Для чего Rust подходит лучше всего?.................................................................. 50
1.10.1. В утилитах командной строки .................................................................... 50
1.10.2. В обработке данных..................................................................................... 51
1.10.3. В расширяемых приложениях .................................................................... 51
1.10.4. В средах с ограниченными ресурсами ....................................................... 51
1.10.5. В серверных приложениях .......................................................................... 52
1.10.6. В приложениях для ПК................................................................................ 52
1.10.7. В автономном режиме ................................................................................. 52
1.10.8. В мобильных приложениях......................................................................... 53
1.10.9. В веб-режиме................................................................................................ 53
1.10.10. В системном программировании.............................................................. 53
1.11. Скрытая фишка Rust: его сообщество.................................................................. 54
1.12. Разговорник по Rust ............................................................................................... 54
Резюме.............................................................................................................................. 54
ЧАСТЬ I. ОСОБЕННОСТИ ЯЗЫКА RUST......................................................................... 57
Глава 2. Основы языка ................................................................................................ 59
2.1. Создание работоспособной программы................................................................. 60
2.1.1. Компиляция одиночных файлов с помощью утилиты rustc ...................... 60
2.1.2. Компиляция Rust-проектов с использованием cargo.................................. 61
2.2. Взгляд на синтаксис Rust......................................................................................... 62
2.2.1. Определение переменных и вызов функций ............................................... 63
2.3. Числа ......................................................................................................................... 64
2.3.1. Целые и десятичные (с плавающей точкой) числа ..................................... 65
2.3.2. Записи целых чисел с основанием 2, 8 и 16 ................................................ 66
2.3.3. Сравнение чисел ............................................................................................ 68
2.3.4. Рациональные, комплексные числа и другие числовые типы ................... 73
Оглавление 7
2.4. Управление ходом выполнения программы.......................................................... 76
2.4.1. For: основной механизм итераций................................................................ 76
2.4.2. Continue: пропуск оставшейся части текущей итерации ........................... 78
2.4.3. While: выполнение цикла, пока не изменится состояние условия ............ 78
2.4.4. Loop: основа для циклических конструкций Rust ...................................... 79
2.4.5. Break: прерывание цикла............................................................................... 80
2.4.6. If, if else и else: условное ветвление ............................................................. 81
2.4.7. Match: соответствие образцу с учетом типов.............................................. 82
2.5. Определение функций ............................................................................................. 84
2.6. Использование указателей ...................................................................................... 85
2.7. Проект: визуализация множества Мандельброта ................................................. 86
2.8. Расширенные определения функций...................................................................... 90
2.8.1. Явные аннотации времени жизни ................................................................ 90
2.8.2. Обобщенные функции................................................................................... 92
2.9. Создание grep-lite ..................................................................................................... 95
2.10. Создание списков с использованием массивов, слайсов и векторов ................ 99
2.10.1. Массивы........................................................................................................ 99
2.10.2. Слайсы ........................................................................................................ 101
2.10.3. Векторы....................................................................................................... 102
2.11. Включение стороннего кода ............................................................................... 104
2.11.2. Создание документации по сторонним контейнерам
в локальной среде .................................................................................................. 107
2.11.3. Управление имеющимся в Rust набором инструментальных
средств с помощью rustup ..................................................................................... 107
2.12. Поддержка аргументов командной строки........................................................ 108
2.13. Чтение данных из файлов.................................................................................... 110
2.14. Чтение из стандартного устройства ввода stdin ................................................ 113
Резюме............................................................................................................................ 114
Глава 3. Составные типы данных ........................................................................... 117
3.1. Использование простых функций для экспериментов с API ............................. 117
3.2. Моделирование файлов с помощью struct ........................................................... 120
3.3. Добавление методов к структуре struct путем использования
блока impl....................................................................................................................... 125
3.3.1. Упрощение создания объектов за счет реализации
метода new () .......................................................................................................... 126
3.4. Возвращение сообщений об ошибках .................................................................. 129
3.4.1. Изменение значения известной глобальной переменной ........................ 129
3.4.2. Использование возвращаемого типа Result............................................... 135
8 Оглавление
3.5. Определение и использование перечисления enum............................................ 138
3.5.1. Использование enum для управления внутренним состоянием .............. 141
3.6. Определение общего поведения с помощью типажей ....................................... 143
3.6.1. Создание типажа Read................................................................................. 143
3.6.2. Реализация std::fmt::Display для ваших собственных типов.................... 145
3.7. Выставление своих типов на всеобщее обозрение ............................................. 148
3.7.1. Protecting private data Защита личных данных .......................................... 148
3.8. Создание встроенной документации ваших проектов ....................................... 149
3.8.1. Использование rustdoc для визуализации документов,
касающихся одного исходного файла.................................................................. 151
3.8.2. Использование cargo для визуализации документов
для контейнера и его зависимостей ..................................................................... 151
Резюме............................................................................................................................ 153
Глава 4. Время жизни, владение и заимствование ............................................... 155
4.1. Реализация имитации наземной станции CubeSat .............................................. 156
4.1.1. Выявление первой проблемы, связанной со временем жизни................. 158
4.1.2. Особое поведение элементарных типов .................................................... 161
4.2. Справочник по рисункам, используемым в этой главе ...................................... 163
4.3. Кто такой владелец? Есть ли у него какие-либо обязанности? ......................... 164
4.4. Как происходит переход владения ....................................................................... 165
4.5. Решение проблем, связанных с владением.......................................................... 167
4.5.1. Если полное владение не требуется, используйте ссылки....................... 170
4.5.2. Сократите количество долгоживущих значений ...................................... 173
4.5.3. Продублируйте значение ............................................................................ 180
4.5.4. Заключите данные в специальные типы .................................................... 184
Резюме............................................................................................................................ 186
ЧАСТЬ II. ДЕМИСТИФИКАЦИЯ СИСТЕМНОГО ПРОГРАММИРОВАНИЯ ................... 189
Глава 5. Углубленное изучение данных ................................................................. 191
5.1. Комбинации битов и типы .................................................................................... 191
5.2. Жизнь целых чисел ................................................................................................ 194
5.2.1. Усвоение порядка следования байтов........................................................ 197
5.3. Представление десятичных чисел ........................................................................ 198
5.4. Числа с плавающей точкой ................................................................................... 199
5.4.1. Взгляд на f32 изнутри.................................................................................. 200
5.4.2. Выделение знакового бита.......................................................................... 201
Оглавление 9
5.4.3. Выделение экспоненты ............................................................................... 202
5.4.4. Выделение мантиссы ................................................................................... 203
5.4.5. Разбиение числа с плавающей точкой на составные части ..................... 205
5.5. Форматы чисел с фиксированной точкой ............................................................ 207
5.6. Генерация случайных вероятностей из случайных байтов................................ 213
5.7. Реализация центрального процессора (CPU), чтобы удостовериться,
что функции также являются данными....................................................................... 215
5.7.1. CPU RIA/1: сумматор .................................................................................. 215
5.7.2. Полный листинг кода для CPU RIA/1: сумматор ..................................... 220
5.7.3. CPU RIA/2: мультипликатор....................................................................... 222
5.7.4. CPU RIA/3: блок вызова.............................................................................. 226
5.7.5. CPU 4: добавление всего остального ......................................................... 233
Резюме............................................................................................................................ 233
Глава 6. Память........................................................................................................... 235
6.1. Указатели ................................................................................................................ 235
6.2. Исследование типов ссылок и указателей, имеющихся в Rust.......................... 238
6.2.1. Обычные указатели, используемые в Rust ................................................ 243
6.2.2. Экосистема указателей Rust........................................................................ 246
6.2.3. Строительные блоки интеллектуальных указателей ................................ 248
6.3. Предоставление программам памяти для размещения их данных.................... 249
6.3.1. Стек ............................................................................................................... 250
6.3.2. Куча ............................................................................................................... 252
6.3.3. Что такое динамическое распределение памяти? ..................................... 256
6.3.4. Анализ влияния, оказываемого динамическим выделением памяти...... 264
6.4. Виртуальная память ............................................................................................... 266
6.4.1. История вопроса........................................................................................... 266
6.4.2. Шаг 1. Сканирование процессом собственной памяти ............................ 267
6.4.3. Преобразование виртуальных адресов в физические............................... 271
6.4.4. Шаг 2. Работа с операционной системой для сканирования
адресного пространства......................................................................................... 274
6.4.5. Шаг 3. Чтение и запись в память процесса................................................ 277
Резюме............................................................................................................................ 277
Глава 7. Файлы и хранилища................................................................................... 279
7.1. Что такое формат файла? ...................................................................................... 279
7.2. Создание собственных форматов файлов для хранения данных....................... 281
7.2.1. Запись данных на диск с помощью serde и формата bincode................... 281
10 Оглавление
7.3. Реализация клона hexdump.................................................................................... 284
7.4. Файловые операции, проводимые в Rust ............................................................. 288
7.4.1. Открытие файла в Rust и управление его режимом доступности ........... 288
7.4.2. Безопасное взаимодействие с файловой системой с помощью
std::fs::Path .............................................................................................................. 289
7.5. Реализация хранилища «ключ-значение» с архитектурой,
структурированной по записям и доступной только для добавления ..................... 291
7.5.1. Модель «ключ-значение»............................................................................ 291
7.5.2. Представление actionkv v1: хранилище ключей и значений
в памяти с интерфейсом командной строки........................................................ 292
7.6. Actionkv v1: интерфейсный код............................................................................ 293
7.6.1. Настройка продукта условной компиляции .............................................. 296
7.7. Понимание сути actionkv: контейнер libactionkv ................................................ 298
7.7.1. Инициализация структуры ActionKV ........................................................ 298
7.7.2. Работа с отдельно взятой записью ............................................................. 302
7.7.3. Запись многобайтных двоичных данных на диск
в гарантированном порядке следования байтов ................................................. 304
7.7.4. Проверка ошибок ввода-вывода с помощью контрольных сумм ........... 306
7.7.5. Вставка в существующую базу данных новой пары
«ключ-значение».................................................................................................... 309
7.7.6. Полный код листинга для actionkv............................................................. 310
7.7.7. Работа с ключами и значениями с использованием
HashMap и BTreeMap ............................................................................................ 315
7.7.8. Создание HashMap и ее заполнение значениями...................................... 318
7.7.9. Извлечение значений из HashMap и BTreeMap ........................................ 319
7.7.10. Что выбрать: HashMap или BTreeMap? ................................................... 320
7.7.11. Добавление к actionkv v2.0 индекса базы данных .................................. 322
Резюме............................................................................................................................ 326
Глава 8. Работа в сети ................................................................................................ 327
8.1. Все о сетевой работе в семи абзацах .................................................................... 328
8.2. Создание HTTP GET-запроса с использованием reqwest................................... 330
8.3. Типажные объекты................................................................................................. 332
8.3.1. На что способны типажные объекты? ....................................................... 332
8.3.2. Что такое типажные объекты?.................................................................... 333
8.3.3. Создание небольшой ролевой игры: rpg-проект ....................................... 333
8.4. TCP .......................................................................................................................... 337
8.4.1. Что такое номер порта? ............................................................................... 339
8.4.2. Преобразование имени хоста в IP-адрес.................................................... 339
Оглавление 11
8.5. Способы обработки ошибок, наиболее удобные для помещения
в библиотеки.................................................................................................................. 347
8.5.1. Проблема: невозможность возвращения нескольких типов
ошибок .................................................................................................................... 347
8.5.2. Заключение в оболочку нижестоящих ошибок путем
определения нашего собственного типа ошибки................................................ 351
8.5.3. Фокусы с unwrap() и expect() ...................................................................... 358
8.6. MAC-адреса ............................................................................................................ 358
8.6.1. Создание MAC-адресов............................................................................... 360
8.7. Реализация конечных автоматов с помощью перечислений ............................. 362
8.8. Чистый TCP ............................................................................................................ 363
8.9. Создание виртуального сетевого устройства ...................................................... 363
8.10. «Чистый» HTTP.................................................................................................... 365
Резюме............................................................................................................................ 376
Глава 9. Время и хронометраж................................................................................. 377
9.1. Предыстория вопроса ............................................................................................ 378
9.2. Источники времени................................................................................................ 380
9.3. Определения ........................................................................................................... 380
9.4. Кодирование времени ............................................................................................ 382
9.4.1. Представление часовых поясов .................................................................. 383
9.5. clock v0.1.0: учим приложение сообщать о времени .......................................... 383
9.6. clock v0.1.1: форматирование меток времени в соответствии
с ISO 8601 и стандартами электронной почты........................................................... 384
9.6.1. Реструктуризация кода clock v0.1.0 с целью более широкой
архитектурной поддержки .................................................................................... 385
9.6.2. Форматирование времени ........................................................................... 386
9.6.3. Предоставление полноценного интерфейса командной строки.............. 387
9.6.4. clock v0.1.1: полный проект ........................................................................ 388
9.7. clock v0.1.2: установка времени............................................................................ 392
9.7.1. Общее поведение ......................................................................................... 392
9.7.2. Установка времени для операционных систем,
использующих libc................................................................................................. 392
9.7.3. Установка времени в MS Windows ............................................................ 395
9.7.4. clock v0.1.2: листинг полного кода............................................................. 397
9.8. Более совершенные способы обработки ошибок................................................ 401
9.9. clock v0.1.3: вычисление разницы показания часов с показанием
протокола сетевого времени — Network Time Protocol (NTP) ................................. 402
9.9.1. Отправка NTP-запросов и интерпретация ответов ................................... 402
9.9.2. Корректировка местного времени по ответу сервера............................... 405
12 Оглавление
9.9.3. Преобразования между представлениями о времени,
использующими различные степени точности и эпохи ..................................... 407
9.9.4. clock v0.1.3: листинг полной версии кода ................................................. 409
Резюме............................................................................................................................ 417
Глава 10. Процессы, потоки и контейнеры............................................................ 419
10.1. Безымянные функции .......................................................................................... 420
10.2. Порождение потоков ........................................................................................... 421
10.2.1. Введение в замыкания ............................................................................... 421
10.2.2. Порождение потока ................................................................................... 422
10.2.3. Эффект от порождения нескольких потоков........................................... 423
10.2.4. Эффект от порождения множества потоков............................................ 424
10.2.5. Воспроизведение результатов .................................................................. 426
10.2.6. Совместно используемые переменные .................................................... 430
10.3. Отличие замыканий от функций......................................................................... 433
10.4. Аватары, процедурно генерируемые из многопоточного парсера
и генератора кода .......................................................................................................... 434
10.4.1. Как запустить проект render-hex, и как выглядит
его предполагаемый вывод ................................................................................... 434
10.4.2. Обзор однопоточной версии render-hex................................................... 435
10.4.3. Порождение потока для каждой логической задачи .............................. 446
10.4.4. Использование пула потоков и очереди задач ........................................ 449
10.5. Конкурентные вычисления и виртуализация задач .......................................... 457
10.5.1. Потоки......................................................................................................... 460
10.5.2. Что такое контекстное переключение?.................................................... 460
10.5.3. Процессы .................................................................................................... 460
10.5.4. WebAssembly .............................................................................................. 461
10.5.5. Контейнеры ................................................................................................ 461
10.5.6. А зачем вообще использовать операционную систему? ........................ 461
Резюме............................................................................................................................ 462
Глава 11. Ядро операционной системы .................................................................. 463
11.1. Оперяющаяся операционная система (FledgeOS) ............................................. 463
11.1.1. Настройка среды разработки под создание ядра операционной
системы ................................................................................................................... 463
11.1.2. Проверка среды разработки ...................................................................... 465
11.2. FledgeOS-0: получение хоть чего-то работоспособного .................................. 466
11.2.1. Первая загрузка .......................................................................................... 466
11.2.2. Инструкции по компиляции...................................................................... 468
Оглавление 13
11.2.3. Листинги исходного кода.......................................................................... 469
11.2.4. Способы справиться с паникой ................................................................ 474
11.2.5. Вывод информации на экран с использованием
VGA-совместимого текстового режима .............................................................. 475
11.2.6 _start (): функция main () для FledgeOS..................................................... 477
11.3. fledgeos-1: избавление от цикла занятости ........................................................ 477
11.3.1. Экономия ресурсов за счет прямого взаимодействия
с центральным процессором................................................................................. 477
11.3.2. Исходный код fledgeos-1 ........................................................................... 478
11.4. fledgeos-2: самостоятельная обработка исключений ........................................ 479
11.4.1. Почти что правильная обработка исключений ....................................... 479
11.4.2. Исходный код fledgeos-2 ........................................................................... 480
11.5. fledgeos-3: текстовый вывод................................................................................ 481
11.5.1. Вывод на экран цветного текста............................................................... 482
11.5.2. Управление представлением перечислений в памяти ............................ 482
11.5.3. Зачем использовать перечисления?.......................................................... 483
11.5.4. Создание шрифта для вывода информации в буфер кадра VGA .......... 483
11.5.5. Вывод на экран........................................................................................... 484
11.5.6. Исходный код fledgeos-3 ........................................................................... 485
11.6. fledgeos-4: специализированная обработка паники .......................................... 487
11.6.1. Реализация обработчика паники, сообщающего пользователю
об ошибке. .............................................................................................................. 487
11.6.2. Повторная реализация panic() с использованием core::fmt::Write......... 487
11.6.3. Реализация core::fmt::Write ....................................................................... 488
11.6.4. Исходный код fledge-4............................................................................... 489
Резюме............................................................................................................................ 491
Глава 12. Сигналы, прерывания и исключения................................................... 493
12.1. Глоссарий.............................................................................................................. 493
12.1.1. Сравнение сигналов и прерываний .......................................................... 495
12.2. Влияние прерываний на приложения................................................................. 496
12.3. Программные прерывания .................................................................................. 498
12.4. Аппаратные прерывания ..................................................................................... 499
12.5. Обработка сигналов ............................................................................................. 499
12.5.1. Поведение по умолчанию ......................................................................... 499
12.5.2. Приостановка и возобновление работы программы............................... 500
12.5.3. Перечень всех сигналов, поддерживаемых операционной
системой.................................................................................................................. 503
14 Оглавление
12.6. Обработка сигналов с помощью настраиваемых действий ............................. 504
12.6.1. Применение в Rust глобальных переменных .......................................... 505
12.6.2. Использование глобальной переменной для указания на
инициирование завершения выполнения программы ........................................ 507
12.7. Отправка сигналов, определяемых в приложении............................................ 510
12.7.1. Общие сведения об указателях на функции и их синтаксисе................ 511
12.8. Игнорирование сигналов..................................................................................... 512
12.9. Завершение работы из глубокой вложенности в стеке вызовов...................... 514
12.9.1. Представление проекта sjlj........................................................................ 516
12.9.2. Настройка встроенных функций для их использования
в программе ............................................................................................................ 517
12.9.3. Приведение указателя к другому типу .................................................... 519
12.9.4. Компиляция кода проекта sjlj ................................................................... 520
12.9.5. Исходный код проекта sjlj......................................................................... 521
12.10. Заметка о применении этих методов на платформах,
не использующих сигналы. .......................................................................................... 524
12.11. Пересмотр исключений ..................................................................................... 524
Резюме............................................................................................................................ 525
Предисловие
Кто знает, стоит ли вообще читать техническую литературу. Она может быть доро-
гой, скучной и сомнительной по содержанию. Хуже того, велика вероятность, что с
ней вы так ничему и не научитесь. К счастью, эта книга написана автором, который
все это прекрасно понимает.
Главная цель книги — обучить вас программированию на языке Rust. В книге
представлены довольно крупные и способствующие обучению рабочие проекты.
По ходу изучения материала будут созданы база данных, эмулятор процессора, яд-
ро операционной системы и разработано несколько других интересных проектов.
Предстоит даже заняться процедуральным искусством. Каждый проект разработан
с целью изучения языка программирования Rust в удобном для вас темпе. Для тех,
кто еще не освоился в Rust-программировании, есть множество возможностей по
расширению проектов в любом выбранном направлении.
Но изучение языка программирования — это не только освоение его синтаксиса и
семантики. Это также вступление в сообщество. К сожалению, уже устоявшиеся
сообщества могут создавать для новичков незримые препятствия из-за совместно
приобретенных знаний, применения специальных терминов и уже наработанной
практики.
Для многих новичков Rust-программирования один из таких барьеров — концеп-
ция системного программирования. Редко у кого из программистов, переходящих
на Rust, имеется опыт в данной области. В качестве компенсации перед книгой по-
ставлена еще одна задача: научить вас системному программированию. А кроме
других тем, в двенадцати главах книги вы почерпнете сведения о памяти, цифровом
хронометраже и о работе драйверов устройств. Надеюсь, что после этого вы, став
участником Rust-сообщества, сможете почувствовать себя в нем вполне уютно.
И вы нам в нем нужны!
Наше общество зависит от применения программных средств, а критические дыры
в безопасности считаются вполне нормальным и, возможно, даже неизбежным яв-
лением. Rust показывает нам, что это уже не соответствует истине. А кроме того,
наши компьютеры забиты под завязку раздутыми энергоемкими приложениями.
Rust представляет собой жизнеспособную альтернативу, предназначенную для раз-
работки программного обеспечения, менее требовательного к имеющимся ограни-
ченным ресурсам.
Эта книга посвящена расширению возможностей. Ее конечная цель — убедить вас
в этом. Rust не предназначен для какой-то избранной группы экспертов. Он являет-
ся инструментом, доступным каждому. Читатели, проделавшие столь длинный путь
в своем обучающем путешествии, удостоятся моей похвалы, и я с удовольствием
сделаю для вас еще несколько шагов.
16 Предисловие
Благодарности
Спасибо Кэти за то, что удержала меня от свертывания проекта, и за то, что раз за
разом поднимала мне настроение, когда я падал духом. Спасибо также Флоренс и
Октавии за их обнимашки и улыбки, даже когда папа не мог с ними играть, потому
что он писал эту книгу.
Я в долгу перед столькими людьми, что мне кажется несправедливым перечислить
лишь немногих избранных. При работе над книгой я пользовался поддержкой мно-
гих участников Rust-сообщества. В ходе разработки материалов книги через live-
Book поступили тысячи читательских исправлений, вопросов и предложений.
И вклад каждого читателя помог мне усовершенствовать текст. Спасибо.
Особое чувство благодарности я испытываю к небольшому числу читателей,
со многими из которых мы стали друзьями. Это Ай Майга (Aï Maiga), Ана Хобден
(Ana Hobden), Эндрю Мередит (Andrew Meredith), Андрей Лесников (Andréy
Lesnikóv), Энди Гроув (Andy Grove), Артуро Ж. Перес (Arturo J. Pérez), Брюс Мит-
ченер (Bruce Mitchener), Сесиль Тонглет (Cecile Tonglet), Дэниел Карозоне (Daniel
Carosone), Эрик Ридж (Eric Ridge), Эстебан Кубер (Esteban Kuber), Флориан Гилчер
(Florian Gilcher), Ян Бэттерсби (Ian Battersby), Джейн Ласби (Jane Lusby), Хавьер
Виола (Javier Viola), Джонатан Тернер (Jonathan Turner), Лачезар Лечев (Lachezar
Lechev), Лучано Маммино (Luciano Mammino), Люк Джонс (Luke Jones), Натали
Блумфилд (Natalie Bloomfield), Олександр Каленюк (Oleksandr Kaleniuk), Оливия
Ифрим (Olivia Ifrim), Пол Фариа (Paul Faria), Пол Дж. Саймондс (Paul J. Symonds),
Филипп Гневош (Philipp Gniewosz), Род Элиас (Rod Elias), Стивен Оутс (Stephen
Oates), Стив Клабник (Steve Klabnik), Таннер Аллард (Tanner Allard), Томас Локни
(Thomas Lockney) и Уильям Браун (William Brown); для меня общение с вами на
протяжении последних четырех лет было особой привилегией.
Я выражаю сердечную благодарность рецензентам книги, среди которых Афшин
Мехрабани (Afshin Mehrabani), Аластер Смит (Alastair Smith), Брайс Дарлинг (Bryce
Darling), Кристоффер Финк (Christoffer Fink), Кристофер Хаупт (Christopher Haupt),
Дамиан Эстебан (Damian Esteban), Федерико Эрнандес (Federico Hernandez), Герт
Ван Лаэтхем (Geert Van Laethem), Джефф Лим (Jeff Lim), Йохан Лизеборн (Johan
Liseborn), Джош Коэн (Josh Cohen), Конарк Моди (Konark Modi), Марк Купер (Marc
Cooper), Морган Нельсон (Morgan Nelson), Рамнивас Ладдад (Ramnivas Laddad),
Риккардо Москетти (Riccardo Moschetti), Санкет Найк (Sanket Naik), Сумант Тамбе
(Sumant Tambe), Тим ван Дерзен (Tim van Deurzen), Том Барбер (Tom Barber), Уэйд
Джонсон (Wade Johnson), Уильям Браун (William Brown), Уильям Уиллер (William
Wheeler) и Ив Дорфсман (Yves Dorfsman). Все ваши комментарии были прочитаны.
А многие улучшения на последних этапах работы над книгой стали возможны бла-
годаря вашим содержательным отзывам.
18 Благодарности
Особой похвалы за их терпение, профессионализм и позитивный настрой заслужи-
вают два представителя команды Manning: Элеша Хайд (Elesha Hyde) и Фрэнсис
Буран (Frances Buran), которые умело провели книгу через длинную череду черно-
вых вариантов.
Также спасибо всем остальным редакторам разработки, в числе которых Берт Бейтс
(Bert Bates), Джерри Куч (Jerry Kuch), Михаэла Батинич (Mihaela Batinić), Ребекка
Райнхарт (Rebecca Rinehart), Рене ван ден Берг (René van den Berg) и Тим ван Дейр-
зен (Tim van Deurzen). Моя благодарность распространяется также на производст-
венных редакторов, в числе которых Бенджамин Берг (Benjamin Berg), Дейрдре
Хиам (Deirdre Hiam), Дженнифер Хоул (Jennifer Houle) и Пол Уэллс (Paul Wells).
В процессе выполнения принятой в Manning программы раннего доступа (MEAP-
программы) книга претерпела 16 выпусков, что было бы невозможно без поддерж-
ки большой группы специалистов. Спасибо вам, Александар Драгосавлевич (Alek-
sandar Dragosavljević), Ана Ромак (Ana Romac), Элеонора Гарднер (Eleonor
Gardner), Иван Мартинович (Ivan Martinović), Лори Вейдерт (Lori Weidert), Марко
Райкович (Marko Rajkovic), Матко Хрватин (Matko Hrvatin), Мехмед Пашич (Meh-
med Pasic), Мелисса Айс (Melissa Ice), Михаэла Батинич (Mihaela Batinic), Оуэн Ро-
бертс (Owen Roberts), Радмила Эрцеговац (Radmila Ercegovac) и Рейхана Маркано-
вич (Rejhana Markanovic).
Спасибо также маркетинговой команде в составе Бранко Латинчича (Branko Latin-
cic), Кэндис Гиллхулли (Candace Gillhoolley), Коди Танкерсли (Cody Tankersley),
Лукаса Вебера (Lucas Weber) и Степана Юрековича (Stjepan Jureković). Вы были
для меня великим источником поддержки.
Отзывчивость и польза чувствовались и от расширенного состава команды
Manning. Спасибо вам, Айра Дукжич (Aira Dučić), Эндрю Уолдрон (Andrew Wal-
dron), Барбара Мирецки (Barbara Mirecki), Бранко Латинчич (Branko Latincic), Бре-
кин Эли (Breckyn Ely), Кристофер Кауфманн (Christopher Kaufmann), Деннис Да-
линник (Dennis Dalinnik), Эрин Туи (Erin Twohey), Ян Хаф (Ian Hough), Иосип Ма-
рас (Josip Maras), Джулия Куинн (Julia Quinn), Лана Класик (Lana Klasic), Линда
Котлярская (Linda Kotlyarsky), Лори Кервальд (Lori Kehrwald) и Мелоди Долаб
(Melody Dolab) за помощь в работе над книгой. Спасибо и Майку Стивенсу (Mike
Stephens) за то, что он положил начало этому изменяющему всю мою жизнь про-
цессу. Он меня предупреждал, что будет сложно, и был абсолютно прав.
О книге
В первую очередь книга предназначена для людей, которые, возможно, изучали
бесплатные материалы по Rust в Интернете, а затем спросили себя: «А что же
дальше?». В книге содержатся десятки интересных примеров, поддающихся рас-
ширению при наличии соответствующего времени и творческого потенциала. Эти
примеры позволяют двенадцати главам книги охватить продуктивное подмножест-
во Rust и многие из наиболее важных сторонних библиотек экосистемы.
Основной упор в примерах кода делается на доступность для начинающих. В них
нет особых излишеств, применяемых в элегантных, идиоматических приемах языка
Rust. Читатели, неплохо разбирающиеся в программировании на Rust, могут не со-
гласиться с некоторыми принятыми в примерах стилевыми решениями. Но я наде-
юсь, что они потерпят их ради тех, кто еще только учится.
Эта книга не задумывалась в качестве исчерпывающего справочника. Некоторые
части языка и стандартной библиотеки были опущены. Как правило, здесь имеются
в виду узкоспециализированные составляющие, изучению которых потребовалось
бы уделить отдельное внимание. Вместо этого книга сосредоточена на предостав-
лении читателям достаточного объема базовых знаний и придания уверенности в
том, что, в случае необходимости, им будет по силам изучить опущенные здесь
специализированные области. Книга также уникальна в семействе книг, посвящен-
ных системному программированию, поскольку почти каждый приведенный в ней
пример работает в Microsoft Windows.
Кому следует прочитать эту книгу
Эта книга должна понравиться тем, кто интересуется языком Rust, кто учится на
практических примерах, или тем, кто напуган фактом принадлежности Rust к язы-
кам системного программирования. Наибольшую пользу от книги получат читате-
ли, уже имеющие опыт программирования, поскольку в ней будет изложен ряд
концепций, используемых при программировании компьютерного оборудования.
Организация книги: дорожная карта
Книга состоит из двух частей. В первой части представлен синтаксис языка Rust и
ряд его характерных особенностей. Во второй части те знания, что были получены
при изучении первой, применяются при разработке ряда проектов. В каждой главе
вводится одна-две новых концепций языка Rust.
20 О книге
Согласно вышесказанному, в первой части книги дается краткое введение в Rust:
Глава 1, «Введение в Rust», объясняет причины появления Rust и рассказывает
о том, как приступить к программированию на этом языке.
Глава 2, «Особенности языка Rust», предоставляет фундаментальные сведения
о синтаксисе языка. В качестве примеров используются визуализация множества
Мандельброта и создание клона команды grep.
Глава 3, «Составные типы данных», объясняет, как в Rust составляются типы
данных и какими средствами обработки ошибок располагает этот язык.
Глава 4, «Время жизни, владение и заимствование», рассматривает механизмы,
гарантирующие неизменную корректность доступа к данным.
Во второй части книги Rust применяется для введения в область системного про-
граммирования:
Глава 5, «Углубленное изучение данных», повествует о том, как информация
представлена в цифровых компьютерах, при этом особое внимание уделяется
приближению чисел. Примеры включают создание собственного числового
формата и эмулятора центрального процессора.
Глава 6, «Память», объясняет такие понятия, как ссылки, указатели, виртуальная
память, стек и куча. Примеры включают создание сканера памяти и реализацию
проекта процедурального искусства.
Глава 7, «Файлы и хранилища», объясняет процессы сохранения структур дан-
ных на устройствах хранения информации. Примеры включают создание клона
утилиты hex dump и разработку работоспособной базы данных.
Глава 8, «Работа в сети», объясняет способы, применяемые компьютерами для
обмена данными посредством многократной повторной реализации HTTP с из-
бавлением при каждом шаге от очередного уровня абстракции.
Глава 9, «Время и хронометраж», исследует процессы отслеживания времени в
цифровом компьютере. Примеры включают создание работоспособного NTP-
клиента.
Глава 10, «Процессы, потоки и контейнеры», объясняет, что такое процессы,
потоки и связанные с ними абстракции. Примеры включают создание приложе-
ния черепашьей графики и средства синтаксического анализа, работающего в
режиме параллельных вычислений.
Глава 11, «Ядро операционной системы», дает описание роли операционной
системы и способов начальной загрузки компьютеров. Примеры включают ком-
пиляцию своего собственного загрузчика и ядра операционной системы.
Глава 12, «Сигналы, прерывания и исключения», объясняет порядок связи
внешнего мира с центральным процессором и операционными системами.
Книга предназначена для последовательного чтения. Предполагается, что во всех
последующих главах используются знания, полученные в предыдущих. Но проекты
из каждой главы не связаны друг с другом. Поэтому по всем проектам, касающимся
интересующих вас тем, можно проходить в произвольном порядке.
О книге 21
О программном коде
Примеры кода, приведенные в этой книге, написаны на языке Rust в редакции 2018 го-
да и протестированы под управлением Windows и Ubuntu Linux. Никакого специ-
ального программного обеспечения, кроме рабочей установки Rust, не требуется.
Инструкции по установке изложены в главе 2.
Эта книга содержит множество примеров исходного кода как в пронумерованных
листингах, так и в виде обычного текста. В обоих случаях, чтобы исходный код от-
личался от обычного текста, он отформатирован таким вот шрифтом фиксированной ши-
рины. Иногда код также выделяется жирным шрифтом, чтобы отметить его фраг-
менты, изменившиеся по сравнению с предыдущими действиями, рассмотренными
в главе, например когда к существующей строке кода добавляется новая функция.
Во многих случаях первоначальный исходный код был переформатирован: чтобы
он уместился на книжной странице, в нем были переработаны отступы и добавле-
ны разрывы строк. Но иногда и этого было недостаточно, поэтому в листингах
попадаются маркеры продолжения строки (➥). Кроме того, при описании кода в
тексте комментарии, имевшиеся в листингах исходного кода, зачастую удаляют-
ся. Для выделения важных понятий многие листинги сопровождаются примеча-
ниями к коду.
Дискуссионный форум liveBook
Приобретение этой книги открывает свободный доступ к закрытому веб-форуму,
запущенному издательством Manning Publications, где можно оставлять отзывы
о книге, задавать вопросы технического плана и получать помощь от автора и от
других пользователей:
Чтобы попасть на форум, перейдите по адресу
https://livebook.manning.com/book/rust-in-action/welcome/v-16/.
Дополнительные сведения о форумах, запущенных издательством Manning, и
о правилах поведения на них можно получить по адресу
https://livebook.manning.com/#!/discussion.
Придерживаясь обязательств, принятых перед читателями, издательство Manning
обеспечивает место, где может состояться содержательный диалог между отдель-
ными читателями, а также между читателями и автором. При этом автор не обязан
общаться с каким-то определенным количеством участников форума, поскольку
его собственное участие остается добровольным (и неоплачиваемым). Чтобы не
потерять авторский интерес к форуму, предлагаем попробовать задать автору не-
сколько сложных вопросов! Пока книга находится в печати, форум и архивы пре-
дыдущих обсуждений будут доступны с веб-сайта издателя.
22 О книге
Другие онлайн-ресурсы
Тима Макнамару можно найти в социальной сети по метке @timClicks. Его основные
каналы — Twitter (https://twitter.com/timclicks), YouTube (https://youtube.com/c/timclicks) и
Twitch (https://twitch.tv/timclicks). Также можно свободно подключиться к его Discord-
серверу по адресу https://discord.gg/vZBX2bDa7W.
Об авторе
Тим Макнамара (Tim McNamara) учился программировать, чтобы помогать осуще-
ствлению проектов гуманитарной помощи по всему миру прямо из своего дома в
Новой Зеландии. За последние 15 лет Тим стал экспертом в области интеллекту-
ального анализа текста, обработки естественного языка и обработки данных. Он
организатор сообщества Rust Wellington и регулярно проводит учебные занятия по
программированию на языке Rust не только в формате живого общения, но и по
Интернету через Twitch и YouTube.
Об иллюстрации на обложке книги
Рисунок на обложке этой книги имел подпись «Le maitre de chausson» («Мастер та-
почек») или «The boxer» («Боксер»). Иллюстрация взята из собрания работ множе-
ства художников под редакцией Луи Кармера (Louis Curmer), опубликованного в
Париже в 1841 году. Коллекция называется «LesFrançais peints par eux-mêmes», что
переводится как «Французы на собственных рисунках». Каждая иллюстрация четко
прорисована и раскрашена вручную, а богатое разнообразие рисунков, представ-
ленных в коллекции, живо напоминает нам о том, насколько обособленными в
культурном отношении были регионы мира, города́, деревни и кварталы всего 200 лет
назад. Будучи разобщенными, люди говорили на разных диалектах и языках. На
улицах или в сельской местности просто по тому, как люди одеты, было легко
определить, где они живут и каково их ремесло или социальное положение.
С тех пор стиль одежды изменился, и разнообразие по регионам, столь богатое в то
время, исчезло. Сейчас трудно отличить друг от друга жителей разных континен-
тов, не говоря уже о разных городах или регионах. Возможно, мы обменяли куль-
турное разнообразие на более яркую личную жизнь, проходящую, конечно же,
в более разнообразном и быстро развивающемся техническом окружении.
В наше время, когда одну компьютерную книгу трудно отличить от другой, изда-
тельство Manning подчеркивает изобретательность и инициативу компьютерного
бизнеса за счет обложек своих книг, показывающих богатое разнообразие регио-
нальной жизни двухсотлетней давности, оживленное изображениями из таких, как
упомянутая здесь, коллекций.
1 Введение в Rust
В этой главе рассматриваются следующие вопросы:
Введение в свойства Rust и о том, для чего он создавался.
Разбор синтаксиса Rust.
Где следует, а где не следует применять Rust.
Создание вашей первой программы на Rust.
Сравнение Rust с объектно-ориентированными языками и системами програм-
мирования более широкого спектра.
Знакомьтесь с Rust — это язык программирования, расширяющий ваши возможно-
сти. Углубленное знакомство с ним откроет вам не только язык программирования,
обладающий невероятной скоростью и безопасностью, но и весьма приятный инст-
румент для программирования «на каждый день».
Приступив к программированию на Rust, вы вряд ли остановитесь. И эта книга,
«Rust в действии», поможет вашему становлению в качестве Rust-программиста.
Но она не станет учить программированию с нуля. Книга рассчитана на тех, кто
рассматривает Rust в качестве своего следующего языка, и на тех, кому нравится
освоение практических примеров. Вот наиболее яркие примеры, включенные в эту
книгу:
Визуализация множества Мандельброта.
Grep-клон.
Эмулятор центрального процессора.
Искусство генерации.
База данных.
Клиенты HTTP, NTP и hexdumps.
Интерпретатор языка LOGO.
Ядро операционной системы.
Из списка ясно, что книга позволит не только освоить Rust, но и познакомит с сис-
темным и низкоуровневым программированием. Изучая книгу «Rust в действии»,
вы сможете узнать о роли операционной системы, о том, как работает процессор,
как компьютеры отслеживают время, что такое указатели и что такое тип данных.
Вы получите представление о взаимодействии внутренних систем компьютера.
Выйдя за рамки изучения синтаксиса Rust, вы также поймете, для чего был создан
этот язык и для решения каких задач он предназначен.
24 Глава 1
1.1. Где используется Rust?
Ежегодно, с 2016 по 2020 год, в опросе разработчиков в Stack Overflow Rust удо-
стаивался звания «Самый любимый язык программирования». Видимо, именно по-
этому Rust был принят ведущими разработчиками компьютерных технологий:
Amazon Web Services (AWS) пользуется Rust с 2017 года для своих решений
по бессерверным вычислениям AWS Lambda и AWS Fargate. Благодаря
этому Rust развил свои успехи. Для выпуска своего сервиса Elastic Compute
Cloud (EC2) компания Amazon создала операционную систему Bottlerocket
OS и AWS Nitro System 1.
Компания Cloudflare применяет Rust при разработке множества своих сер-
висов, включая общедоступную DNS, средства бессерверных вычислений и
программы по проверке пакетов2.
Компания Dropbox применила Rust для перестройки своего внутреннего
хранилища данных, управляющего эксабайтами данных3.
Компания Google применяет Rust при разработке таких компонентов An-
droid, как модуль Bluetooth. Rust также используется для компонента
Chrome OS под названием crosvm и играет важную роль в разработке новой
операционной системы Google под названием Fuchsia4.
Компания Facebook использует Rust для повышения эффективности работы
своих веб-, мобильных и API-сервисов, а также для разработки компонен-
тов HHVM, виртуальной машины HipHop, используемой языком програм-
мирования Hack5.
Компания Microsoft пишет на Rust компоненты своей платформы Azure,
включая демон безопасности для своей службы Интернета вещей (IoT)6.
В Mozilla язык Rust используется для совершенствования веб-браузера Fire-
fox, база которого содержит 15 миллионов строк кода. Первые два проекта
Mozilla Rust-в-Firefox — анализатор метаданных MP4 и система кодирова-
ния-декодирования текста, позволили повысить общую производительность
и стабильность работы браузера.
Входящая в GitHub компания npm Inc. использует Rust, чтобы справиться с
«более чем 1,3 миллиардов загрузок пакетов в день»7.
1 См. «How our AWS Rust team will contribute to Rust’s future successes», http://mng.bz/BR4J.
2 См. «Rust at Cloudflare», https://news.ycombinator.com/item?id=17077358.
3 См. «The Epic Story of Dropbox’s Exodus From the Amazon Cloud Empire», http://mng.bz/d45Q.
4 См. «Google joins the Rust Foundation», http://mng.bz/ryOX.
5 См. «HHVM 4.20.0 and 4.20.1», https://hhvm.com/blog/2019/08/27/hhvm-4.20.0.html.
6 См. https://github.com/Azure/iotedge/tree/master/edgelet.
7 См. «Rust Case Study: Community makes Rust an easy choice for npm», http://mng.bz/xm9B.
Введение в Rust 25
Компания Oracle для устранения проблем с реализацией ссылок в Go разра-
ботала с применением Rust контейнерную среду выполнения8.
Компания Samsung через свою дочернюю компанию SmartThings использу-
ет Rust в своем подразделении по разработке серверной части прошивки для
сервиса Интернета вещей (IoT).
Производительности Rust также вполне хватает для развертывания динамично раз-
вивающихся стартапов. Вот несколько примеров:
Sourcegraph использует Rust для подсветки синтаксиса на всех своих языках9.
Figma использует Rust в самых важных для производительности компонен-
тах своего многопользовательского сервера10.
Parity использует Rust для разработки клиентской части блокчейна
Ethereum11.
1.2. С какой целью была написана книга
«Rust в действии»
Что побудило меня написать «Rust в действии»? Как только были преодолены на-
чальные трудности, дело стало налаживаться. Переговоры 2017 года, показанные
ниже, сродни хорошему анекдоту. В них один из специалистов команды Google
Chrome OS рассуждает, чего им стоило ввести язык в проект12:
indy on Sept 27, 2017
Разве Rust официально разрешен в Google?
zaxcellent on Sept 27, 2017
Вопрос по адресу: Rust не имеет официального разрешения в Google, но им все же
пользуется целый ряд сотрудников. Склонить моих сотрудников к применению Rust в
конкретном компоненте удалось после того, как я убедил их, что никакой другой язык не
подойдет для решения поставленной задачи лучше Rust, в чем сам я нисколько не
сомневался.
Стоит отметить, что удачно встроить Rust в среду разработки Chrome OS стоило немалого
труда. Неоценимую помощь в этом оказали разработчики Rust, которые давали
исчерпывающие ответы на все мои вопросы.
ekidd on Sept 27, 2017
> Склонить моих сотрудников к применению Rust в конкретном
> компоненте удалось после того, как я убедил их, что никакой
> другой язык не будет лучше его для решения данной задачи,
> в чем сам я нисколько не сомневался.
8 См. «Building a Container Runtime in Rust», http://mng.bz/d40Q.
9 См. «HTTP code syntax highlighting server written in Rust»,
https://github.com/sourcegraph/syntect_server.
10 См. «Rust in Production at Figma», https://www.figma.com/blog/rust-in-production-at-figma/.
11 См. «The fast, light, and robust EVM and WASM client», https://github.com/paritytech/parity-ethereum.
12 См. «Chrome OS KVM — A component written in Rust»,
https://news.ycombinator.com/item?id=15346557.
26 Глава 1
У меня похожий случай был с одним из моих проектов — с декодером субтитров vobsub,
обеспечивавшим парсинг сложных бинарных данных, который я планировал со временем
запустить в виде веб-сервиса. Естественно, я хотел убедиться в отсутствии слабых мест
в моем коде.
Код был написан на Rust, а затем я воспользовался командой 'cargo fuzz' для прогона
программы и выявления недочетов. После прохода миллиарда (!) fuzz-итераций я обнаружил
5 дефектов (см. раздел 'vobsub' в trophy case for a list по адресу
https://github.com/rust-fuzz/trophy-case).
К счастью, ни _один_ из этих дефектов не мог вылиться в реальный эксплойт. Для каждого
из них Rust успешно выявлял проблему и переводил ее в состояние управляемой паники.
(При практическом применении это привело бы к полному перезапуску веб-сервера.)
В результате я пришел к выводу: если мне понадобится язык, во-первых, без сборки
мусора, но, во-вторых, которому я смогу доверять в ситуации, требующей особых мер
безопасности, то Rust — прекрасный выбор. Возможность статической компоновки двоичных
файлов (как с Go) — большой плюс.
Manishearth on Sept 27, 2017
> К счастью, ни _один_ из этих дефектов не мог обернуться
> лазейкой для хакера. Для каждого из них Rust успешно выявлял
> проблему и переводил ее в управляемый сигнал тревоги.
Если кому-то интересно, то у нас также есть некоторый опыт автоматического
тестирования безопасности (фаззинга)кода в firefox. Фаззинг позволяет выявить массу
тревожных моментов (и предпосылок для отладки или для «безопасного» переполнения).
Однажды им был выявлен дефект в аналогичном коде Gecko, остававшийся незамеченным
около десяти лет.
Из данного фрагмента можно понять, что признание языка шло в восходящем на-
правлении усилиями специалистов, стремящихся преодолеть технические трудно-
сти в относительно небольших проектах. Затем опыт, накапливаемый за счет слу-
чаев успешного применения нового языка, использовался в качестве обоснования
более амбициозных работ.
За период с конца 2017 года Rust продолжал совершенствоваться и укреплять свои
позиции. Он стал общепринятой составляющей технологического ландшафта
Google, и теперь уже является официально допущенным языком в операционных
системах Android и Fuchsia.
1.3. Вкус языка
Этот раздел позволит вам немного распробовать работу с Rust. В нем будет показан
порядок использования компилятора с переходом к созданию простой программы.
А в следующих главах будут рассмотрены полноценные проекты.
ПРИМЕЧАНИЕ
Для установки Rust нужно воспользоваться официальными установщиками, предос-
тавленными по адресу https://rustup.rs/.
Введение в Rust 27
1.3.1. Хитрый путь к «Hello, world!»
Первым делом, осваивая новый язык, большинство программистов учатся выводить
на консоль приветствие «Hello, world!». Вы не станете исключением, но сделаете
это особым образом. Чтобы убедиться, что все находится в рабочем состоянии,
нужно будет выявить досадные синтаксические ошибки.
Если работа ведется под Windows, откройте командную строку Rust, доступную
после установки Rust в меню Пуск, и запустите на выполнение следующую команду:
C:\> cd %TMP%
Если вы работаете под Linux или macOS, откройте окно Terminal. Запустите в нем
следующую команду:
$ cd $TMP
Далее команды для всех операционных систем будут одинаковыми. При правиль-
ной установке Rust следующие три команды позволят отобразить на экране привет-
ствие «Hello, world!» (а также множество других выходных данных):
$ cargo new hello
$ cd hello
$ cargo run
Посмотрим, как выглядит весь сеанс при запуске cmd.exe под MS Windows:
C:\> cd %TMP%
C:\Users\Tim\AppData\Local\Temp\> cargo new hello
Created binary (application) `hello` project
C:\Users\Tim\AppData\Local\Temp\> cd hello
C:\Users\Tim\AppData\Local\Temp\hello\> cargo run
Compiling hello v0.1.0 (file:/ / /
C:/Users/Tim/AppData/Local/Temp/hello)
Finished dev [unoptimized + debuginfo] target(s) in 0.32s
Running `target\debug\hello.exe`
Hello, world!
А при запуске под Linux или macOS увидим в консоли следующее:
$ cd $TMP
$ cargo new hello
Created binary (application) `hello` package
$ cd hello
$ cargo run
Compiling hello v0.1.0 (/tmp/hello)
Finished dev [unoptimized + debuginfo] target(s) in 0.26s
Running `target/debug/hello`
Hello, world!
Если все получилось, вас можно поздравить! Только что запущен ваш первый Rust-
код без всякого программирования на Rust. Посмотрим, как это получилось.
28 Глава 1
Имеющийся в Rust инструмент под названием cargo предоставляет как систему
сборки, так и диспетчер пакетов. То есть, cargo знает, как превратить ваш Rust-код
в исполняемые двоичные файлы, а также может управлять процессом загрузки и
компиляции проектных зависимостей.
cargo new создает для вас проект, который построен по стандартному шаблону.
Команда tree может показать исходную структуру проекта и файлы, созданные
после ввода команды cargo new:
$ tree hello
hello
├── Cargo.toml
└── src
└── main.rs
1 directory, 2 files
Аналогичную структуру имеют все Rust-проекты, созданные с помощью cargo.
В основном каталоге имеется файл Cargo.toml, содержащий описание метаданных
проекта, таких как имя проекта, его версия и его зависимости. Исходный код попа-
дает в каталог src. Для файлов исходного кода языка Rust используется расширение
.rs. Для просмотра файлов, созданных командой cargo new, используется команда
tree.
Следующей выполненной командой была cargo run. Понять эту командную строку
гораздо проще, но cargo фактически проводит куда более серьезную работу, чем
можно было бы предположить. Вы требуете от cargo запустить проект. Если при
вызове команды запускать было нечего, cargo принимает от вашего имени решение
о компиляции кода в режиме отладки, чтобы предоставить вам максимальный объ-
ем информации об ошибках. Оказывается, в файле src/main.rs всегда содержится за-
готовка «Hello, world!». В результате компиляции получился файл по имени hello
(или hello.exe). Этот файл был выполнен, и результат был выведен на экран.
Выполнение команды cargo run привело также к добавлению к проекту новых фай-
лов. Теперь у нас в основном каталоге проекта есть файл Cargo.lock и каталог target/.
Оба они управляются инструментальным средством cargo. Поскольку это артефак-
ты процесса компиляции, изучать их не нужно. В Cargo.lock указываются конкрет-
ные номера версий всех зависимостей, чтобы будущие сборки составлялись точно
также, как и эта, пока содержимое Cargo.toml не изменится.
Повторный запуск команды tree открывает новую структуру, созданную вызовом
cargo run для компиляции проекта hello:
$ tree --dirsfirst hello
hello
├── src
│ └── main.rs
├── target
│ └── debug
│ ├── build
│ ├── deps
│ ├── examples
Введение в Rust 29
│ ├── native
│ └── hello
├── Cargo.lock
└── Cargo.toml
Если все именно так и получилось, значит, вы молодец! После освоения хитрого
способа получения «Hello, World!» давайте добьемся того же результата более
длинным путем.
1.3.2. Ваша первая программа на Rust
Для нашей первой программы нужно написать код, выводящий на экран следую-
щий текст на нескольких языках:
Hello, world!
Grüß Gott!
ハロー・ワールド
Первая строчка наверняка вам уже знакома. А две другие здесь, чтобы подчеркнуть
ряд свойств, присущих языку Rust: легкую итерацию и встроенную поддержку
Unicode. Для создания этой программы мы, как и раньше, воспользуемся cargo.
Выполните следующие действия:
1. Откройте командную строку консоли.
2. Запустите команду cd %TMP% под MS Windows или же команду cd $TMP под дру-
гой ОС.
3. Запустите для создания нового проекта команду cargo new hello2.
4. Запустите команду cd hello2 для перехода в корневой каталог проекта.
5. Откройте в текстовом редакторе файл src/main.rs.
6. Замените содержимое этого файла текстом, показанным в листинге 1.1.
Код следующего листинга имеется в хранилище исходного кода. Откройте файл
ch1/ch1hello2/src/hello2.rs.
Листинг 1.1. «Hello World!» на трех языках (1)
(2)
1 fn greet_world() { (3)
2 println!("Hello, world!"); (4)
3 let southern_germany = "Grüß Gott!"; (5)
4 let japan = "ハロー・ワールド"; (6)
5 let regions = [southern_germany, japan];
6 for region in regions.iter() {
7 println!("{}", ®ion);
8}
9}
10
11 fn main() {
30 greet_world(); Глава 1
(7)
12
13 }
(1) Восклицательный знак свидетельствует об использовании макроса, с чем мы вскоре
разберемся.
(2) Для операции присваивания в Rust, которую правильнее было бы назвать привязкой
переменной, используется ключевое слово let.
(3) Поддержка Unicode предоставляется изначально самим языком.
(4) Для литералов массива используются квадратные скобки.
(5) Для возврата итератора метод iter() может присутствовать во многих типах.
(6) Амперсанд «заимствует» region так, чтобы доступ предоставлялся только для чтения.
(7) Вызов функции. Обратите внимание на круглые скобки, следующие за именем функции.
Теперь, когда у src/main.rs новое содержимое, запустите из каталога hello2/ команду
cargo run. После ряда выходных данных, сгенерированных самим cargo, вы увиди-
те появление трех приветствий:
$ cargo run
Compiling hello2 v0.1.0 (/path/to/ch1/ch1-hello2)
Finished dev [unoptimized + debuginfo] target(s) in 0.95s
Running `target/debug/hello2`
Hello, world!
Grüß Gott!
ハロー・ワールド
Давайте уделим пару минут разбору ряда интересных моментов в коде Rust, пока-
занном в листинге 1.2.
Первым делом можно было бы заметить, что строки в Rust могут содержать весьма
широкий диапазон символов. Строки гарантированно получат кодировку UTF-8.
Значит, вам будет относительно несложно воспользоваться не только английским
языком.
Единственным символом, который может показаться неуместным, будет восклица-
тельный знак после println. Привыкшим к языку Ruby он покажется признаком
операции деструкции. В Rust он сигнализирует об использовании макроса. Пока
макросы можно считать проcто какими-то необычными функциями. Они позволя-
ют избежать применения шаблонного кода. В данном случае применяется макрос
println!, выполняющий свои внутренние операции определения типов, что позво-
ляет выводить на экран произвольные типы данных.
1.4. Загрузка исходного кода книги
Чтобы следовать примерам, приводимым в книге, вам может понадобиться доступ
к исходному коду листингов. Чтобы вам было удобнее, исходный код каждого
примера можно получить из двух источников:
https://manning.com/books/rust-in-action.
https://github.com/rust-in-action/code.
Введение в Rust 31
1.5. На что похож Rust?
Rust — язык программирования, позволяющий программистам Haskell и Java ла-
дить друг с другом. Rust можно сравнить с высокоуровневыми, весьма выразитель-
ными динамичными языками вроде Haskell и Java, но при этом он способен дости-
гать низкоуровневой, чуть ли не чисто аппаратной производительности.
В разделе 1.3 уже рассматривалось несколько примеров «Hello, world!», а чтобы
получить лучшее представление о свойствах Rust, давайте займемся чем-то по-
сложнее. В листинге 1.2 дается краткий обзор того, на что способен Rust при обыч-
ной обработке текста. Исходный код листинга находится в файле ch1/ch1-
penguins/src/main.rs. Обратите внимание на следующие моменты:
Общие механизмы управления ходом выполнения программы — в их числе
циклы for и ключевое слово continue.
Синтаксис методов — хотя Rust и не относится к объектно-ориентирован-
ным языкам, поскольку не поддерживает наследование, у него все же при-
сутствует данная особенность.
Программирование функций высшего порядка — функции могут как при-
нимать, так и возвращать функции. Например, строка 19 (.map(|field|
field.trim())) включает замкнутое выражение, известное также как безымян-
ная функция или лямбда-функция.
Сигнатуры типов — они встречаются редко, но все же иногда нужны в ка-
честве подсказки компилятору (посмотрите, к примеру, на строку 27, начи-
нающуюся с if let Ok(length)).
Условная компиляция — имеющиеся в листинге строки 21–24 (if cfg!(…);)
не включаются в сборки конечных версий программы.
Подразумеваемое возвращение — Rust предоставляет ключевое слово return,
но оно обычно опускается. Rust — язык, основанный на выражениях.
Листинг 1.2. Пример Rust-кода, демонстрирующего ряд простых приемов обработки CSV-данных
1 fn main() { (1)
2 let penguin_data = "\ (2)
3 common name,length (cm)
4 Little penguin,33 (3)
5 Yellow-eyed penguin,65
6 Fiordland penguin,60
7 Invalid,data
8 ";
9
10 let records = penguin_data.lines();
11
12 for (i, record) in records.enumerate() {
13 if i == 0 || record.trim().len() == 0 {
32 continue; Глава 1
}
14 (4)
15 let fields: Vec<_> = record (5)
16 .split(',') (6)
17 .map(|field| field.trim()) (7)
18 .collect(); (8)
19 if cfg!(debug_assertions) {
20 eprintln!("debug: {:?} -> {:?}", (9)
21 record, fields);
22 (10)
23 } (11)
24 let name = fields[0];
25 if let Ok(length) = fields[1].parse::<f32>() {
26
27 println!("{}, {}cm", name, length);
28 }
29 }
30
31 }
(1) Исполняемым проектам требуется функция main()
(2) Отключение завершающего символа новой строки
(3) Пропуск строки заголовка и строк, состоящих из одних пробелов
(4) Начало со строки текста
(5) Разбиение записи на поля
(6) Обрезка пробелов в каждом поле
(7) Сборка набора полей
(8) cfg! проверяет конфигурацию в процессе компиляции.
(9) eprintln! выводит данные на стандартное устройство сообщений об ошибках (stderr)
(10) Попытка выполнения парсинга поля в виде числа с плавающей точкой
(11) println! помещает данные на стандартное устройство вывода (stdout).
Кому-то из читателей листинг 1.2 может показаться странным, особенно если им
еще не приходилось сталкиваться с кодом на Rust. Перед тем, как продолжить, сле-
дует кое-что пояснить:
В строке 17 переменная fields помечена типом Vec<_>. Vec — сокращение
от _vector_, типа коллекции, способного динамически расширяться. Знак
подчеркивания (_) предписывает Rust вывести тип элементов.
В строках 22 и 28 Rust получает предписание по выводу информации на
консоль. Макрос println! выводит свои аргументы на стандартное устрой-
ство вывода (stdout), а макрос eprintln! делает то же самое на стандартное
устройство для сообщения об ошибках (stderr).
Макросы похожи на функции, но вместо возвращения данных они возвра-
щают код. Макросы часто используются для упрощения общеупотреби-
Введение в Rust 33
тельных шаблонов. Для управления своим выводом макросы eprintln! и
println! используют в качестве первого аргумента строковый литерал со
встроенным миниязыком. Поле заполнения {} заставляет Rust воспользо-
ваться методом представления значения в виде строки, который определил
программист, а не представлением по умолчанию, доступным при указании
поля заполнителя {:?}.
В строке 27 содержится ряд новых элементов. if let Ok(length) =
fields[1].parse::<f32>() читается как «попытаться разобрать fields[1]
в виде 32-разрядного числа с плавающей точкой, и в случае успеха присво-
ить число переменной length».
Конструкция if let — краткий метод обработки данных, предоставляю-
щий также локальную переменную, которой присваиваются эти данные.
Метод parse() возвращает Ok(T) (где T означает любой тип), если ему уда-
ется провести разбор строки; в противном случае он возвращает Err(E) (где
E означает тип ошибки). Применение if let Ok(T) позволяет пропустить
любые случаи ошибок, подобные той, что встречается при обработке строки
Invalid,data.
Когда Rust не способен вывести тип из окружающего контекста, он запра-
шивает конкретное указание. В вызов parse() включается встроенная анно-
тация типа в виде parse::<f32>().
Преобразование исходного кода в исполняемый файл называется компиляцией.
Чтобы скомпилировать Rust-код, нужно установить компилятор Rust и запустить
его в отношении исходного кода. Для компиляции кода листинга 1.2 выполните
следующее:
1. Откройте командную строку консоли (например, cmd.exe, PowerShell, Terminal
или Alacritty).
2. Перейдите в каталог ch1/ch1-penguins (но не в ch1/ch1-penguins/src) исходного кода,
загруженного вами в разделе 1.4.
3. Запустите на выполнение команду cargo run. Выведенная этой командой ин-
формация показана в следующем фрагменте кода:
$ cargo run
Compiling ch1-penguins v0.1.0 (../code/ch1/ch1-penguins)
Finished dev [unoptimized + debuginfo] target(s) in 0.40s
Running `target/debug/ch1-penguins`
dbg: " Little penguin,33" -> ["Little penguin", "33"]
Little penguin, 33cm
dbg: " Yellow-eyed penguin,65" -> ["Yellow-eyed penguin", "65"]
Yellow-eyed penguin, 65cm
dbg: " Fiordland penguin,60" -> ["Fiordland penguin", "60"]
Fiordland penguin, 60cm
dbg: " Invalid,data" -> ["Invalid", "data"]
34 Глава 1
Наверное, вы заметили какие-то непонятные строки, начинающиеся с метки dbg:?
Их можно убрать, компилируя выходную сборку с имеющимся в cargo флагом --
release. Эта функция условной компиляции обеспечивается блоком
cfg!(debug_assertions) { … } в строках 22–24 листинга 1.2. Сборки конечных
версий выполняются намного быстрее, но компилируются гораздо дольше:
$ cargo run --release
Compiling ch1-penguins v0.1.0 (.../code/ch1/ch1-penguins)
Finished release [optimized] target(s) in 0.34s
Running `target/release/ch1-penguins`
Little penguin, 33cm
Yellow-eyed penguin, 65cm
Fiordland penguin, 60cm
Выводимую информацию можно сделать еще короче, добавив к команде cargo
флаг -q, являющийся сокращением слова quiet (молчание). Что из этого выйдет,
показано в следующем фрагменте кода:
$ cargo run -q --release
Little penguin, 33cm
Yellow-eyed penguin, 65cm
Fiordland penguin, 60cm
Листинги 1.1 и 1.2 были выбраны, чтобы собрать в примерах как можно больше
характерных для Rust и простых в понимании функций. Надеюсь, с их помощью
было показано, что программы на Rust воспринимаются написанными на языке вы-
сокого уровня, но при этом отличаются высокой производительностью, присущей
программам, написанным на языках низкого уровня. Давайте отвлечемся на время
от конкретных свойств этого языка и рассмотрим идеи, положенные в его основу,
а также посмотрим, как он вписывается в экосистему языков программирования.
1.6. В чем заключается особенность Rust?
Отличительная особенность Rust как языка программирования — возможность
предотвращения недопустимого доступа к данным в ходе компиляции. Исследова-
тельские проекты Microsoft Security Response Center и проект браузера Chromium
приписывают проблемам недопустимого доступа к данным примерно 70% серьез-
ных ошибок систем безопасности13. В Rust данный класс ошибок полностью ис-
ключен. Этим гарантируется, что ваша программа будет безопасной для памяти без
каких-либо издержек времени выполнения.
Такой же уровень безопасности могут предоставить и другие языки, но это повле-
чет за собой дополнительные проверки, осуществляемые в ходе выполнения про-
граммы и замедляющие ее работу. В Rust удалось разорвать этот порочный круг,
создав для него собственное пространство, показанное на рис. 1.1.
13 Дополнительную информацию можно найти в статье «We need a safer systems programming lan-
guage», http://mng.bz/VdN5, и в статье «Memory safety», http://mng.bz/xm7B for more information.
Введение в Rust 35
Профессиональное сообщество Rust славится стремлением учитывать в процессе
принятия решений все самые ценные идеи. Этот дух всеобщей причастности витает
в сообществе повсеместно. Открытые сообщения всецело приветствуются. Весь
обмен информацией внутри Rust-сообщества регулируется принятыми в нем этиче-
скими нормами. Даже сообщения об ошибках, выдаваемые компилятором Rust, на
удивление содержательны.
Рис. 1.1. Rust обеспечивает как безопасность, так и возможность управления.
Другим языкам приходится балансировать между этих двух показателей.
До конца 2018 года на главной странице Rust посетителей встречало сообщение:
«Rust — язык системного программирования, работающий поразительно быстро,
не допускающий ошибок сегментации и гарантирующий безопасность потоков».
Затем сообщество изменило эту формулировку, сосредоточившись на интересах
своих действующих и потенциальных пользователей (см. табл. 1.1).
Таблица 1.1. Времена меняются, и слоган Rust меняется вместе с ними. По мере того,
как укреплялись позиции языка Rust, в нем все активнее проступала такая идея:
пусть этот язык способствует устремлениям тех программистов,
которые ставят перед собой самые амбициозные цели
До конца 2018 года Затем
«Rust — язык системного программирования, работаю- «Язык, позволяющий каждому
щий невероятно быстро, не допускающий ошибок сег- создавать надежное и эффектив-
ментации и гарантирующий безопасность потоков». ное программное обеспечение».
Rust позиционируется как язык системного программирования, которое, как прави-
ло, рассматривается в качестве особой ветви программирования, являющейся уде-
лом узкого круга специалистов. Но многие Rust-программисты поняли, что его
можно применять ко многим другим областям. Безопасность, производительность и
36 Глава 1
управляемость пригодятся во всех проектах разработки программного обеспечения.
Более того, всеохватность, присущая Rust-сообществу, означает, что язык выигры-
вает от постоянного притока новых участников с различными интересами.
Давайте конкретизируем три уже упомянутые цели создания языка: безопасность,
продуктивность и управляемость. Что под ними понимается, и в чем их важность?
1.6.1. Цель создания Rust: безопасность
В Rust-программах отсутствуют
Висячие указатели — прямые ссылки на данные, ставшие недействитель-
ными в ходе выполнения программы (см. листинг 1.3).
Состояния гонки — неспособность из-за изменения внешних факторов опре-
делить, как программа будет вести себя от запуска к запуску (см. листинг 1.4).
Переполнение буфера — попытка обращения к 12-му элементу массива, со-
стоящего из 6 элементов (см. листинг 1.5).
Недействительность итератора — проблема, вызываемая проходом како-
го-то объекта-итератора, претерпевшего изменение уже в ходе итерации
(см. листинг 1.6).
Когда программа компилируется в режиме отладки, Rust также обеспечивает защи-
ту от целочисленного переполнения. В чем его суть? Дело в том, что целые числа
могут представлять только конечный набор чисел, поскольку имеют в памяти фик-
сированную ширину. Целочисленное переполнение происходит, когда целочислен-
ные значения упираются в свой предел и снова переходят к началу своего ряда.
В следующем примере показан висячий указатель. Исходный код листинга нахо-
дится в файле ch1/ch1-cereals/src/main.rs.
Листинг 1.3. Попытка создания висячего указателя (1)
(2)
1 #[derive(Debug)]
2 enum Cereal { (3)
3 Barley, Millet, Rice, (4)
4 Rye, Spelt, Wheat, (5)
5} (6)
6
7 fn main() {
8 let mut grains: Vec<Cereal> = vec![];
9 grains.push(Cereal::Rye);
10 drop(grains);
11 println!("{:?}", grains);
12 }
(1) Разрешение макросу println! вывести перечисление Cereal
(2) enum (перечисление) — тип с фиксированным количеством допустимых вариантов
Введение в Rust 37
(3) Инициализация пустого вектора Cereal
(4) Добавление элемента к вектору grains
(5) Удаление grains и его содержимого
(6) Попытка обращения к значению, которое уже удалено
В листинге 1.3 в grains имеется указатель, созданный в строке 8. Вектор
Vec<Cereal> реализован с внутренним указателем на основной массив. Но код лис-
тинга не проходит компиляцию. При ее попытке выдается сообщение об ошибке с
жалобой на попытку «позаимствовать» «перемещенное» значение. Интерпретация
этого сообщения об ошибке и способ ее исправления будут рассмотрены далее.
А сейчас посмотрим на то, что было выведено на экран при попытке скомпилиро-
вать код листинга 1.3:
$ cargo run
Compiling ch1-cereals v0.1.0 (/rust-in-action/code/ch1/ch1-cereals)
error[E0382]: borrow of moved value: `grains` (1)
--> src/main.rs:12:22
|
8 | let mut grains: Vec<Cereal> = vec![];
| ------- move occurs because `grains` has type (2)
`std::vec::Vec<Cereal>`, which does not implement
the `Copy` trait
9 | grains.push(Cereal::Rye);
10| drop(grains);
| ------ value moved here (3)
11|
12| println!("{:?}", grains);
| ^^^^^^ value borrowed here after move (4)
error: aborting due to previous error (5)
For more information about this error, try `rustc --explain E0382`.
error: could not compile `ch1-cereals`. (6)
(1) ошибка[E0382]: заимствование перемещенного значения: `grains`
(2) перемещение произошло, потому что у `grains` тип `std::vec::Vec<Cereal>`,
в котором не реализован типаж `Copy`
(3) значение перемещено сюда
(4) значение заимствовано здесь после перемещения
(5) ошибка: прервано из-за предыдущей ошибки
(6) Дополнительную информацию об ошибке можно получить, запустив команду `rustc --
explain E0382`
ошибка: скомпилировать `ch1-cereals` невозможно.
В листинге 1.4 показан пример состояния гонки. Если помните, это состояние воз-
никает, когда из-за изменений внешних факторов невозможно определить характер
поведения программы от запуска к запуску. Исходный код листинга находится в
файле ch1/ch1-race/src/main.rs.
38 Глава 1
Листинг 1.4. Пример, в котором Rust предотвращает состояние гонки
1 use std::thread; (1)
2 fn main() {
3 let mut data = 100; (2)
4 (2)
5 thread::spawn(|| { data = 500; });
6 thread::spawn(|| { data = 1000; });
7 println!("{}", data);
8}
(1) Сведение многопоточности к локальной области видимости
(2) thread::spawn() принимает в качестве аргумента замыкание
Если термин поток (thread) вам неизвестен, то суть в том, что данный код не явля-
ется детерминированным. Узнать, какое значение будет у data при выходе из
функции main(), невозможно. В строках 6 и 7 при вызове метода thread::spawn()
создаются два потока. Каждый вызов получает в качестве аргумента замыкание,
обозначенное вертикальными линиями и фигурными скобками (например, || {…} ).
Поток, порожденный в строке 5, пытается установить для переменной data значе-
ние 500, а поток, порожденный в строке 6, пытается установить для переменной
data значение 1000. Поскольку диспетчеризация потоков определяется не про-
граммой, а операционной системой, невозможно узнать, будет ли первым запущен
тот поток, который был первым определен.
Попытка компиляции кода из листинга 1.4 приводит к целому ряду тревожных со-
общений об ошибках. Rust не позволяет иметь допуск по записи к данным сразу из
нескольких мест приложения. Код пытается позволить себе это в трех местах: когда
в основном потоке запускается main(), и по одному разу в каждом дочернем пото-
ке, порожденном вызовом thread::spawn(). Сообщения, выданные компилятором,
выглядят следующим образом:
$ cargo run (1)
Compiling ch1-race v0.1.0 (rust-in-action/code/ch1/ch1-race) (2)
(3)
error[E0373]: closure may outlive the current function, but it
borrows `data`, which is owned by the current function
--> src/main.rs:6:19
|
6 | thread::spawn(|| { data = 500; });
| ^^ ---- `data` is borrowed here
||
| may outlive borrowed value `data`
|
note: function requires argument type to outlive `static`
--> src/main.rs:6:5
|
Введение в Rust 39
6 | thread::spawn(|| { data = 500; });
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: to force the closure to take ownership of `data`
(and any other referenced variables), use the `move` keyword (4)
|
6 | thread::spawn(move || { data = 500; });
| ^^^^^^^
... (5)
error: aborting due to 4 previous errors (6)
Some errors have detailed explanations: E0373, E0499, E0502. (7)
For more information about an error, try `rustc --explain E0373`.
error: could not compile `ch1-race`.
(1) Компиляция ch1-race v0.1.0 (rust-in-action/code/ch1/ch1-race)
ошибка [E0373]: замыкание может прожить дольше текущей функции, но оно заимствует
переменную `data`, принадлежащую текущей функции
(2) `data`, заимствованная здесь, может пережить заимствованное значение `data`
(3) Примечание: чтобы пережить `static`, функции нужен тип аргумента
(4) Подсказка: чтобы заставить замыкание завладеть `data` (и любой другой ссылочной
переменной), воспользуйтесь ключевым словом `move`
(5) Еще три ошибки опущены
(6) Ошибка: прервано из-за четырех предыдущих ошибок
(7) У некоторых ошибок имеются подробные объяснения: E0373, E0499, E0502.
Дополнительную информацию об ошибке можно получить, запустив команду `rustc --
explain E0373`
ошибка: скомпилировать `ch1-race` невозможно
В листинге 1.5 показан пример переполнения буфера. Им описывается ситуация,
при которой совершается попытка обращения к несуществующему или некоррект-
ному элементу памяти. В данном случае предпринимается попытка обращения к
fruit[4], приводящая к сбою программы, поскольку в переменной fruit содер-
жится только три фрукта. Исходный код листинга находится в файле ch1/ch1-
fruit/src/main.rs.
Листинг 1.5. Пример выдачи тревожного сообщения при переполнении буфера
1 fn main() { (1)
2 let fruit = vec!['_', '_', '_']; (2)
3
4 let buffer_overflow = fruit[4];
5 assert_eq!(buffer_overflow, '_')
6}
(1) В Rust вместо того, чтобы переменной было присвоено неправильное место в памяти,
произойдет сбой компиляции
(2) assert_eq!() проверяет равенство аргументов.
40 Глава 1
При компиляции кода листинга 1.5 будет получено следующее сообщение об
ошибке:
$ cargo run
Compiling ch1-fruit v0.1.0 (/rust-in-action/code/ch1/ch1-fruit) (1)
Finished dev [unoptimized + debuginfo] target(s) in 0.31s
Running `target/debug/ch1-fruit`
thread 'main' panicked at 'index out of bounds:
the len is 3 but the index is 4', src/main.rs:3:25
note: run with `RUST_BACKTRACE=1` environment variable
to display a backtrace
(1) Компиляция ch1-fruit v0.1.0 (/rust-in-action/code/ch1/ch1-fruit)
Целевое действие [без оптимизации + отладочная
информация] завершено за 0,31 с
Запуск `target/debug/ch1-fruit`
поток 'main' забил тревогу «выход индекса за допустимые границы:
длина равна 3, а индекс равен 4», src/main.rs:3:25
примечание: для вывода обратной трассировки запустите команду
с переменной среды окружения `RUST_BACKTRACE=1`
В следующем листинге показан пример недействительности итератора, получив-
шейся при проходе какого-то объекта-итератора, претерпевшего изменение уже в
ходе итерации. Исходный код листинга находится в файле ch1/ch1-letters/src/main.rs.
Листинг 1.6. Попытка в ходе итерации изменить итератор до его прохода
1 fn main() { (1)
2 let mut letters = vec![ (2)
3 "a", "b", "c"
4 ];
5
6 for letter in letters {
7 println!("{}", letter);
8 letters.push(letter.clone());
9}
10 }
(1) Создание изменяемого вектора букв
(2) Копирование каждой буквы и добавление ее к концу вектора letters
Код листинга 1.6 не проходит компиляцию, поскольку Rust не позволяет перемен-
ной letters изменяться в блоке итерации. Сообщение об ошибке выглядит так:
$ cargo run
Compiling ch1-letters v0.1.0 (/rust-in-action/code/ch1/ch1-letters)
error[E0382]: borrow of moved value: `letters` (1)
--> src/main.rs:8:7
|
Введение в Rust 41
2 | let mut letters = vec![
| ----------- move occurs because `letters` has type
| `std::vec::Vec<&str>`, which does not
| implement the `Copy` trait (2)
...
6 | for letter in letters {
| -------
||
| `letters` moved due to this implicit call
| to `.into_iter()`
| help: consider borrowing to avoid moving
| into the for loop: `&letters` (3)
7 | println!("{}", letter);
8 | letters.push(letter.clone());
| ^^^^^^^ value borrowed here after move
error: aborting due to previous error
For more information about this error, try `rustc --explain E0382`.
error: could not compile `ch1-letters`.
To learn more, run the command again with --verbose. (4)
(1) Компиляция ch1-letters v0.1.0 (/rust-in-action/code/ch1/ch1-letters)
ошибка[E0382]: заимствование перемещенного значения: `letters`
(2) Перемещение произошло из-за того, что `letters` относится к типу
`std::vec::Vec<&str>, в котором не реализован типаж `Copy`
(3) перемещение `letters` вызвано этим неявным вызовом `.into_iter()`/ Подсказка:
чтобы помешать перемещению в цикле for, подумайте над заимствованием: `&letters`
(4) значение заимствовано здесь после перемещения. Ошибка: прервано из-за предыдущей
ошибки
Дополнительную информацию об ошибке можно получить, запустив команду `rustc --
explain E0382`
ошибка: скомпилировать `ch1-letters` невозможно.
Пусть язык сообщений об ошибках изобилует новыми терминами (заимствование,
перемещение, типаж и т.д.), суть здесь в том, что Rust страхует программиста,
уберегая его от ловушек, в которые попадают многие другие его коллеги. И не нуж-
но бояться: новые термины станут понятнее по мере изучения первых нескольких
глав книги.
Убежденность в безопасности языка предоставляет программистам дополнитель-
ную степень свободы. Благодаря знанию, что крах программе не грозит, у них по-
является тяга к экспериментам. Эта свобода в Rust-сообществе породила выраже-
ние бесстрашный параллелизм.
1.6.2. Цель создания Rust: производительность
Рассматривая варианты предполагаемых свойств Rust, его создатели выбрали то,
что больше всего придется по нраву разработчику. Среди множества присущих
языку свойств особо выделяется повышенная производительность. Но производи-
42 Глава 1
тельность программиста трудно продемонстрировать на примере, приводимом в
книге. Давайте начнем с того, на чем могут споткнуться новички, — с применения
присваивания (=) в выражении, где должна использоваться проверка на равенство
(==):
1 fn main() {
2 let a = 10;
3
4 if a = 10 {
5 println!("a equals ten");
6}
7}
В Rust предыдущий код не пройдет компиляцию. Компилятор Rust выдаст сле-
дующее сообщение:
error[E0308]: mismatched types (1)
--> src/main.rs:4:8
|
4 | if a = 10 {
| ^^^^^^ (2)
||
| expected `bool`, found `()`
| help: try comparing for equality: `a == 10`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0308`.
error: could not compile `playground`.
To learn more, run the command again with --verbose. (3)
ошибка[E0308]: несоответствующие типы
(1) ожидался `bool`, встретился `()`. Подсказка: попробуйте проверить на равенство:
`a == 10`
(2) ошибка: прервано из-за предыдущей ошибки. Дополнительную информацию об ошибке
можно получить, запустив команду `rustc --explain E0308`. Ошибка: скомпилировать
`playground` невозможно.
Поначалу сообщение об ошибке «несоответствующие типы» может показаться
странным. Ведь можно же проверить переменные на равенство целым числам.
Но если вдуматься, можно понять, почему тест if получает неверный тип. Условие
if не получает целое число. Оно получает результат присваивания. В Rust это пус-
той тип: () (произносится как юнит [unit])14.
14 Название «unit» раскрывает часть наследства Rust, являющегося потомком семейства языков
программирования ML, включающего OCaml и F#. Это понятие имеет математические корни.
Теоретически единственное значение имеется только у типа unit. Сравните его с булевым типом,
у которого два значения, true или false, или же со строковым типом, у которого бесконечное
количество допустимых значений.
Введение в Rust 43
Когда нет никакого другого значимого возвращаемого значения, выражение воз-
вращает (). Как показано в следующем фрагменте кода, в результате добавления в
строке 4 второго знака равенства получается работоспособная программа, которая
выводит на экран a equals ten:
1 fn main() {
2 let a = 10;
3
4 if a == 10 { (1)
5 println!("a equals ten");
6}
7}
Использование допустимого оператора равенства (==) позволяет программе пройти
компиляцию.
В языке Rust имеется множество удобных свойств. В нем предлагаются обобщения,
сложные типы данных, сопоставления с образцами и замыкания15. Кто уже работал
с другими языками предварительной компиляции, вероятно, оценит систему сборки
и полноценный диспетчер пакетов под названием cargo.
На первый взгляд представляется, что cargo — это интерфейс rustc, Rust-
компилятора, но cargo предоставляет еще и ряд дополнительных утилит, в числе
которых:
cargo new, которая закладывает основу Rust-проекта в новом каталоге (есть
еще cargo init, использующая текущий каталог).
cargo build, которая загружает зависимости и компилирует код.
cargo run, которая выполняет cargo build, а затем вдобавок к этому за-
пускает получившийся исполняемый файл.
cargo doc, которая создает HTML-документацию для каждой зависимости,
имеющейся в текущем проекте.
1.6.3. Цель создания Rust: управляемость
Rust предлагает программистам детальный контроль над размещением структур
данных в памяти и над схемами доступа к ним. В Rust, конечно же, используются
весьма рациональные установки по умолчанию, соответствующие его философии
«абстракций с нулевыми затратами», но они подходят не для всех ситуаций.
Временами возникает острая потребность в управлении производительностью ва-
шего приложения. При этом важную роль может сыграть хранение данных в стеке,
а не в куче. Возможно, будет смысл и в добавлении подсчета ссылок для создания
общей ссылки на то или иное значение. Иногда для определенной схемы доступа
15 Даже если эти понятия вам не известны, продолжайте чтение. Объяснения последуют дальше. Это
особенности языка, которые могут не встречаться в других языках программирования.
44 Глава 1
бывает полезно создать свой собственный тип указателя. Rust открывает широкий
простор для проектирования и предоставляет инструменты, позволяющие реализо-
вать наиболее предпочтительное решение.
ПРИМЕЧАНИЕ
Если понятия стека, кучи и подсчета ссылок вам еще не известны, не бросайте кни-
гу! Далее в ней будет выделено достаточно места для объяснения сути и порядка их
совместного применения.
Код листинга 1.7 выводит строку a: 10, b: 20, c: 30, d: Mutex { data: 40 }.
Во всех представлениях используются разные способы хранения целочисленных
значений. По мере изучения следующих нескольких глав станут понятны все плю-
сы и минусы каждого способа. А сейчас важно запомнить, что перечень способов
весьма обширен. Вы можете свободно выбирать именно тот тип, который больше
всего подходит для вашего конкретного случая.
В листинге 1.7 также показаны несколько способов создания целочисленных значе-
ний. Каждой формой предоставляется различная семантика и характеристики вре-
мени выполнения. Но программисты могут полностью сохранять контроль над
всеми избранными компромиссами.
Листинг 1.7. Несколько способов создания целочисленных значений
1 use std::rc::Rc;
2 use std::sync::{Arc, Mutex};
3
4 fn main() {
5 let a = 10; (1)
6 let b = Box::new(20); (2)
7 let c = Rc::new(Box::new(30)); (3)
8 let d = Arc::new(Mutex::new(40)); (4)
9 println!("a: {:?}, b: {:?}, c: {:?}, d: {:?}", a, b, c, d);
10 }
(1) Целое число в стеке
(2) Целое число в куче, также именуемое «упакованное целое число»
(3) Упакованное целое число, завернутое в счетчик ссылок
(4) Целое число, завернутое в атомарный счетчик ссылок и защищенное блокировкой
взаимного исключения
Чтобы понять, почему Rust что-то делает так, а не иначе, будет, наверное, полезно
обратиться к следующим трем принципиальным положениям:
Главный приоритет языка — безопасность.
По умолчанию данные в Rust являются неизменяемыми.
Настоятельно рекомендуется проводить проверки в ходе компиляции. Безо-
пасность должна быть «абстракцией с нулевыми затратами».
Введение в Rust 45
1.7. Особые возможности Rust
Мы считаем, что результат работы предопределяется используемыми инструмен-
тами. Rust позволяет создавать программные продукты, которые вы хотели бы, но
боялись создавать. Так к каким же инструментам относится Rust? Из трех принци-
пиальных положений, рассмотренных в предыдущем разделе, можно вывести три
наиболее значимые возможности языка:
Достижение высокой производительности.
Выполнение одновременных (параллельных) вычислений.
Достижение эффективной работы с памятью.
1.7.1. Достижение высокой производительности
Rust позволяет воспользоваться всей доступной производительностью вашего ком-
пьютера. Он знаменит тем, что не использует для обеспечения безопасности памяти
сборщик мусора.
К сожалению, обещание более быстрых программ упирается в фиксированную ско-
рость вашего центрального процессора. Поэтому, чтобы программы выполнялись
быстрее, нужно уменьшить объем их работы. А между тем много места во всех
смыслах занимает сам язык. Чтобы разрешить данное противоречие, Rust всецело
полагается на компилятор.
Сообщество Rust отдает предпочтение более объемному языку с компилятором,
выполняющим больший объем работы, а не простому языку, где компилятор вы-
полняет меньший объем работы. Компилятор Rust тщательно оптимизирует как
размер, так и скорость работы вашей программы. А еще в Rust применяется ряд
менее заметных приемов:
Предоставление по умолчанию удобных для кэширования структур данных.
Массивы обычно содержат данные в Rust-программах, а не в древовидных
структурах с глубоким вложением, созданных с помощью указателей. Это
называется программированием, ориентированным на данные.
Доступность современного диспетчера пакетов (cargo), упрощающего
использование десятков тысяч пакетов с открытым исходным кодом.
В этом смысле согласованность у C и C++ оставляет желать лучшего, и соз-
дание крупных проектов с массой зависимостей на этих языках обычно за-
труднено.
Неизменная статическая диспетчеризация методов, пока не будет явного
запроса на динамическую диспетчеризацию. Это позволяет компилятору
проводить сильную оптимизацию кода, иногда вплоть до полного устране-
ния издержек на вызов функции.
46 Глава 1
1.7.2. Многопоточное выполнение программ
Программистам сложно заставить компьютер выполнять несколько дел одновре-
менно. Если рассматривать действия операционной системы, то при серьезной
ошибке программиста два независимых потока выполнения могут уничтожить друг
друга. И тем не менее в сообществе Rust родилось выражение «безбоязненная кон-
курентность». Сфокусированность языка на безопасности позволяет преодолеть
ограничения независимых потоков. При этом нет никакой глобальной блокировки
интерпретатора (global interpreter lock, GIL), ограничивающей скорость потока. По-
следствия такого подхода будут рассмотрены во второй части книги.
1.7.3. Достижение эффективной работы с памятью
Rust позволяет создавать программы, требующие минимального объема памяти.
При необходимости можно воспользоваться структурами фиксированного размера
и точно знать, как управляется каждый байт. Применение конструкций высокого
уровня, таких как итераторы и обобщенные типы, влечет за собой минимальные
издержки времени выполнения.
1.8. Недостатки Rust
Было бы, конечно, проще заявить, что этот язык — универсальное средство разра-
ботки любых программных продуктов. Например, сказать про него следующее:
«Высокоуровневый синтаксис в сочетании с производительностью как у
низкоуровневых языков!».
«Параллелизм без сбоев!».
«Как язык C, но с идеальной безопасностью!».
Все эти тезисы (иногда явно преувеличенные), конечно, великолепны. Но при всех
своих достоинствах Rust не лишен и ряда недостатков.
1.8.1. Циклические структуры данных
Моделировать в Rust циклические данные, вроде произвольной структуры графа,
весьма непросто. Реализация двусвязного списка — задача по информатике, ре-
шаемая на уровне бакалавриата. К тому же прогрессу в этой области сильно меша-
ют имеющиеся в Rust проверки безопасности. Новичкам на первых порах их зна-
комства с Rust реализации подобных структур данных лучше избегать.
1.8.2. Время, затрачиваемое на компиляцию
Rust компилирует код медленнее, чем сопоставимые с ним языки. В его компиляторе
имеется довольно сложная инструментальная цепочка, получающая ряд промежу-
точных представлений и отправляющая компилятору LLVM большой объем кода.
Введение в Rust 47
Единицей компиляции программы на Rust является не отдельный файл, а целый па-
кет (известный как крейт). Поскольку крейты могут включать в себя несколько мо-
дулей, они могут становиться весьма большим объектом компиляции. Это, конечно,
позволяет оптимизировать весь крейт, но требует также его полной компиляции.
1.8.3. Строгость
Лениться при программировании на Rust довольно сложно или же практически не-
возможно. Пока все не будет в порядке, программы просто не пройдут компиля-
цию. Компилятор в Rust строгий, но полезный.
Возможно, со временем вы оцените эту особенность языка по достоинству. Если
когда-либо уже приходилось программировать на динамическом языке, то вполне
вероятно вам встречались и досадные сбои из-за неверно названной переменной.
Rust вводит в подобные разочарования заранее, чтобы они не превращались во все-
возможные сбои, испытываемые пользователем вашей программы.
1.8.4. Объем языка
Rust огромен! В нем богатая система типов, несколько десятков ключевых слов,
а также имеется ряд функций, недоступных в других языках программирования.
Совокупность этих факторов задает весьма крутую кривую обучения. Чтобы управ-
лять этим процессом, рекомендуется поэтапное изучение Rust. Следует начинать с
минимального поднабора языка, выделяя по мере надобности время на усвоение
подробностей. Именно такой подход принят в этой книге. Рассмотрение более
серьезных понятий откладывается на более поздний срок.
1.8.5. Излишний ажиотаж
Сообществом Rust не приветствуется быстрый рост приверженцев и излишняя шу-
миха вокруг языка. Но в почте ряда программных проектов все же оказываются
письма с вопросом: «А вы не задумывались над тем, чтобы переписать все на
Rust?». К сожалению, программы на Rust — это всего лишь программы. Они не за-
щищены от проблем безопасности и не предлагают никакого универсального сред-
ства от всех бед, с которыми сталкиваются разработчики программных продуктов.
1.9. Примеры использования TLS-безопасности
Чтобы показать, что Rust не устраняет всех ошибок, рассмотрим две серьезные уяз-
вимости, угрожавшие чуть ли не всем устройствам, подключенным к Интернету,
и посмотрим, смог ли бы Rust предотвратить их возникновение.
Когда к 2015 году Rust приобрел известность, в реализациях SSL/TLS (а именно,
в OpenSSL и в форке от Apple) были обнаружены серьезные бреши в безопасности,
неформально названные Heartbleed и goto fail;, обе уязвимости предоставляют
возможность проверить бытующие в Rust утверждения о безопасности памяти.
48 Глава 1
Rust, скорее всего, помог бы в обоих случаях, но все же и на нем можно написать
код, страдающий схожими проблемами.
1.9.1. Heartbleed
Уязвимость Heartbleed, получившая официальное обозначение CVE-2014-016016,
была вызвана неправильным переиспользованием буфера. Под буфером здесь по-
нимается пространство, зарезервированное в памяти для приема входных данных.
Если между операциями записи содержимое буфера не будет очищено, то от чтения
к чтению может произойти утечка данных.
Почему так получается? Программисты гонятся за производительностью. Буферы
переиспользуются для сведения к минимуму запросов к памяти со стороны прило-
жения.
Представим, что нужно обработать некую секретную информацию от нескольких
пользователей. По какой-то причине в ходе выполнения программы решено один и
тот же буфер использовать повторно. Если после использования этот буфер не
сбросить, то информация из ранее состоявшихся вызовов утечет к последующим
вызовам. Вот выдержка из программы, в которой может проявиться такая ошибка:
let buffer = &mut[0u8; 1024]; (1)
read_secrets(&user1, buffer); (2)
store_secrets(buffer);
read_secrets(&user2, buffer); (3)
store_secrets(buffer);
(1) Привязка к переменной buffer ссылки (&) на изменяемый (mut) массив ([…]),
содержащему 1,024 беззнаковых 8-разрядных целых числа (u8), изначально
установленных в 0
(2) Заполнение буфера байтами данных от user1
(3) Буфер все еще содержит данные от user1, которые необязательно могут быть затерты
данными от user2.
Rust не защищает вас от логических ошибок. Он гарантирует, что ваши данные ни-
когда не будут одновременно записаны в двух местах. Но не гарантирует, что ваша
программа полностью избавлена от всех проблем безопасности.
1.9.2. Goto fail;
Ошибка, обозначаемая, как goto fail; и получившая официальное обозначение
CVE-2014-126617, вызывается ошибкой программиста в совокупности с проблемами
конструкции языка C (и, возможно, тем, что его компилятор не указывает на
16 См. «CVE-2014-0160 Detail», https://nvd.nist.gov/vuln/detail/CVE-2014-0160.
17 См. «CVE-2014-1266 Detail», https://nvd.nist.gov/vuln/detail/CVE-2014-1266.
Введение в Rust 49
дефект). В итоге функция, разработанная для проверки пары криптографических
ключей, проходит мимо всех проверок. Рассмотрим фрагмент, извлеченный из ис-
ходной функции SSLVerifySignedServerKeyExchange и сохранивший изрядное ко-
личество весьма запутанного синтаксиса18:
1 static OSStatus
2 SSLVerifySignedServerKeyExchange(SSLContext *ctx,
3 bool isRsa,
4 SSLBuffer signedParams,
5 uint8_t *signature,
6 UInt16 signatureLen)
7{
8 OSStatus err; (1)
9 ...
10
11 if ((err = SSLHashSHA1.update(
12 &hashCtx, &serverRandom)) != 0) (2)
13 goto fail;
14
15 if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0)
16 goto fail;
17 goto fail; (3)
18 if ((err = SSLHashSHA1.final(&hashCtx, &hashOut)) != 0)
19 goto fail;
20
21 err = sslRawVerify(ctx,
22 ctx->peerPubKey,
23 dataToSign, /* plaintext \*/
24 dataToSignLen, /* plaintext length \*/
25 signature,
26 signatureLen);
27 if(err) {
28 sslErrorLog("SSLDecodeSignedServerKeyExchange: sslRawVerify "
29 "returned %d\n", (int)err);
30 goto fail;
31 }
32
33 fail:
34 SSLFreeBuffer(&signedHashes);
35 SSLFreeBuffer(&hashCtx);
36 return err; (4)
37 }
(1) Инициализация OSStatus проходным значением (например, 0)
(2) Серия защитных программных проверок
18 Оригинал доступен по адресу http://mng.bz/RKGj.
50 Глава 1
(3) Безусловный переход пропускает SSLHashSHA1.final() и (важный) вызов
sslRawVerify().
(4) Возврат проходного значения 0 даже для входных данных, которые бы провалили
проверочный тест
Проблема в коде примера находится между строк 15 и 17. В языке C логические
тесты не требуют фигурных скобок. Компиляторы C интерпретируют эти три стро-
ки следующим образом:
if ((err = SSLHashSHA1.update(&hashCtx, &signedParams)) != 0) {
goto fail;
}
goto fail;
Поможет ли в такой ситуации Rust? Возможно. В данном конкретном случае
имеющийся в Rust парсер выловил бы ошибку. Он не допускает логические тесты
без фигурных скобок. Rust также выдает предупреждение, когда код недоступен.
Но это еще не значит, что допустить ошибку в Rust просто невозможно. Когда про-
граммистов поджимают сроки, они склонны совершать ошибки. Как правило, по-
добный код проходит компиляцию и может быть запущен на выполнение.
СОВЕТ
Программируйте осмотрительнее.
1.10. Для чего Rust подходит лучше всего?
Хотя Rust разрабатывался как язык системного программирования, он все же язык
общего назначения. Этот язык успешно применяется во многих рассматриваемых
далее областях.
1.10.1. В утилитах командной строки
Программистам, создающим утилиты командной строки, Rust дает три основных
преимущества: минимальное время запуска, низкий уровень потребления памяти и
простое развертывание. Программы быстро включаются в работу, поскольку Rust
не нуждается в инициализации интерпретатора (как Python, Ruby и т.д.) или в вир-
туальной машине (как Java, C# и т.д.).
Будучи языком чисто аппаратного уровня, Rust позволяет создавать программы,
эффективно распоряжающиеся памятью19. Далее в книге будет показано, что мно-
гие типы имеют нулевой размер. То есть, по сути, они являются подсказками ком-
пилятору и вообще не потребляют память в запущенной на выполнение программе.
Утилиты, написанные на Rust, по умолчанию компилируются как статические дво-
ичные файлы. Этот метод компиляции позволяет избежать зависимости от совме-
стно используемых библиотек, требующих установки перед запуском программы.
19 Бытует шутка, что Rust (ржавчина) максимально приближен к металлу.