- Объявлять структуру, создавать её экземпляр и печатать через
#[derive(Debug)] - Писать в блоке
implметоды с&selfи&mut selfи ассоциированные функции - Создавать
enumс вариантами, несущими данные, и обрабатывать его исчерпывающимmatch - Безопасно работать с возможно отсутствующим значением через
Option,if letиunwrap_or
В электронном журнале у каждого ученика есть имя, класс и средний балл — хранить эти три значения в отдельных переменных неудобно, их нужно держать вместе. А светофор в каждый момент находится ровно в одном состоянии: красный, жёлтый или зелёный. Для первого случая в Rust есть структуры (struct), для второго — перечисления (enum).
Структуры
Структура — тип с именованными полями. Имя типа пишут в стиле PascalCase, а поля — в snake_case. При создании экземпляра все поля должны получить значения, а к полю обращаются через точку: aysel.score. Чтобы изменить поле, mut должен быть весь экземпляр — отдельное поле сделать mut в Rust нельзя.
#[derive(Debug)]
struct Student {
name: String,
grade: u8,
score: f64,
}
fn main() {
let mut aysel = Student {
name: String::from("Aysel"),
grade: 9,
score: 91.5,
};
aysel.score += 2.0;
println!("{} is in grade {}", aysel.name, aysel.grade);
println!("{:?}", aysel);
println!("{:#?}", aysel);
}Aysel is in grade 9
Student { name: "Aysel", grade: 9, score: 93.5 }
Student {
name: "Aysel",
grade: 9,
score: 93.5,
}#[derive(Debug)] учит компилятор печатать структуру через {:?}. А {:#?} выводит её аккуратно, каждое поле на отдельной строке.Методы и impl
Функции, относящиеся к типу, пишут в блоке impl. Функция, первый параметр которой — self, называется методом и вызывается через точку: room.area(). Функция без параметра self — ассоциированная функция, её вызывают через ::: Rectangle::new(4, 3) — именно такая и String::from. Внутри impl запись Self заменяет имя самого типа.
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn new(width: u32, height: u32) -> Self {
Self { width, height }
}
fn area(&self) -> u32 {
self.width * self.height
}
fn scale(&mut self, factor: u32) {
self.width *= factor;
self.height *= factor;
}
}
fn main() {
let mut room = Rectangle::new(4, 3);
println!("Area: {}", room.area());
room.scale(2);
println!("Scaled: {}x{}, area {}", room.width, room.height, room.area());
}Area: 12 Scaled: 8x6, area 48
| Первый параметр | Значение | Вызов |
|---|---|---|
&self | только читает экземпляр | room.area() |
&mut self | изменяет экземпляр | room.scale(2) |
self | забирает владение, после вызова экземпляр недоступен | text.into_bytes() |
нет self | ассоциированная функция, часто конструктор | Rectangle::new(4, 3) |
Перечисления и match
enum говорит, что значение — один из нескольких вариантов. В Rust варианты могут нести собственные данные: ничего, именованные поля или значения в скобках. Перечисления обрабатывают через match: каждая ветвь имеет вид образец => выражение. match должен быть исчерпывающим — нужно учесть каждый возможный вариант, иначе код не скомпилируется.
enum Command {
Quit,
Move { x: i32, y: i32 },
Say(String),
}
fn run(cmd: &Command) -> String {
match cmd {
Command::Quit => String::from("bye"),
Command::Move { x, y } => format!("move to ({x}, {y})"),
Command::Say(text) => format!("say: {text}"),
}
}
fn main() {
let commands = [
Command::Say(String::from("hello")),
Command::Move { x: 3, y: -1 },
Command::Quit,
];
for cmd in &commands {
println!("{}", run(cmd));
}
}say: hello move to (3, -1) bye
format! работает как println!, но не печатает текст, а возвращает его как String. В ветвях match имена x, y и text связываются с данными внутри варианта.fn day_type(day: u8) -> char {
match day {
1..=5 => 'W',
6 | 7 => 'H',
}
}fn day_type(day: u8) -> char {
match day {
1..=5 => 'W',
6 | 7 => 'H',
_ => '?',
}
}u8 может принимать значения от 0 до 255, поэтому компилятор пишет: non-exhaustive patterns: 0_u8 and 8_u8..=u8::MAX not covered. _ означает «всё остальное». В образцах можно использовать диапазоны (1..=5) и «или» (6 | 7).Option вместо null
В Rust нет null. Если значение может быть, а может и не быть, используют перечисление Option<T> из стандартной библиотеки: Some(значение) или None. Значение внутри нельзя использовать напрямую — компилятор заставляет учесть и случай None. Если интересен только один вариант, вместо полного match пиши короткий if let.
fn find_index(names: &[&str], target: &str) -> Option<usize> {
for (i, name) in names.iter().enumerate() {
if *name == target {
return Some(i);
}
}
None
}
fn main() {
let names = ["Elvin", "Leyla", "Murad"];
match find_index(&names, "Leyla") {
Some(i) => println!("Leyla is at {i}"),
None => println!("Leyla not found"),
}
if let Some(i) = find_index(&names, "Nigar") {
println!("Nigar is at {i}");
} else {
println!("Nigar not found");
}
let murad = find_index(&names, "Murad");
println!("{:?} {}", murad, murad.unwrap_or(0));
}Leyla is at 1 Nigar not found Some(2) 2
unwrap_or(0) возвращает значение из Some, а при None — указанное запасное значение. В стандартной библиотеке есть и готовый метод, делающий то же самое: names.iter().position(|n| *n == target).Главное
structобъединяет именованные поля в один тип;#[derive(Debug)]позволяет печатать его через{:?}.- Методы пишут в блоке
impl:&selfчитает,&mut selfизменяет; функцию безselfвызывают какТип::имя(). - Варианты
enumмогут нести данные;matchдолжен охватывать все варианты, а_ловит остальные. - В Rust нет
null: возможно отсутствующее значение — этоOption<T>, то естьSome(x)илиNone. unwrap()паникует приNone;match,if letиunwrap_orбезопаснее.
Проверь себя
Вопросов: 10. Каждый правильный ответ приносит XP.