Structures

Struct Data Type

Struct - комплексный изменяемый тип данных, размещается в куче (heap), содержит внутри себя разные типы данных. Он похож на кортеж (tuple), однако типы данных должны иметь явные именования.

struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64, // запятая в конце обязательна
}

Можно передать struct в функцию или вернуть из функции:

fn main() {
// создаём изменяяемый объект по структуре данных выше
let mut user1 = create_user(String::from("john@doe.com"), String::from("testuser")); 

println!("User Email is {}", user1.email);
user1.email = String::from("Parker@doe.com");
println!("User Email is {}", user1.email);
}

fn create_user(email: String, username: String) -> User { // возврат из функции
User {
active: true,
username,  
email,
// имена полей имеют с входными переменными, и можно не писать username: username, email: email.
sign_in_count: 1,
} // return заменяется отсутствием знака ";"" как обычно 
}

Updating Structs

Если нужно создать новую структуру по подобию старой, и большая часть полей у них похожи, то можно использовать синтаксический сахар:

let user2 = User {
email: String::from("another@example.com"), // задать новое значение поля
..user1 // взять остальные атрибуты из user1. Идёт последней записью
};

Tuple structs

Структуры такого вида похожи на кортежи, но имеют имя структуры и тип. Нужны, когда нужно выделить кортеж отдельным типом, либо когда надо дать общее имя кортежу. При этом отдельные поля не имеют имён.

struct Color (i32, i32, i32);
struct Point (i32, i32, i32);

fn main() {
let red = Color(255,0,0);
let origin = Point(0, 0, 0);

Переменные red и origin разных типов. Функции, которые берут Color как параметр, не возьмут Point, несмотря на одинаковые типы внутри. Каждая структура = свой собственный тип. Разбор таких структур на элементы аналогичен кортежам.

let (x,y,z) = (origin.0,origin.1,origin.2);

Unit-like structs

Структуры без полей аналогичны кортежам без полей, только с именем.

struct TestTrait;

fn main() {
 test = TestTrait;
}

Такие структуры нужны для задания признаков (traits), когда в самой структуре данные хранить не нужно.

Структурные признаки

Можно выводить информацию о содержимом полей структуры для анализа кода. Для этого нужно добавить над структурой пометку debug:

#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}

fn main() {
  let scale = 2;
  let rect = Rectangle {
  width: dbg!(20 * scale), // вывод поля структуры. dbg! возвращает назад
  height: 10,              // взятое значение, с ним далее можно работать
  };
  println!("Rectangle content: {:?}",rect); // вывод содержимого структуры
  dbg!(&rect); // ещё вариант вывода - в поток stderr. Функция dbg! 
               // забирает владение структурой, поэтому передача по ссылке
}

Структурные методы

Можно добавлять функции как методы, привязанные к структурам. Это позволяет организовать код более чётко - по объектам и действиям над ними.

struct Rectangle {
width: u32,
height: u32,
}

impl Rectangle {  // impl определяет блок методов структуры Rectangle
fn area(&self, scale) -> u32 { // 1-ый параметр всегда self = структура
 self.width * self.height * scale // тело функции и возврат значения
} }

fn main() {
  let rect = Rectangle {
  width: 20,
  height: 10,
  };
  println!("Rectangle area is {}", rect.area(2)); // вызов метода
}

Как и везде, для внесения изменений в объект структуры, в блоке методов можно объявить &mut self, а для перемещения владения - просто self. Это нужно изредка при превращении self в другой вид объекта, с целью запретить вызов предыдущей версии объекта. Блоков impl может быть несколько.

Асоциированные функции

В блоке методов impl можно добавлять функции, которые первым параметром не берут саму структуру self. Такие функции не являются методами и часто служат для создания новых версий объекта.

fn square(side: u32) -> Self { // Self - алиас для типа данных Rectangle
  Self {
    width: side,
    height: side,
} } }

fn main() {
  let sq = Rectangle::square(10); // вызов асоциированной функции через ::
  println!("Square created from {:?}",sq);
}

Создание типа данных с проверками

Вместо проверять введённые данные на корректность внутри функций, можно объявить собственный тип данных, содержащий в себе все необходимые проверки. Например, объявим число от 1 до 100 для игры, где надо угадать число:

pub struct Guess { // объявили тип данных (публичный)
    value: i32,    // внутри число (приватное)
}

impl Guess {
    pub fn new(value: i32) -> Guess { // метод new проверяет значение
        if value < 1 || value > 100 { // на заданные границы 1-100
            panic!("Guess value must be between 1 and 100, got {}.", value);
        }
        Guess { value } // возврат нового типа данных
    }

    pub fn value(&self) -> i32 { // метод getter для получения значения value
        self.value               // он нужен, тк напрямую видеть value нельзя
    }                            // Это приватная переменная в структуре.
}