Привет, Rustations, спасибо, что вернулись за частью 3! Rust — прекрасный язык системного программирования, который, как известно, «труден для изучения». В этой серии мы разобьем его концепции на простые для понимания части с ключевыми выводами. Надежда этой серии состоит в том, чтобы заинтересовать больше людей ржавчиной и увидеть, насколько она удивительна на самом деле. В этой статье мы поговорим о структурах в rust.

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

Определение структур

Чтобы определить структуру в Rust, вы используете ключевое слово struct, за которым следует имя структуры, а затем поля структуры в фигурных скобках. Поля могут быть любого типа данных, включая скалярные типы, такие как i32 и f64, и сложные типы, такие как массивы и другие структуры. Вот пример простого определения структуры в Rust:

struct Point {
    x: i32,
    y: i32,
}

Это определяет структуру с именем Point с двумя полями, x и y, оба типа i32.

Создание структур

Чтобы создать экземпляр структуры, вы используете имя структуры, за которым следуют фигурные скобки, содержащие значения для каждого из полей. Например:

let p = Point { x: 2, y: 3 };

Это создает новую структуру Point, где x равно 2, а y равно 3.

Доступ к полям структуры

Когда у вас есть структура, вы можете получить доступ к ее полям, используя запись через точку. Например:

println!("The value of x is {}", p.x);
println!("The value of y is {}", p.y);

Это напечатает значения полей x и y структуры p.

Изменение полей структуры

Вы можете изменять поля структуры, присваивая им новые значения. Например:

p.x = 4;
p.y = 5;

Это изменяет значения полей x и y структуры p на 4 и 5 соответственно.

Синтаксис обновления структуры

Rust предоставляет удобный синтаксис обновления для создания новой структуры на основе существующей, но с измененными некоторыми полями. Например:

let p2 = Point { x: 1, ..p };
println!("The value of x in p2 is {}", p2.x);
println!("The value of y in p2 is {}", p2.y);

Это создает новую структуру Point, p2, с полем x, установленным на 1, а остальные поля копируются из существующей структуры p.

Кортежные структуры

В дополнение к именованным полям Rust также поддерживает структуры кортежей, то есть структуры с неименованными полями. Структуры кортежей определяются с помощью ключевого слова struct, за которым следует имя структуры, а затем поля в круглых скобках. Например:

struct Color(i32, i32, i32);

let red = Color(255, 0, 0);
println!("The value of red is ({}, {}, {})", red.0, red.1, red.2);

Этот код определяет структуру кортежа Color с тремя полями и создает экземпляр Color со значениями 255, 0 и 0. Обратите внимание, что доступ к полям в структуре кортежа осуществляется с помощью индексации, а не имен полей, как в этом примере.

Структуры юнитов

Структуры модулей — это частный случай структур кортежей с нулевыми полями. Структуры модулей определяются с помощью ключевого слова struct, за которым следует имя структуры и пустой набор скобок. Например:

struct Empty;

let e = Empty;

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

Методы

В дополнение к данным, структуры также могут иметь связанные с ними методы. Методы — это функции, которые определены в структуре и могут обращаться к данным в структуре. Чтобы определить метод в структуре, вы используете ключевое слово fn, за которым следует имя метода и тело метода в фигурных скобках. Тело метода имеет доступ к данным в структуре через ключевое слово self. Например:

struct Point {
    x: i32,
    y: i32,
}

impl Point {
    fn magnitude(&self) -> f64 {
        let x_squared = self.x * self.x;
        let y_squared = self.y * self.y;
        (x_squared + y_squared) as f64
    }
}

let p = Point { x: 3, y: 4 };
println!("The magnitude of p is {}", p.magnitude());

Это определяет метод magnitude в структуре Point, который вычисляет величину точки на основе ее полей x и y.

Владение структурой и заимствование

В Rust все значения имеют одного владельца, и право собственности может быть передано от одного значения к другому посредством присваивания или вызовов функций. При создании структуры ее поля также имеют право собственности, и правила владения и заимствования применяются к полям структуры так же, как и к любым другим значениям. Например:

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = p1;
    println!("The value of x in p1 is {}", p1.x);
}

Этот код приведет к ошибке компиляции, поскольку p1 больше недействителен после присвоения p2. Чтобы избежать этой проблемы, вы можете использовать заимствование, чтобы позволить как p1, так и p2 получать доступ к данным в структуре без передачи права собственности. Например:

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = &p1;
    println!("The value of x in p1 is {}", p1.x);
    println!("The value of x in p2 is {}", p2.x);
}

Этот код будет компилироваться и работать правильно, потому что p2 заимствует ссылку на данные в p1, а не владеет данными. Если вас смущают понятия владения и заимствования в ржавчине, мы подробно рассмотрим это в будущем.

Расширенные функции структуры

Rust предоставляет несколько дополнительных функций для структур, в том числе:

Получение признаков

Вы можете использовать синтаксис #[derive(Trait)] для автоматического получения определенных признаков для структуры, таких как Debug, Clone, Copy и другие. Например:

#[derive(Debug)]
struct Point {
    x: i32,
    y: i32,
}

let p = Point { x: 1, y: 2 };
println!("{:?}", p);

Этот код автоматически выводит трейт Debug для структуры Point, что позволяет легко печатать экземпляры структуры с помощью макроса println!. Мы подробно рассмотрим черты в следующем уроке.

Функции инициализатора

Вы можете определить функцию инициализации (также известную как «конструктор») для структуры, используя метод new. Например:

struct Point {
    x: i32,
    y: i32,
}

impl Point {
    fn new(x: i32, y: i32) -> Point {
        Point { x, y }
    }
}

let p = Point::new(1, 2);
println!("{:?}", p);

Этот код определяет функцию инициализации для структуры Point, которая принимает два аргумента, x и y, и возвращает новый экземпляр структуры.

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

Вы можете использовать перечисления, чтобы определить набор связанных вариантов для структуры. Например:

enum Shape {
    Circle(Point, f32),
    Rectangle(Point, Point),
}

let s = Shape::Circle(Point { x: 0, y: 0 }, 1.0);
println!("{:?}", s);

Этот код определяет перечисление Shape с двумя вариантами, Circle и Rectangle. Каждый вариант содержит данные, связанные с вариантом, такие как центральная точка и радиус для варианта Circle или две угловые точки для варианта Rectangle.

Союзы

Еще одной расширенной функцией Rust являются объединения, которые похожи на структуры, но позволяют хранить данные разных типов в одной и той же ячейке памяти. Объединения определяются с помощью ключевого слова union, за которым следует имя объединения и поля в фигурных скобках. Например:

union IntOrFloat {
    i: i32,
    f: f32,
}

let u = IntOrFloat { i: 42 };
println!("The value of u is {}", unsafe { u.i });

Этот код определяет объединение IntOrFloat с двумя полями, i и f, и создает экземпляр объединения со значением 42 в поле i. Обратите внимание, что для доступа к полям объединения требуется использование ключевого слова unsafe, поскольку содержимое объединения считается неопределенным, если установлено более одного поля.

Дженерики

В Rust структуры также можно использовать в сочетании с другими функциями языка для создания более сложных структур данных. Например, вы можете использовать структуры в сочетании с универсальными шаблонами для создания параметризованных структур, которые могут работать с любым типом данных. Вот пример:

struct Point<T> {
    x: T,
    y: T,
}

let p = Point { x: 1, y: 2 };
println!("{:?}", p);

let p = Point { x: 1.0, y: 2.0 };
println!("{:?}", p);

В этом примере структура Point параметризована типом T, который может быть любым типом, реализующим необходимые черты. Это позволяет вам создавать экземпляры Point либо с целыми числами, либо с числами с плавающей запятой, в зависимости от ваших потребностей.

Использование макросов

Еще один расширенный вариант использования структур в Rust — их использование в сочетании с макросами. Макросы — это мощная функция Rust, позволяющая динамически генерировать код во время компиляции. Например, вы можете использовать макрос для автоматической генерации метода new для структуры или для реализации типажа для структуры. Вот пример макроса, который генерирует метод new для структуры:

macro_rules! struct_new {
    ($struct_name:ident { $( $field_name:ident : $field_type:ty ),* }) => {
        impl $struct_name {
            fn new($( $field_name : $field_type ),*) -> Self {
                $struct_name { $( $field_name ),* }
            }
        }
    }
}

struct Point {
    x: i32,
    y: i32,
}

struct_new!(Point { x: i32, y: i32 });

let p = Point::new(1, 2);
println!("{:?}", p);

В этом примере макрос struct_new! создает реализацию метода new для структуры Point, используя поля и типы, указанные в вызове макроса. Это может сэкономить вам много времени и усилий по сравнению с написанием реализации вручную.

Сериализация и десериализация

Одним из распространенных вариантов использования структур в Rust является сериализация и десериализация данных. Сериализация — это процесс преобразования данных в формат, который можно хранить или передавать по сети, а десериализация — это процесс преобразования сериализованных данных обратно в исходный формат.

Rust предоставляет несколько библиотек для сериализации и десериализации, например serde и bincode. serde — популярный выбор благодаря своей гибкости и поддержке различных форматов данных, включая JSON, YAML и BSON. Вот пример использования serde для сериализации и десериализации структуры:

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Point {
    x: i32,
    y: i32,
}

let p = Point { x: 1, y: 2 };

let serialized = serde_json::to_string(&p).unwrap();
println!("Serialized: {}", serialized);

let deserialized: Point = serde_json::from_str(&serialized).unwrap();
println!("Deserialized: {:?}", deserialized);

В этом примере структура Point снабжена аннотацией #[derive(Serialize, Deserialize, Debug)], которая автоматически генерирует реализации трейтов Serialize и Deserialize для структуры. Это позволяет использовать библиотеку serde_json для простой сериализации и десериализации экземпляров структуры Point.

Заключение

В этой статье мы рассмотрели основы структур в Rust, в том числе то, как определять структуры с именованными и неименованными полями, как определять методы в структурах и как управлять владением и заимствованием данных структур. Независимо от того, являетесь ли вы новичком или опытным программистом на Rust, понимание структур является важной частью разработки эффективного, действенного и удобного в сопровождении кода на Rust. Итак, строите ли вы простую структуру данных или сложную систему, не забывайте об этих концепциях при работе со структурами в Rust! Если вам понравился этот урок и вы хотите увидеть больше, пожалуйста, похлопайте в ладоши и поделитесь этой статьей в социальных сетях! Спасибо за чтение!

Спасибо, что прочитали, пожалуйста 👏 и поделитесь, чтобы помочь другим найти это. Подпишитесь на меня в Твиттере, чтобы не пропустить новый контент @gregcodesstuff

До скорой встречи. 😃

Подпишитесь на DDIntel Здесь.

Посетите наш сайт здесь: https://www.datadriveninvestor.com

Присоединяйтесь к нашей сети здесь: https://datadriveninvestor.com/collaborate