Структуры — одна из самых полезных функций, которые предоставляет Rust. Они дают программистам возможность моделировать сложные структуры данных. Кроме того, они позволяют программистам абстрагировать объекты, вовлеченные в проблему. В этой статье мы рассмотрим их основы и узнаем, как с их помощью можно улучшить наш код.
Предварительные условия
Прежде чем вы полностью поймете эту статью, вам будет полезно знать следующее.
- У вас должно быть базовое понимание языка Rust
- У вас также должен быть установлен Rust версии 1.0 и выше.
Что такое структура?
Это ключевое слово, используемое в Rust для построения структур данных. Эти структуры используются для группировки связанных данных вместе. Возможность хранения связанных данных может значительно ускорить разработку и выполнение вашего кода. Помимо улучшения этих показателей, они также помогают в отладке и понимании того, как часть данных взаимодействует с другими частями.
Структуры данных — это основа разработки программного обеспечения. Они помогают организовать данные в нашем коде. С помощью правильных структур данных наш код может легко занимать либо меньше места в памяти, либо меньше времени выполнения, либо и то, и другое. Это особенно полезно при попытке создать масштабируемые системы, обслуживающие миллионы пользователей одновременно.
Типы
В rust существует три различных типа структур: полевые структуры, кортежные структуры и единичные структуры.
Полевые структуры
В этом типе структур каждое поле имеет имя и тип данных, которые оно хранит. Это похоже на обычные структуры, которые вы видите в C. В Rust мы определяем их с помощью ключевого слова struct. Например, мы создадим структуру Person. Эта структура имеет свойства имя и возраст.
struct Person {
name: String,
age: u8,
}
В этом фрагменте свойства называются полями. Поля этих структур определяются в паре фигурных скобок «{ }» и разделяются запятой.
После того как мы определили структуру, мы можем инициализировать ее. Инициализация — это процесс создания экземпляра структуры. Структура подобна чертежу, а экземпляр — это адаптация чертежа. Это похоже на объекты в объектно-ориентированном программировании. Несмотря на сходство, все же есть некоторые различия. Поскольку Rust не является языком такого типа, некоторые основные возможности, такие как наследование, конструирование и деконструкция, не поддерживаются.
Чтобы инициализировать приведенную выше структуру, сделаем следующее.
let person = Person {
name: "John".to_string(),
age: 23,
};
В этом примере мы инициализируем имя человека как «Джон» и задаем возраст 23 года. Теперь мы можем получить доступ к полям этой структуры. Мы получим имя человека с помощью person.name и возраст с помощью person.age.
println!("Name: {}nAge: {}", person.name, person.age);
И мы можем получить полноценную программу, как показано ниже.
struct Person {
name: String,
age: u8,
}
fn main() {
let person = Person {
name: "John".to_string(),
age: 23,
};
println!("Name: {}nAge: {}", person.name, person.age);
}
Когда мы запустим это в консоли, мы получим следующий результат.
Name: John
Age: 23
Структуры кортежей
Это структуры, которые хранят данные в кортежеподобном формате. Кортеж — это последовательный набор данных. Хранимые данные могут быть как одинакового, так и разного типа. Структура обычно дает имя кортежу. На примере человека мы объявим его кортежную структуру.
struct Person (String, u8);
Теперь, когда мы ее создали, мы можем ее инициализировать. Процесс инициализации этой структуры похож на обычный кортеж. Единственное отличие заключается в том, что мы помещаем имя структуры перед круглыми скобками.
let person = Person ("John".to_string(), 23);
Мы обращаемся к элементам этой структуры, используя позицию, в которой они находятся внутри кортежа. Это то же самое, что и с обычными кортежами. Мы обращаемся к имени человека, используя person.0, и к возрасту, используя person.1.
println!("Name: {}nAge: {}", person.0, person.1);
Используя эту концепцию, мы можем создать простую программу следующим образом.
struct Person (String, u8);
fn main() {
let person = Person ("John".to_string(), 23);
println!("Name: {}nAge: {}", person.0, person.1);
}
Единичные структуры
Эти типы структур не содержат никаких данных. Поэтому их размер равен нулю байт. Они полезны, когда мы хотим сгруппировать связанные функции вместе без необходимости хранить какие-либо данные. Вы увидите, как мы можем использовать их для этого позже в этом учебнике.
Мы объявляем их с помощью ключевого слова struct следующим образом.
struct Unit;
После объявления мы можем инициализировать его именем struct, приведенным ниже.
let unit_struct = Unit;
Изменяемость структур
Помимо создания и использования структур, Rust также дает нам возможность изменять и модифицировать их. Это полезная возможность, если наша структура должна измениться во время выполнения кода.
Хорошим примером может служить активация учетной записи пользователя. Учетная запись пользователя будет представлять собой структуру данных. В этой структуре у нас будет имя и поле is_active.
struct User {
name: String,
is_active: bool,
}
Мы инициализируем его с помощью ключевого слова mut. Это ключевое слово является сокращением от mutable. Это означает, что мы делаем нашу переменную мутабельной. Это означает, что она открыта для модификаций. Без него Rust не позволит переменной измениться после инициализации.
let mut user = User {
name: "John".to_string(),
is_active: false,
};
После инициализации этой структуры как mutable мы можем изменить поле is_active.
user.is_active = true;
Теперь мы можем получить простой код, подобный этому.
struct User {
name: String,
is_active: bool,
}
fn main() {
let mut user = User {
name: "John".to_string(),
is_active: false,
};
println!("{} is active: {}", user.name, user.is_active);
// Perform some processes needed
// to activate the user's account.
// Then activate it
user.is_active = true;
println!("{} is active: {}", user.name, user.is_active);
}
Когда мы запустим приведенный выше код в терминале, мы получим следующий результат.
John is active: false
John is active: true
Передача и возврат из функций
Структуры также можно передавать в функции и возвращать из них. Это повышает уровень абстракции, который мы можем реализовать в нашем коде. Более высокий уровень абстракции может быть действительно полезен для облегчения понимания кода. Абстрагируя наш код, мы группируем действия более низкого уровня в одну операцию высокого уровня. Например, мы создадим функцию под названием create_person. Эта функция будет создавать новый экземпляр struct при каждом вызове.
fn create_person(name: String, age: u8) -> Person {
return Person {
name: name,
age: age,
};
}
Благодаря этому в нашем коде мы сможем создать экземпляр, передав нашей функции имя и возраст. Затем наша функция вернет экземпляр структуры Person. В Rust существует сокращение, которое мы можем использовать для возврата данных из функций. Это удаление точки с запятой из последнего выражения. Если мы сделаем это, то нам не понадобится использовать return.
fn create_person(name: String, age: u8) -> Person {
Person {
name: name,
age: age,
}
}
Чтобы передать struct в функцию, мы устанавливаем тип данных аргумента в имя struct. Например, мы создадим функцию display, которая выведет struct на терминал.
fn display(person: Person) {
println!("Name: {}nAge: {}", person.name, person.age);
}
В этом примере мы создали аргумент person со структурой типа Person. Именно сюда мы будем передавать нашу структуру. Затем мы можем получить доступ к ее элементам. Теперь у нас есть полноценная программа.
struct Person {
name: String,
age: u8,
}
fn main() {
let person = create_person("John".to_string(), 23);
display(person);
}
fn create_person(name: String, age: u8) -> Person {
Person {
name: name,
age: age,
}
}
fn display(person: Person) {
println!("Name: {}nAge: {}", person.name, person.age);
}
Использование impl
Иногда, когда мы создаем функции, взаимодействующие с нашими структурами, они обычно смешиваются с другими функциями. Такой беспорядок затрудняет понимание того, что делает наш код. К счастью, Rust позволяет нам группировать функции, непосредственно связанные со структурой, вместе. Это делается с помощью блока impl. Например, мы начинаем с нашего struct.
struct Person {
name: String,
age: u8,
}
Затем у нас есть блок impl для определения функций, которые мы будем использовать с этой структурой.
impl Person {
fn create_person(name: String, age: u8) -> Person {
Person {
name: name,
age: age,
}
}
fn display(&self) {
println!("Name: {}nAge: {}", self.name, self.age);
}
}
В приведенном выше фрагменте мы добавили имя изменяемой нами структуры сразу после ключевого слова impl. Это говорит Rust, что любая функция, объявленная в блоке, будет присоединена к этой структуре.
В нашем примере мы создали два типа функций для struct. Первая — это статическая функция, а вторая — метод. Статические функции — это функции, доступ к которым осуществляется непосредственно из struct, а не из его экземпляра. Статическая функция в нашем коде — create_person, и доступ к ней осуществляется следующим образом.
let person = Person::create_person("John".to_string(), 23);
Методы — это тип функции, которая принадлежит экземпляру структуры. Мы объявляем метод, добавляя &self в качестве первого аргумента функции. А затем мы можем получить доступ к элементам экземпляра struct, который его вызывает, используя self.property_name. Метод в нашем коде называется display, и мы оцениваем его следующим образом.
person.display();
Простая программа, основанная на предыдущем примере.
struct Person {
name: String,
age: u8,
}
impl Person {
fn create_person(name: String, age: u8) -> Person {
return Person {
name: name,
age: age,
};
}
fn display(&self) {
println!("Name: {}nAge: {}", self.name, self.age);
}
}
fn main() {
let person = Person::create_person("John".to_string(), 23);
person.display();
}
Наконец, мы можем использовать единичные структуры для хранения связанных функций. Доступ к функциям будет осуществляться как к пространству имен. Эти функции статичны и будут создаваться в блоке impl. Например, возьмем такую простую функцию.
struct SomeFunctions;
impl SomeFunctions {
fn print_hello() {
println!("Hello");
}
fn print_goodbye() {
println!("Goodbye");
}
}
К ней можно получить доступ следующим образом.
SomeFunctions::print_hello();
SomeFunctions::print_goodbye();
Заключение
В этом уроке мы рассмотрели процесс создания структуры данных с помощью struct. Затем мы также добавили больше функциональности к структурам. И мы увидели, как мы можем использовать пустые структуры для создания модулей для организации нашего кода.
Надеюсь, это поможет понять, как работают структуры в rust и как мы можем использовать их для улучшения качества нашего кода.
Спасибо за прочтение и счастливого хакинга!