Этот цикл статей является вольным переводом книги «Rust by Example», которую пишет Хорхе Апарисио на Github.
На момент написания этого топика автор книги создал 49 глав, в первой части будет перевод первых пяти. Убедитесь, что Rust установлен и под рукой имеется документация.
Давайте начинать!
Это код традиционной программы «Hello World»:
Программа может быть сгенерирована с помощью компилятора Rust rustc:
rustc создаст бинарный файл «hello», который можно запустить:
Макрос
Дополнительная информация о форматировании здесь: std::fmt
Целые числа 1, с плавающей точкой 1.2, символы 'a', строки
Также целые числа можно выразить через шестнадцатеричное, восьмеричное или двоичное обозначение, используя один из префиксов: 0x, 0o или 0b.
В числовые литералы можно вставлять подчёркивания для читабельности, например, 1_000 такой же, как и 1000, а 0.000_001 такой же, как и 0.000001.
Мы должны сказать компилятору, какой из литералов мы используем. Сейчас мы будем использовать суффикс
Доступные операторы и их приоритет похож на C-подобных языках.
Значения (как и литералы) могут быть связаны с переменными, используя обозначение let.
По умолчанию переменные нельзя изменять, но это можно исправить, добавив модификатор mut.
Компилятор будет выводить сообщения об ошибке изменчивости.
Переменные имеют локальную область, и имеют видимость в блоке (блок представляет собой набор операторов, заключённых в фигурные скобки {}). Кроме того, допускается скрытие переменной.
Можно сперва объявлять переменные, а инициализировать их позже. Но эта форма редко используется, так как это может привести к использованию неинициализированных переменных.
Компилятор запрещает использование неинициализированных переменных, так как это привело бы к непредсказуемым последствиям.
Rust обеспечивает безопасность с помощью статической проверки типов. Тип переменной может быть явно указан при её объявлении. Тем не менее, в большинстве случаев, компилятор сможет определить тип переменной из контекста, если это очевидно.
Это краткое изложение примитивных типов в Rust:
Rust не предоставляет неявного преобразования типов (coercion) между примитивами, но, явное приведение типов (casting) может быть достигнуто с помощью ключевого слова as.
В числовых литералах тип может быть аннотирован, добавив тип в качестве суффикса, за исключением
Тип литералов без суффикса будет зависеть от того, как они используются. Если никаких ограничений не существует, то компилятор выдаст сообщение об ошибке.
Есть некоторые понятия, используемые в предыдущем коде, которые не были объяснены раньше, вот краткое объяснение для нетерпеливых читателей:
Логический вывод типов довольно умён. Тип добавляемой переменной используется как определитель типа для второй переменной. Вот продвинутый пример:
Отсутствует необходимость в аннотации типа переменной, компилятор счастлив как и программист!
Оператор
Основное применение псевдонимов это снижение количества кода, например, тип
На момент написания этого топика автор книги создал 49 глав, в первой части будет перевод первых пяти. Убедитесь, что Rust установлен и под рукой имеется документация.
Давайте начинать!
Содержание
Примечание:
- При написании программ использовался компилятор версии Nightly (0.10), не забудьте про это.
- Запустить код можно в Rust Playpen (кнопка «evaluate»): http://play.rust-lang.org
1. Привет, мир!
Это код традиционной программы «Hello World»:
// Комментарии игнорируются компилятором
// Это основная функция
fn main() {
// Вывести текст в консоль
println!("Hello World!");
}
println!
это макрос (мы рассмотрим их позже), который печатает текст в консоль.Программа может быть сгенерирована с помощью компилятора Rust rustc:
$ rustc hello.rs
rustc создаст бинарный файл «hello», который можно запустить:
$ ./hello
Hello World!
2. Форматированный вывод
Макрос
println!
не только выводит в консоль, а также способен форматировать текст и сериализованные значения. Корректность проверяется во время компиляции.fn main() {
// `print!`, как `println!`, но он не добавляет новую строку в конце
print!("January has ");
// `{}` это заполнители для аргументов, которые будут строками
println!("{} days", 31i);
// `i` суффикс указывает компилятору, что этот литерал имеет тип: целое
// число со знаком, смотрите следующую главу для более подробной информации
// Позиционные аргументы могут быть повторно использованы по шаблону
println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob");
// Аргументы можно называть
println!("{subject} {verb} {predicate}",
predicate="over the lazy dog",
subject="the quick brown fox",
verb="jumps");
// Специальное форматирование может быть указано в заполнителе после `:`, `t` это бинарное представление
println!("{} of {:t} people know binary, the other half don't", 1i, 2i);
// Ошибка! Не хватает аргумента для вывода
println!("My name is {0}, {1} {0}", "Bond");
// ИСПРАВЬТЕ ^ добавьте отсутствующий аргумент: "James"
}
Дополнительная информация о форматировании здесь: std::fmt
3. Литералы и операторы
Целые числа 1, с плавающей точкой 1.2, символы 'a', строки
"abc"
, логические true и значения пустого типа () могут быть выражены с помощью литералов.Также целые числа можно выразить через шестнадцатеричное, восьмеричное или двоичное обозначение, используя один из префиксов: 0x, 0o или 0b.
В числовые литералы можно вставлять подчёркивания для читабельности, например, 1_000 такой же, как и 1000, а 0.000_001 такой же, как и 0.000001.
Мы должны сказать компилятору, какой из литералов мы используем. Сейчас мы будем использовать суффикс
u
, указывающий, что литерал является целым числом без знака, суффикс i
чтобы указать, что это знаковое целое число. Мы рассмотрим систему типов в 5 главе, а также подробную информацию о аннотировании литералов.Доступные операторы и их приоритет похож на C-подобных языках.
fn main() {
// Целочисленное сложение
println!("1 + 2 = {}", 1u + 2);
// Вычитание
println!("1 - 2 = {}", 1i - 2);
// Попробуйте изменить `1i` на `1u` и понять, почему тип важен
// Булева логика
println!("true AND false is {}", true && false);
println!("true OR false is {}", true || false);
println!("NOT true is {}", !true);
// Битовые операции
println!("0011 AND 0101 is {:04t}", 0b0011u & 0b0101);
println!("0011 OR 0101 is {:04t}", 0b0011u | 0b0101);
println!("0011 XOR 0101 is {:04t}", 0b0011u ^ 0b0101);
println!("1 << 5 is {}", 1u << 5);
println!("0x80 >> 2 is 0x{:x}", 0x80u >> 2);
// Используйте подчеркивания, чтобы улучшить читаемость
println!("One million is written as {}", 1_000_000u);
}
4. Переменные
Значения (как и литералы) могут быть связаны с переменными, используя обозначение let.
fn main() {
let an_integer = 1u;
let a_boolean = true;
let unit = ();
// скопировать значение `an_integer` в `copied_integer`
let copied_integer = an_integer;
println!("An integer: {}", copied_integer);
println!("A boolean: {}", a_boolean);
println!("Meet the unit value: {}", unit);
// Компилятор предупреждает о неиспользуемых переменных; эти предупреждения можно
// отключить используя подчёркивание перед именем переменной
let _unused_variable = 3u;
let noisy_unused_variable = 2u;
// ИСПРАВЬТЕ ^ Добавьте подчёркивание
}
4.1 Изменяемость
По умолчанию переменные нельзя изменять, но это можно исправить, добавив модификатор mut.
fn main() {
let _immutable_variable = 1i;
let mut mutable_variable = 1i;
println!("Before mutation: {}", mutable_variable);
// Ок
mutable_variable += 1;
println!("After mutation: {}", mutable_variable);
// Ошибка!
_immutable_variable += 1;
}
Компилятор будет выводить сообщения об ошибке изменчивости.
4.2 Области и видимость
Переменные имеют локальную область, и имеют видимость в блоке (блок представляет собой набор операторов, заключённых в фигурные скобки {}). Кроме того, допускается скрытие переменной.
fn main() {
// Эта переменная живет в области функции main
let long_lived_variable = 1i;
// Это блок, он имеет меньший объем нежели основная функция
{
// Эта переменная существует только в этом блоке
let short_lived_variable = 2i;
println!("inner short: {}", short_lived_variable);
// Эта переменная не видна внешней функции
let long_lived_variable = 5_f32;
println!("inner long: {}", long_lived_variable);
}
// Конец блока
// Ошибка! `short_lived_variable` не существует в этой области
println!("outer short: {}", short_lived_variable);
// ИСПРАВЬТЕ ^ Закомментируйте строку
println!("outer long: {}", long_lived_variable);
}
4.3 Предварительное объявление
Можно сперва объявлять переменные, а инициализировать их позже. Но эта форма редко используется, так как это может привести к использованию неинициализированных переменных.
fn main() {
// Объявляем переменную
let a_variable;
{
let x = 2i;
// Инициализируем переменную
a_variable = x * x;
}
println!("a variable: {}", a_variable);
let another_variable;
// Ошибка! Использование неинициализированной переменной
println!("another variable: {}", another_variable);
// ИСПРАВЬТЕ ^ Закомментируйте строку
another_variable = 1i;
println!("another variable: {}", another_variable);
}
Компилятор запрещает использование неинициализированных переменных, так как это привело бы к непредсказуемым последствиям.
5. Типы
Rust обеспечивает безопасность с помощью статической проверки типов. Тип переменной может быть явно указан при её объявлении. Тем не менее, в большинстве случаев, компилятор сможет определить тип переменной из контекста, если это очевидно.
fn main() {
// Аннотированный тип переменной
let a_float: f64 = 1.0;
// Эта переменная типа `int`
let mut an_integer = 5i;
// Ошибка! Тип переменной нельзя изменять
an_integer = true;
}
Это краткое изложение примитивных типов в Rust:
- целые числа:
i8
,i16
,i32
,i64
иint
(размер зависит от платформы) - целые числа без знака:
u8
,u16
,u32
,u64
иuint
(размер зависит от платформы) - с плавающей точкой:
f32
,f64
char
значения Unicode:'a', 'α'
и'∞'
(4 байта каждый)bool
true или false- кортежи
()
5.1 Приведение типов
Rust не предоставляет неявного преобразования типов (coercion) между примитивами, но, явное приведение типов (casting) может быть достигнуто с помощью ключевого слова as.
fn main() {
let decimal = 65.4321_f32;
// Ошибка! Нет неявного преобразования
let integer: u8 = decimal;
// ИСПРАВЬТЕ ^ Закомментируйте строку
// Явное преобразование
let integer = decimal as u8;
let character = integer as char;
println!("Casting: {} -> {} -> {}", decimal, integer, character);
}
5.2 Литералы
В числовых литералах тип может быть аннотирован, добавив тип в качестве суффикса, за исключением
uint
, использующей суффикс u
и int
, который использует суффикс i
.Тип литералов без суффикса будет зависеть от того, как они используются. Если никаких ограничений не существует, то компилятор выдаст сообщение об ошибке.
fn main() {
// Литералы с суффиксами, их вид известен при инициализации
let x = 1u8;
let y = 2u;
let z = 3f32;
// Литералы без суффикса, их вид зависит от того, как они используются
let i = 1;
let f = 1.0;
// `size_of_val` возвращает размер переменной в байтах
println!("size of `x` in bytes: {}", std::mem::size_of_val(&x));
println!("size of `y` in bytes: {}", std::mem::size_of_val(&y));
println!("size of `z` in bytes: {}", std::mem::size_of_val(&z));
println!("size of `i` in bytes: {}", std::mem::size_of_val(&i));
println!("size of `f` in bytes: {}", std::mem::size_of_val(&f));
// Ограничения (слагаемые должны иметь тот же тип) для `i` и `f`
let _constraint_i = x + i;
let _constraint_f = z + f;
// Закомментируйте эти две строки
}
Есть некоторые понятия, используемые в предыдущем коде, которые не были объяснены раньше, вот краткое объяснение для нетерпеливых читателей:
fun(&foo)
используется, чтобы передать аргумент в функцию по ссылке, а не по значениюfun(foo)
.std::mem::size_of_val
является функцией, но вызывается с указанием полного пути. Код можно разделить на логические единицы, называемые модулями. Здесь функцияsize_of_val
определена в модулеmem
, а модульmem
определен в крэйтеstd
.
5.3 Логический вывод
Логический вывод типов довольно умён. Тип добавляемой переменной используется как определитель типа для второй переменной. Вот продвинутый пример:
fn main() {
// Использование локального вывода, компилятор знает, что `elem` имеет тип `u8`
let elem = 5u8;
// Создадим пустой вектор (расширяемый массив)
let mut vec = Vec::new();
// В этот момент компилятор не знает точный тип `vec`, он
// просто знает, что это вектор `Vec<_>`
// Вставим `elem` в вектор
vec.push(elem);
// Ага! Теперь компилятор знает, что `vec` это вектор `u8` (`Vec<u8>`)
// Попробуйте закомментировать строку `vec.push(elem)`
println!("{}", vec);
}
Отсутствует необходимость в аннотации типа переменной, компилятор счастлив как и программист!
5.4 Псевдонимы (алиасы)
Оператор
type
может быть использован, чтобы задать новое имя существующему типу. Тип должен быть в стиле CamelCase, либо компилятор выдаст предупреждение. Исключением из этого правила являются примитивные типы: uint
, f32
и другие.// `NanoSecond` это новое имя для `u64`
type NanoSecond = u64;
type Inch = u64;
// Используйте этот атрибут, чтобы не выводить предупреждение
#[allow(non_camel_case_types)]
type uint64_t = u64;
// Попробуйте удалить атрибут
fn main() {
// `NanoSecond` = `Inch` = `uint64_t` = `u64`
let nanoseconds: NanoSecond = 5 as uint64_t;
let inches: Inch = 2 as uint64_t;
// Обратите внимание, что псевдонимы новых типов не предоставляют
// дополнительную безопасность, из-за того, что они не нового типа
println!("{} nanoseconds + {} inches = {} unit?",
nanoseconds,
inches,
nanoseconds + inches);
}
Основное применение псевдонимов это снижение количества кода, например, тип
IoResult является псевдонимом типа Result<T, IoError>
.
Заключение
Присоединяйтесь к google-группе: Rust по-русски для получения дополнительной информации по этому языку.
Можно помочь с переводом на Github: github.com/eg0r/rust-by-example
Все замечания, ошибки или неточности отправляйте мне в почту.