Основы Rust: синтаксис и структуры данных

b4ca5b4a9e01a3474b0c5f787d2846ea.png

Привет, Хабр!

Rust — это язык, который становится плюс-минус более популярным в последние годы благодаря своей высокой производительности и безопасности. Он был разработан Mozilla и сообществом разработчиков с целью предоставить инструмент для системного программирования, который позволяет избегать многих распространенных ошибок, связанных с управлением памятью.

В этой статье рассмотрим основы Rust.

Настройка окружения

На сайте раста есть инструкции по установке Rust на любую ОС. Процесс установки прост и интуитивно понятен и ее может выполнить любой школьник.

После установки Rust и Cargo, убедимся, что они работают правильно. Открываем терминал и юзаем команду:

rustc --version

Команда должна вернуть текущую версию компилятора Rust, установленную на ПК.

Проверяем версию Cargo:

cargo --version

Если обе команды успешно возвращают версии инструментов, то все ок.

Создание первой программы на Rust

Для создания нового проекта на Rust, можно воспользоваться командой cargo new hello_rust. Cargo — это универсальный инструмент для управления зависимостями в расте. Команда создаст каталог с именем hello_rust, который будет содержать структуру проекта Rust. Внутри этого каталога лежит файл main.rs, в нем напишем код:

По классике поздорваемся с миром:

fn main() {
    println!("Hello, World!");
}

После этого можно перейти в терминал, перейти в каталог проекта и выполнить команду cargo run

Синтаксис Rust

Переменные и их мутабельность

Одна из особенностей Rust — это строгая система типов, которая помогает избежать множества ошибок на этапе компиляции:

let x = 42; // Неизменяемая переменная
let mut y = 24; // Изменяемая переменная

let используется для объявления переменных. Первая переменная x неизменяема, что означает, что после присвоения значения 42, она не может быть изменена. Вторая переменная y объявлена как изменяемая, поэтому можно изменять ее значение.

Операторы и контроль потока

Rust поддерживает все базовые операторы, включая арифметические, логические и сравнительные операторы:

fn ariph() {
    let a = 10;
    let b = 3;
    
    let sum = a + b; // Сложение
    let difference = a - b; // Вычитание
    let product = a * b; // Умножение
    let quotient = a / b; // Деление
    let remainder = a % b; // Остаток от деления

fn logic() {
    let is_sunny = true;
    let is_warm = false;
    
    let is_good_weather = is_sunny && is_warm; // Логическое И
    let is_raining = !is_sunny; // Логическое НЕ
}

fn sravn() {
    let x = 5;
    let y = 10;
    
    let is_equal = x == y; // Равно
    let is_not_equal = x != y; // Не равно
    let is_greater = x > y; // Больше
    let is_less = x < y; // Меньше
    let is_greater_or_equal = x >= y; // Больше или равно
    let is_less_or_equal = x <= y; // Меньше или равно
}

Условные операторы: if, else if, else

fn main() {
    let age = 25;

    if age < 18 {
        println!("Вы несовершеннолетний");
    } else if age >= 18 && age < 65 {
        println!("Вы взрослый");
    } else {
        println!("Вы пенсионер");
    }
}

Циклы: for, while

Циклы позволяют нам выполнять определенные блоки кода несколько раз. В Rust есть два основных типа циклов: for и while:

fn main() {
    // Цикл for
    for i in 1..=5 {
        println!("Итерация {}", i);
    }

    // Цикл while
    let mut counter = 0;
    while counter < 3 {
        println!("Повторение {}", counter);
        counter += 1;
    }
}

В for используем диапазон чисел 1..=5 для выполнения блока кода пять раз. В случае цикла while, выполняем блок кода, пока значение переменной counter меньше 3, и увеличиваем его после каждой итерации.

Match-выражения

Match-выражения позволяют сравнивать значение с несколькими возможными вариантами и выполнять соответствующий блок кода:

fn main() {
    let language = "Rust";

    match language {
        "Rust" => println!("Вы выбрали Rust - отличный выбор!"),
        "Python" | "JavaScript" => println!("Тоже хорошие языки!"),
        _ => println!("Неизвестный язык программирования"),
    }
}

Если значение соответствует «Rust», выводим сообщение о выборе Rust. Если оно соответствует «Python» или «JavaScript», выводим общее сообщение. В противном случае, используем _, чтобы обработать все остальные значения.

Объявление функций

fn main() {
    // Вызов функции
    greet("Alice");

    // Вызов функции с возвращаемым значением
    let result = add(3, 5);
    println!("Сумма: {}", result);
}

// Объявление функции без возвращаемого значения
fn greet(name: &str) {
    println!("Привет, {}!", name);
}

// Объявление функции с возвращаемым значением
fn add(a: i32, b: i32) -> i32 {
    a + b
}

Параметры функций

fn main() {
    let a = 5;
    let b = 3;
    let sum = add(a, b);
    println!("Сумма: {}", sum);
}

fn add(x: i32, y: i32) -> i32 {
    x + y
}

add принимает два параметра x и y, их значения передаются из функции main при вызове функции add.

Возвращаемые значения функций

Возвращаемый тип указывается после параметров функции и определяет тип данных, который функция вернет:

fn main() {
    let result = multiply(4, 6);
    println!("Произведение: {}", result);
}

fn multiply(a: i32, b: i32) -> i32 {
    a * b // Это выражение будет возвращено из функции
}

multiply возвращает произведение двух чисел типа i32.

Структуры данных

Целые числа

let integer: i32 = 42; // 32-битное целое число со знаком
let unsigned_integer: u64 = 100; // 64-битное целое число без знака

Числа с плавающей точкой

let float: f32 = 3.14159265359; // 32-битное число с плавающей точкой
let double: f64 = 2.71828182845; // 64-битное число с плавающей точкой (по умолчанию)

Булевы значения

let is_rust_cool: bool = true; // Логическое значение true
let is_open_source: bool = false; // Логическое значение false

Символы и строки

let char_type: char = 'A'; // Символ
let string_type: &str = "Hello, Rust!"; // Строка (неизменяемая)

Векторы

В Rust векторы можно создавать и изменять в размере по мере необходимости:

fn main() {
    // Создание вектора целых чисел
    let mut numbers: Vec = Vec::new();
    
    // Добавление элементов в вектор
    numbers.push(42);
    numbers.push(24);
    numbers.push(12);
    
    // Доступ к элементам вектора
    let first = numbers[0];
    
    // Итерация по вектору
    for num in &numbers {
        println!("Число: {}", num);
    }
    
    // Размер вектора
    let length = numbers.len();
    
    // Удаление элемента из вектора
    let removed = numbers.pop();
}

Создали пустой вектор numbers для хранения целых чисел, добавили элементы в него с помощью метода push, получили доступ к элементу по индексу и прошлись по всем элементам вектора.

Массивы

fn main() {
    // Создание массива строк
    let colors: [&str; 3] = ["Red", "Green", "Blue"];
    
    // Доступ к элементам массива
    let first_color = colors[0];
    
    // Итерация по массиву
    for color in &colors {
        println!("Цвет: {}", color);
    }
    
    // Размер массива (известен на этапе компиляции)
    let length = colors.len();
}

Создали массив colors, содержащий три строки. Массивы в Rust имеют фиксированный размер, который задается при объявлении.

Какой контейнер выбрать — вектор или массив? Выбор зависит от конкретной задачи. Если нужна динамичность и возможность изменять размер данных во времени, то векторы оч. хорошо подходят. Если же вы знаете точный размер данных заранее, то массивы более адекватным выбором.

Структуры

Структуры — это способ объединить несколько переменных разных типов в одну логическую единицу:

// Определение структуры Point
struct Point {
    x: f64,
    y: f64,
}

fn main() {
    // Создание экземпляра структуры Point
    let origin = Point { x: 0.0, y: 0.0 };
    
    // Доступ к полям структуры
    println!("Координаты точки: ({}, {})", origin.x, origin.y);
}

Определили структуру Point, содержащую два поля типа f64 — x и y. Затем создали экземпляр структуры origin и получили доступ к полям.

Перечисления

// Определение перечисления Animal
enum Animal {
    Dog,
    Cat,
    Bird,
}

fn main() {
    // Использование перечисления
    let pet = Animal::Dog;
    
    // Проверка значения перечисления
    match pet {
        Animal::Dog => println!("Это собачка!"),
        Animal::Cat => println!("Это котик!"),
        Animal::Bird => println!("Это птиц!"),
    }
}

Определили перечисление Animal, которое может принимать одно из трех значений. Затем создали переменную pet и с помощью match сравнили ее значение с вариантами перечисления.

Если нужно организовать данные с разными полями, то выбор здесь — структура. Если идет работа с вариативными данными или определяете состояния объекта, то перечисления придутся как нельзя кстати.

Также в Rust реализовано хорошее и безопасное управление памятью, у меня есть статья на эту тему: как работает управление памятью в Rust без сборщика мусора.

В целом Rust очень даже хороший ЯП и набирает свою популярность.

Подробнее про Rust и другие языки программирования, мои друзья из OTUS рассказывают в рамках практических онлайн-курсов. С полным списком курсов можно ознакомиться в каталоге.

© Habrahabr.ru