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 нельзя
} // Это приватная переменная в структуре.
}