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), когда в самой структуре данные хранить не нужно. Их смысл заключается в том, чтобы представлять типы или маркеры, которые используются для передачи информации о структуре программы или её логике на уровне типов, а не для хранения данных.
Иногда нужно реализовать трейт (интерфейс) для типа, который не требует хранения данных. Юнит-структуры идеально подходят для этого:
struct Empty;
impl std::fmt::Display for Empty {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Я пустая структура!") }}
fn main() {
let e = Empty;
println!("{}", e); // Вывод: "Я пустая структура!"
}Здесь Empty не хранит данных, но реализует интерфейс Display.
Структурные признаки
Можно выводить информацию о содержимом полей структуры для анализа кода. Для этого нужно добавить над структурой пометку 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 {
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width >= other.width && self.height >= other.height
}}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 51,
};
println!("Area: {}", rect1.area());
println!("Can hold: {}", rect1.can_hold(&rect2));
}Для внесения изменений в объект структуры, в блоке методов можно объявить &mut self, а для перемещения владения - просто self. Это нужно изредка при превращении self в другой вид объекта, с целью запретить вызов предыдущей версии объекта. Внутри impl используется &self, чтобы метод мог обращаться к полям текущего экземпляра структуры, не передавая его явно как аргумент. Блоков impl может быть несколько.
Асоциированные функции
В блоке методов impl можно добавлять функции, которые первым параметром не берут саму структуру self. Обычно используются как конструкторы (например, для создания нового экземпляра структуры) или для других операций, не требующих доступа к данным экземпляра. Они вызываются через синтаксис :: (например, Rectangle::square), а не через точку (.), как методы:
impl Rectangle {
fn square(size: u32) -> Rectangle {
Rectangle { width: size, height: size }
}
// Используется блок `impl Rectangle` — это реализация для структуры `Rectangle` (предполагается, что она определена где-то выше, например, как `struct Rectangle { width: u32, height: u32 }`).
}
fn main() {
let sq = Rectangle::square(5);
println!("Square area: {}", sq.area()); // Square area: 25
}Создание типа данных с проверками
Вместо проверять введённые данные на корректность внутри функций, можно объявить собственный тип данных, содержащий в себе все необходимые проверки. Например, объявим число от 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 } // возврат нового типа данных
}
. // метод getter для получения значения value:
pub fn value(&self) -> i32 {
self.value // он нужен, тк напрямую видеть value нельзя
} // Это приватная переменная в структуре.
}