Структуры представляют составной тип данных, который представляет какую-то определенную сущность и который объединяет набор данных, описывающих эту сущность.
Для определения структуры применяется ключевое слово struct:
struct имя_структуры;
После ключевого слова struct указывается имя структуры. Например:
struct Person;
В данном случае определена структура Person. Структура представляет новый тип, поэтому, создав структуру, мы можем определить ее переменную:
struct Person;
fn main() {
let tom: Person = Person{};
// или так
// let tom = Person;
println!("Person Tom created");
}
Здесь определяется переменная tom, которая представляет тип Person. В качестве значения переменной присваивается Person{} - после названия структуры
идут пустые фигурные скобки. Также мы можем сократить определение, убрав фигурные скобки и определение типа:
let tom = Person;
Пока мы никак не используем структуру, и после создания ее переменной просто выводим некоторое диагностическое сообщение.
Одной из задач структур является хранение или объединение некоторого набора данных. Для этого структура определяется в следующем виде:
struct имя_структуры {
переменная1: тип_данных,
переменная2: тип_данных,
.......................
переменнаяN: тип_данных,
}
После имени структуры внутри фигурных скобок через запятую перечисляются переменные структуры. Переменные структуры еще называются полями. Для каждого поля определяется название и его тип.
Например, определение простейшей структуры:
struct Person // структура - человек
{
name: String, // имя
age: u8, // возраст
height: f32 // рост
}
В данном случае определена структура Person, которая представляет сущность "человек". Названия структур обычно начинаются с большой буквы. Эта структура имеет три поля, которые описывают соответственно имя, возраст и рост человек.
Обратите внимание, что имя человека - поле name представляет тип String, который представляет расширяемую строку и который также
является встроенной в Rust структурой. Таким образом, одни структуры могут быть полями других структур.
При инициализации такой переменной необходимо указать значения для всех полей структуры:
let tom = Person{
name: "Tom".to_string(),
age: 36,
height: 1.78
};
Здесь обращаю внимание, что значение для поля указывается после двоеточия (:), а не после знака равно, как при обычном присвоении. Кроме того,
полю name присваивается не просто строка "Tom", а результат метода "Tom".to_string(), который преобразует строковый литерал к
типу String.
Затем через имя переменной структуры мы сможем обращаться к ее полям:
название_переменной.поле_структуры
После названия переменной через точку указывается поле структуры.
Например, используем вышеописанную структуру в программе:
fn main(){
let tom = Person{
name: "Tom".to_string(),
age: 36,
height: 1.78
};
println!("name = {}", tom.name);
println!("age = {}", tom.age);
println!("height = {}", tom.height);
}
struct Person // структура - человек
{
name: String, // имя
age: u8, // возраст
height: f32 // рост
}
Консольный вывод программы:
name = Tom age = 36 height = 1.78
Если мы хотим изменять значения полей структуры, то переменная структуры должна быть определена с ключевым словом mut:
fn main(){
let mut tom = Person{
name: "Tom".to_string(),
age: 36,
height: 1.78
};
println!("name={} age = {} height={}", tom.name, tom.age, tom.height);
// меняем значения полей
tom.age = 25;
tom.height = 1.81;
println!("name={} age = {} height={}", tom.name, tom.age, tom.height);
}
struct Person // структура - человек
{
name: String, // имя
age: u8, // возраст
height: f32 // рост
}
Консольный вывод:
name = Tom age = 36 height = 1.78 name = Tom age = 25 height = 1.81
Если полям структуры передаются значения переменных или параметров метода, имена которых совпадают с именами полей структуры, то можно сократить инициализацию структуры.
fn main(){
let age = 22;
let height = 1.68;
let tom = Person{
name: "Tom".to_string(),
age,
height
};
println!("name={} age = {} height={}", tom.name, tom.age, tom.height);
}
struct Person
{
name: String,
age: u8,
height: f32
}
Здесь названия переменных age и height совпадает с названиями полей. Поэтому вместо
let age = 22;
let height = 1.68;
let tom = Person{
name: "Tom".to_string(),
age: age,
height: height
};
Можно написать:
let age = 22;
let height = 1.68;
let tom = Person{
name: "Tom".to_string(),
age,
height
};
Иногда возникает необходимость передать одной переменной структуры при инициализации значения другой переменной этой структуры. И тут Rust тоже позволяет сократить код:
fn main(){
let bob = Person{
name: "Bob".to_string(),
age: 33,
height: 1.70
};
let tom = Person{
name: "Tom".to_string(),
..bob
};
println!("name={} age = {} height={}", tom.name, tom.age, tom.height);
}
struct Person
{
name: String,
age: u8,
height: f32
}
Выражение ..bob указывает, что переменная структуры будет наполняться из переменной bob. Если мы хотим для отдельных полей
определить другие значения, то мы можем их прописать явно:
let tom = Person{
name: "Tom".to_string(),
..bob
};
В данном случае переменная tom берет из переменной bob значения для полей age и height, а для поля name определяет свое значение.
Rust позволяет раскладывать структуры на переменные. Для этого применяется оператор let:
let структура{поле_структуры: переменная} = объект_структуры;
Рассмотрим пример с декомпозицией структуры Person:
struct Person
{
name: String,
age: u8,
height: f32
}
fn main(){
let tom = Person{
name: "Tom".to_string(),
age: 33,
height: 1.70
};
let Person{name: username, age: userage, height: _} = tom;
println!("name = {} age = {}", username, userage);
}
Здесь выражение
let Person{name: username, age: userage, height: _} = tom;
указывает, что из структуры tom значение поля name надо передать в переменную username, а значение поля age - в
переменную userage. Переменные username и userage определяются автоматически. А поле height, к примеру, мы не хотим использовать, поэтому ему передается прочерк _.
И далее мы сможем использовать переменные username и userage.
Как и любой другой тип, структура может представлять параметр функции или ее возвращаемое значение. Например:
struct Person
{
name: String,
age: u8
}
fn print_person(person: Person){
println!("Name: {} Age: {}", person.name, person.age);
}
fn main() {
let tom: Person = Person{ name: "Tom".to_string(), age: 40};
print_person(tom); // передача владения структурой Person в функцию
}
Здесь функция print_person в качестве параметра принимает структуру Person и выводит значения ее полей на консоль. Однако обратите внимание, как передается структура -
при передаче структуры в функцию также передается владение этой структурой. Соответственно после вызова функции мы не сможем использовать структуру в функции main:
fn main() {
let tom = Person{ name: "Tom".to_string(), age: 40};
print_person(tom); // передача владения структурой Person в функцию
print_person(tom); // ! Ошибка - функция main уже не владеет структурой Person
}
Потому часто целесообразнее передавать структуру по ссылке, как и в случае с данными других типов:
struct Person
{
name: String,
age: u8
}
fn print_person(person: &Person){
println!("Name: {} Age: {}", person.name, person.age);
}
fn main() {
let tom: Person = Person{ name: "Tom".to_string(), age: 40};
print_person(&tom); // Name: Tom Age: 40
print_person(&tom); // Ошибки нет - Name: Tom Age: 40
}
Возвращение структуры из функции также не отличается от возвращения значений любых других типов:
struct Person
{
name: String,
age: u8
}
fn create_person(name: String, age: u8) -> Person{
Person {name, age}
}
fn main() {
let tom = create_person("Tom".to_string(), 40);
println!("Name: {} Age: {}", tom.name, tom.age); // Name: Tom Age: 40
}
Здесь функция create_person() принимает два параметра и из них создает структуру Person.