Когда-то мы написали программу-гороскоп на Python, которая выдавала случайные предсказания для любого знака зодиака. Это было так:
- пользователь запускал программу;
- видел список знаков и предложение выбрать один из них по номеру;
- вводил номер и получал случайное предсказание.
Все прогнозы собирались по одной схеме: у нас было несколько наборов взаимозаменяемых фраз для 4 частей предсказания. Мы брали по одной фразе из каждого набора и соединяли их. В итоге получался связный текст, очень похожий на те гороскопы, что сейчас печатаются в журналах.
Сегодня напишем то же самое на Rust. Начнём с самого начала: установим Rust на компьютер и будем шаг за шагом писать программу и разбирать, как она работает.
Некоторые сложные вещи упростим или пропустим, чтобы разобрать их более подробно в отдельных статьях.
Что за язык Rust
Rust — молодой, надёжный и довольно сложный в изучении язык программирования. Его архитектура защищает код от множества ошибок, и он работает быстрее таких универсальных языков, как Python или Ruby.
Rust используют в разработке облачной инфрастуктуры, операционных систем, кибербезопасности, авиационной и космической промышленности и множестве других отраслей. Это единственный язык программирования, кроме C, который используется в ядре Linux.
Начинать учить программирование с Rust может быть действительно немного сложно. Но если вы уже знакомы с любым другим языком, разобраться вполне реально.
Устанавливаем Rust на компьютер
Для начала нам нужно установить Rust себе на компьютер.
Если у вас macOS или Linux, запускаем терминал и пишем такую команду:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
Если Windows — скачиваем установщик или выбираем версию по вкусу на официальном сайте.
Проверить установку и обновить Rust до последней версии можно такими командами:
rustc --version
rustup update
Новая стабильная (то есть официально одобренная к использованию) версия Rust выходит примерно раз в 6 недель, так что хорошей практикой будет периодически проверять обновления.
Вместе с Rust на компьютер устанавливаются:
- компилятор, который проверяет код и собирает его в один файл для запуска;
- форматер — инструмент для проверки единого стиля кода;
- линтер для выявления потенциальных ошибок и оптимизаций;
- менеджер пакетов Cargo.
Создаём проект
Если на Python для запуска кода достаточно одного файла скрипта, то в Rust их нужно несколько. Самый простой способ создать новый проект — использовать менеджер проектов Cargo.
Сначала нужно создать пустую папку под будущий проект. Зайдя внутрь через терминал командной строки операционной системы или IDE, выполняем команду:
cargo new hello_world
У нас появится новая папка с готовыми файлами для запуска кода:
Как запустить код
Для запуска готового скрипта нужны ещё две команды. Первая — для компиляции. Переходим в терминале в папку проекта и пишем:
cargo build
После этого компилятор проверит код, и если всё в порядке, то создаст исполняемый файл. Этот файл можно будет запустить такой командой:
cargo build
На обоих этапах иногда могут возникать ошибки, но Rust довольно подробно описывает, где именно проблема и как её исправить.
Подключаем библиотеки
Так же как в любом другом языке, сначала мы импортируем нужные библиотеки, которые будем использовать в проекте. Нам понадобятся две: встроенная std
и внешняя rand
.
Std
даёт доступ к минимальному набору проверенных инструментов. Мы подключим модуль ввода-вывода.
Rand
— библиотека для генерации случайных чисел, которую понадобится скачать. Это делается так:
- открываем нашу папку с проектом любым способом — через проводник, командную строку или редактор кода;
- находим файл Cargo.toml, который по умолчанию выглядит так:
[package]
name = "hello_world"
version = "0.1.0"
edition = "2021"
[dependencies]
- после последней строки дописываем ещё одну:
rand = "0.8.5"
Это аналогично выполнению команды установки библиотеки в терминале на Python. Мы указываем название библиотеки и нужную версию для загрузки.
Теперь можно перейти к скрипту и добавить первые две строчки для импорта зависимостей:
// подключаем модуль для генерации случайных чисел
use rand::Rng;
// подключаем модуль ввода-вывода из стандартной библиотеки
use std::io;
Обратите внимание на такое:
- разные строки кода разделяются точкой с запятой;
- библиотеки и нужные нам модули из этих библиотек разделяются двойным двоеточием.
Двойные двоеточия нужны, если мы хотим использовать конкретный фрагмент какой-то структуры. Двойное двоеточие применяется в том числе для работы с библиотеками и их функциями. Формула использования может выглядеть так:
библиотека::модуль::функция_модуля
Точки применяются для вызова методов, так же как на Python. Мы можем использовать метод из функции, применив сначала двоеточия, а потом точку:
модуль::функция_модуля.метод_функции
Основная функция
Любая программа в Rust начинается с выполнения основной функции main
. Для её создания используется ключевое слово fn
:
// создаём основную функцию
fn main() {
В круглых скобках после функции указываются аргументы, а потом идут фигурные скобки с телом функции.
Внутри main
можно вызывать любые другие функции. Их можно определять до или после основной. Rust понимает, что где, потому что всё находится внутри одной области видимости.
Создаём массивы для гороскопа
Массив в Rust — набор объектов одинакового типа. Нам понадобится четыре массива, в каждом из них будет по пять строк.
Для массивов создадим четыре разные переменные. Это очень похоже на списки в Python: элементы мы напишем в квадратных скобках, а сами строки обозначены кавычками и перечисляются через запятую:
// создаём первый массив с пятью неизменяемыми строками со ссылками
let first = [
// перечисляем элементы внутри квадратных скобок через запятую
"Сегодня — идеальный день для новых начинаний.",
"Оптимальный день для того, чтобы решиться на смелый поступок!",
"Будьте осторожны, сегодня звёзды могут повлиять на ваше финансовое состояние.",
"Лучшее время для того, чтобы начать новые отношения или разобраться со старыми.",
"Плодотворный день для того, чтобы разобраться с накопившимися делами.",
];
// создаём второй массив с пятью неизменяемыми строками со ссылками
let second = [
// перечисляем элементы внутри квадратных скобок через запятую
"Но помните, что даже в этом случае нужно не забывать про",
"Если поедете за город, заранее подумайте про",
"Те, кто сегодня нацелен выполнить множество дел, должны помнить про",
"Если у вас упадок сил, обратите внимание на",
"Помните, что мысли материальны, а значит вам в течение дня нужно постоянно думать про",
];
// создаём третий массив с пятью неизменяемыми строками со ссылками
let second_add = [
// перечисляем элементы внутри квадратных скобок через запятую
"отношения с друзьями и близкими.",
"работу и деловые вопросы, которые могут так некстати помешать планам.",
"себя и своё здоровье, иначе к вечеру возможен полный раздрай.",
"бытовые вопросы — особенно те, которые вы не доделали вчера.",
"отдых, чтобы не превратить себя в загнанную лошадь в конце месяца.",
];
// создаём четвертый массив с пятью неизменяемыми строками со ссылками
let third = [
// перечисляем элементы внутри квадратных скобок через запятую
"Злые языки могут говорить вам обратное, но сегодня их слушать не нужно.",
"Знайте, что успех благоволит только настойчивым, поэтому посвятите этот день воспитанию духа.",
"Даже если вы не сможете уменьшить влияние ретроградного Меркурия, то хотя бы доведите дела до конца.",
"Не нужно бояться одиноких встреч — сегодня то самое время, когда они значат многое.",
"Если встретите незнакомца на пути — проявите участие, и тогда эта встреча посулит вам приятные хлопоты.",
];
В Rust есть два типа строк:
&str
, неизменяемая строка;String
, динамическая строка, которая может меняться.
Строкам из массивов выше Rust автоматически присвоит тип &str
, поэтому весь массив тоже примет значение &str
. Это можно увидеть, если использовать IDE с включёнными подсказками:
Выводим список знаков зодиака
Чтобы пользователь увидел, какие варианты знаков зодиака доступны для выбора, их нужно вывести на экран. Сначала посмотрим, как это делается, а потом разберём принцип работы:
// Выводим знаки зодиака
println!("1 — Овен");
println!("2 — Телец");
println!("3 — Близнецы");
println!("4 — Рак");
println!("5 — Лев");
println!("6 — Дева");
println!("7 — Весы");
println!("8 — Скорпион");
println!("9 — Стрелец");
println!("10 — Козерог");
println!("11 — Водолей");
println!("12 — Рыбы");
// Спрашиваем у пользователя его знак
println!("Введите число с номером знака зодиака:");
Мы используем команду println!
Это не просто оператор для вывода текста, а макрос — вызов предварительно написанного шаблона кода для сокращения количества итогового кода программы.
Главная идея макросов в том, что они могут выглядеть просто как обычные команды или встроенные функции — но внутри них есть код, который скрыт для упрощения работы программистов.
Получаем ответ от пользователя
Сделаем так, чтобы ввод от пользователя сохранялся в отдельную переменную. Для этого нам понадобятся:
- Изменяемые переменные. Все переменные в Rust объявляются через ключевое слово
let
. Стандартные переменные неизменямые — после объявления нельзя перезаписать их содержимое и тип данных. Если нужна переменная изменяемого типа, после словаlet
нужно добавить ещёmut
. - Модуль ввода-вывода
io
из стандартной библиотекиstd
. В начале программы мы подключили его, а теперь нужно передать в модуль нашу переменную, в которой хранится изменяемая строка с вводом от пользователя.
Получится такой код:
// Создаём изменяемую переменную-строку
let mut input = String::new();
// Запускаем модуль ввода-вывода и считываем ввод от пользователя
// Если считывание по каким-то причинам не удалось, выводим сообщение об ошибке
io::stdin().read_line(&mut input).expect("Не удалось прочесть строку");
Посмотрите: функция из модуля подключается через двойное двоеточие, а методы функции — через точки.
А ещё обратите внимание на знак амперсанда & в скобках после метода read_line
. Он означает, что мы передаём ссылку на уже существующую переменную. Если в универсальных простых языках типа Python все переменные — ссылки на объекты в памяти, то Rust честно создаёт полноценные копии. Поэтому, чтобы не переполнять память, нужно указывать, что мы ссылаемся на уже созданный объект через знак &
.
Обрабатываем полученный ответ
Мы получили от пользователя строку с номером, теперь осталось преобразовать её в число. Алгоритм работы этой части выглядит так:
- Создаём переменную типа
usize
— это целое число. - Пытаемся перевести полученную от пользователя строку в целое число. Для этого применяем конструкцию
match:
она берёт переменнуюinput
, применяет к ней метод очистки от пробелов или символов новой строкиtrim
и преобразовывает в целочисленный тип методомparse
. - Если пользователь ввёл число от 1 до 12, преобразовываем строку в число.
- Если введён текст или какие-то другие символы, выдаём ошибку.
- В случае ошибки выходим из программы командой
return
.
Это очень похоже на конструкцию try-except в Python, когда мы пытаемся выполнить какой-то код, а если не получается, выдаём заготовленное сообщение об исключении.
Rust-код будет таким:
// Создаём переменную для хранения целого числа типа usize
// Убираем лишние пробелы и символы новой строки из введённой
// пользователем строки и пытаемся преобразовать строку в целое число
let zodiac: usize = match input.trim().parse() {
// Если строку можно преобразовать в число — преобразовываем
Ok(num) => num,
// Если нет — выдаём ошибку
Err(_) => {
println!("Ошибка! Введите число от 1 до 12.");
// и останавливаем программу
return;
}
};
Проверяем, как будет обрабатываться заведомо неправильный ввод:
Генерация гороскопа
Как и в гороскопах из журналов, нам на самом деле неважно, какой знак зодиака у пользователя. Но нам важно, чтобы он ввёл число от 1 до 12.
У нас уже есть проверка на тип введённых данных. Сейчас добавим проверку диапазона. Принцип работы этой части:
- Если получено число от 1 до 12 — переходим к следующей части кода.
- Создаём генератор случайных чисел в изменяемой переменной
rng
. Для этого используем модульthread_rng
из библиотекиrand
. - Используем этот генератор, чтобы достать по одному случайному элементу-строке из каждого массива. Для этого ограничиваем варианты генератора диапазоном от 0 до длины массива, то есть номера последнего элемента.
Теперь напишем это на Rust:
// Если число находится в промежутке от 1 до 12 — выдаём гороскоп
if zodiac > 0 && zodiac < 13 {
// Создаём генератор случайных чисел для
// выбора случайных строк из каждого массива
let mut rng = rand::thread_rng();
// Создаём четыре переменные и каждой присваиваем случайную строку,
// по одной на массив
let first_choice = rng.gen_range(0..first.len());
let second_choice = rng.gen_range(0..second.len());
let second_add_choice = rng.gen_range(0..second_add.len());
let third_choice = rng.gen_range(0..third.len());
Сейчас у нас есть четыре строки, осталось вывести их по очереди на экран. Если вы знаете, как выводить переменные на Python на экран через оператор print
и метод format
, то сразу поймёте, как работает эта часть:
// Выводим на экран последовательно все четыре переменные
println!(
// Обозначаем, что выводим четыре переменных
"{} {} {} {}",
// Указываем, какие именно переменные выводим:
first[first_choice], second[second_choice], second_add[second_add_choice], third[third_choice]
);
// Если введённое число не входит в диапазон
// от 1 до 12, выдаём сообщение об ошибке
} else {
println!("Вы ошиблись с числом, запустите программу ещё раз.");
}
}
Проверим, что программа правильно реагирует на ошибки:
Запускаем финальную версию и проверяем получение гороскопа:
Что сделаем в следующий раз
Мы написали небольшую простую программу на Rust и разобрали несколько основных вещей. В следующий раз закрепим пройденное и добавим новые возможности, например напишем программу для получения прогноза погоды и попробуем создать для неё красивый внешний вывод в консоли.
// подключаем модуль для генерации случайных чисел
use rand::Rng;
// подключаем модуль ввода-вывода из стандартной библиотеки
use std::io;
// создаём основную функцию
fn main() {
// создаём первый массив с пятью неизменяемыми строками со ссылками
let first = [
// перечисляем элементы внутри квадратных скобок через запятую
"Сегодня — идеальный день для новых начинаний.",
"Оптимальный день для того, чтобы решиться на смелый поступок!",
"Будьте осторожны, сегодня звёзды могут повлиять на ваше финансовое состояние.",
"Лучшее время для того, чтобы начать новые отношения или разобраться со старыми.",
"Плодотворный день для того, чтобы разобраться с накопившимися делами.",
];
// создаём второй массив с пятью неизменяемыми строками со ссылками
let second = [
// перечисляем элементы внутри квадратных скобок через запятую
"Но помните, что даже в этом случае нужно не забывать про",
"Если поедете за город, заранее подумайте про",
"Те, кто сегодня нацелен выполнить множество дел, должны помнить про",
"Если у вас упадок сил, обратите внимание на",
"Помните, что мысли материальны, а значит вам в течение дня нужно постоянно думать про",
];
// создаём третий массив с пятью неизменяемыми строками со ссылками
let second_add = [
// перечисляем элементы внутри квадратных скобок через запятую
"отношения с друзьями и близкими.",
"работу и деловые вопросы, которые могут так некстати помешать планам.",
"себя и своё здоровье, иначе к вечеру возможен полный раздрай.",
"бытовые вопросы — особенно те, которые вы не доделали вчера.",
"отдых, чтобы не превратить себя в загнанную лошадь в конце месяца.",
];
// создаём четвертый массив с пятью неизменяемыми строками со ссылками
let third = [
// перечисляем элементы внутри квадратных скобок через запятую
"Злые языки могут говорить вам обратное, но сегодня их слушать не нужно.",
"Знайте, что успех благоволит только настойчивым, поэтому посвятите этот день воспитанию духа.",
"Даже если вы не сможете уменьшить влияние ретроградного Меркурия, то хотя бы доведите дела до конца.",
"Не нужно бояться одиноких встреч — сегодня то самое время, когда они значат многое.",
"Если встретите незнакомца на пути — проявите участие, и тогда эта встреча посулит вам приятные хлопоты.",
];
// Выводим знаки зодиака
println!("1 — Овен");
println!("2 — Телец");
println!("3 — Близнецы");
println!("4 — Рак");
println!("5 — Лев");
println!("6 — Дева");
println!("7 — Весы");
println!("8 — Скорпион");
println!("9 — Стрелец");
println!("10 — Козерог");
println!("11 — Водолей");
println!("12 — Рыбы");
// Спрашиваем у пользователя его знак
println!("Введите число с номером знака зодиака:");
// Создаём изменяемую переменную-строку
let mut input = String::new();
// Запускаем модуль ввода-вывода и считываем ввод от пользователя
// Если считывание по каким-то причинам не удалось, выводим сообщение об ошибке
io::stdin().read_line(&mut input).expect("Не удалось прочесть строку");
// Создаём переменную для хранения целого числа типа usize
// Убираем лишние пробелы и символы новой строки из введённой
// пользователем строки и пытаемся преобразовать строку в целое число
let zodiac: usize = match input.trim().parse() {
// Если строку можно преобразовать в число — преобразовываем
Ok(num) => num,
// Если нет — выдаём ошибку
Err(_) => {
println!("Ошибка! Введите число от 1 до 12.");
// и останавливаем программу
return;
}
};
// Если число находится в промежутке от 1 до 12 — выдаём гороскоп
if zodiac > 0 && zodiac < 13 {
// Создаём генератор случайных чисел для
// выбора случайных строк из каждого массива
let mut rng = rand::thread_rng();
// Создаём четыре переменные и каждой присваиваем случайную строку,
// по одной на массив
let first_choice = rng.gen_range(0..first.len());
let second_choice = rng.gen_range(0..second.len());
let second_add_choice = rng.gen_range(0..second_add.len());
let third_choice = rng.gen_range(0..third.len());
// Выводим на экран последовательно все четыре переменные
println!(
// Обозначаем, что выводим четыре переменных
"{} {} {} {}",
// Указываем, какие именно переменные выводим:
first[first_choice], second[second_choice], second_add[second_add_choice], third[third_choice]
);
// Если введённое число не входит в диапазон
// от 1 до 12, выдаём сообщение об ошибке
} else {
println!("Вы ошиблись с числом, запустите программу ещё раз.");
}
}