Еnums, pattern matching
Енумерации, съпоставяне на образци
20 октомври 2026
Преговор
- собственост (ownership)
- семантика на преместване (move semantics)
- семантика на копиране - при примитивни типове
let s1 = String::from("foo bar");
let s2 = s1;
// println!("{}", s1); // error: use of moved value
println!("{}", s2);
foo bar
fn main() {
let s1 = String::from("foo bar");
let s2 = s1;
// println!("{}", s1); // error: use of moved value
println!("{}", s2);
}
Преговор
Преговор
- borrowing, референции
- указател, винаги сочи към валидна стойност
Преговор
- borrowing, референции
- указател, винаги сочи към валидна стойност
- Borrow checker
- или произволен брой споделени референции
&T - или точно една ексклузивна референция
&mut T
- или произволен брой споделени референции
Преговор
Резени, масиви и низове
| Масив | Низ | ||
|---|---|---|---|
| статичен | [T; N] |
- | собственост над стойността |
| динамичен | Vec<T> |
String |
собственост над стойността |
| резен | &[T] |
&str |
заета назаем стойност (borrow) |
| mutable резен | &mut [T] |
&mut str |
заета назаем стойност (borrow) |
Структури
(преговор)
struct User {
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
struct User {
username: String,
email: String,
sign_in_count: u64,
}
}
Tuples
(преговор)
(A, B, C, ...)
let tuple: (i32, u32, bool) = (1, 2, false);
println!("{}", tuple.0);
println!("{}", tuple.1);
println!("{}", tuple.2);
1 2 false
fn main() {
let tuple: (i32, u32, bool) = (1, 2, false);
println!("{}", tuple.0);
println!("{}", tuple.1);
println!("{}", tuple.2);
}
Tuple structs
Именувани кортежи
struct Color(f32, f32, f32);
struct Point(f32, f32, f32);
let black = Color(0.0, 0.0, 0.0);
let origin = Point(0.0, 0.0, 0.0);
fn main() {
struct Color(f32, f32, f32);
struct Point(f32, f32, f32);
let black = Color(0.0, 0.0, 0.0);
let origin = Point(0.0, 0.0, 0.0);
}
Tuple structs
Полетата се достъпват с .0, .1, и т.н., както при нормален tupple
struct Color(f32, f32, f32);
let black = Color(0.0, 0.0, 0.0);
println!("r: {}, g: {}, b: {}", black.0, black.1, black.2);
r: 0, g: 0, b: 0
fn main() {
struct Color(f32, f32, f32);
let black = Color(0.0, 0.0, 0.0);
println!("r: {}, g: {}, b: {}", black.0, black.1, black.2);
}
Tuple structs
Защо? Type safety
struct Color(f32, f32, f32);
struct Point(f32, f32, f32);
fn draw(color: Color, from: Point, to: Point) { /* ... */ }
draw(
Point(-10.0, -10.0, -10.0),
Point(10.0, 10.0, 10.0),
Color(0.5, 0.5, 0.5),
);
error[E0308]: arguments to this function are incorrect --> src/bin/main_0cdcb94a65f72f59745d40fdc6a5ce241c0cc806.rs:7:1 | 7 | draw( | ^^^^ 8 | Point(-10.0, -10.0, -10.0), | -------------------------- expected `Color`, found `Point` 9 | Point(10.0, 10.0, 10.0), 10 | Color(0.5, 0.5, 0.5), | -------------------- expected `Point`, found `Color` | note: function defined here --> src/bin/main_0cdcb94a65f72f59745d40fdc6a5ce241c0cc806.rs:5:4 | 5 | fn draw(color: Color, from: Point, to: Point) { /* ... */ } | ^^^^ help: swap these arguments | 7 ~ draw( 8 + Color(0.5, 0.5, 0.5), 9 + Point(10.0, 10.0, 10.0), 10 + Point(-10.0, -10.0, -10.0), 11 ~ ); | For more information about this error, try `rustc --explain E0308`. error: could not compile `rust` (bin "main_0cdcb94a65f72f59745d40fdc6a5ce241c0cc806") due to 1 previous error
fn main() {
struct Color(f32, f32, f32);
struct Point(f32, f32, f32);
fn draw(color: Color, from: Point, to: Point) { /* ... */ }
draw(
Point(-10.0, -10.0, -10.0),
Point(10.0, 10.0, 10.0),
Color(0.5, 0.5, 0.5),
);
}
Tuple structs
Newtype wrapper
- tuple struct с едно поле
- нов тип със същата репрезентация като съществуващ тип
- нарича се newtype
#[derive(Debug, Clone, Copy)]
struct Token(u32);
#[derive(Debug, Clone, Copy)]
struct Token(u32);
fn main() {}
Tuple structs
Разлика между type alias и newtype
- type alias е ново име за съществуващ тип
type NEW_NAME = SOME_TYPE;- удобно е за преименуване на много дълги типове
- например при използване на generics
type Node<T> = Rc<RefCell<TreeNode<T>>>;
use std::rc::Rc; use std::cell::RefCell; struct TreeNode{ x: T } fn main() {} type Node = Rc >>;
Tuple structs
Разлика между type alias и newtype
- newtype създава изцяло нов тип, който има същото разполагане в паметта като съшествуващ тип
- сигурност на ниво типова система
- предотвратява объркване на аргументи
struct UserId(pub u32);
fn find_user(id: UserId) {}
// let id = 10;
// find_user(id); // error
let id = UserId(10);
find_user(id); // ok
fn main() {
struct UserId(pub u32);
fn find_user(id: UserId) {}
// let id = 10;
// find_user(id); // error
let id = UserId(10);
find_user(id); // ok
}
Празни структури
Празни структури
Можем да дефинираме структури без полета
- със синтаксиса за нормална структура, tuple struct, или без скоби
- на практика са еквивалентни
- размер 0 байта - също като unit
()
struct Electron {}
struct Photon();
struct Muon;
let e = Electron {};
let p = Photon();
let m = Muon;
fn main() {
struct Electron {}
struct Photon();
struct Muon;
let e = Electron {};
let p = Photon();
let m = Muon;
}
Enums
Enums
enum IpAddrKind {
V4,
V6,
}
fn main() {
enum IpAddrKind {
V4,
V6,
}
}
Enums
Инстанциране
#[derive(Clone, Copy, Debug)]
enum IpAddrKind {
V4,
V6,
}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
println!("four = {:?}", four);
println!("six = {:?}", six);
four = V4 six = V6
fn main() {
#[derive(Clone, Copy, Debug)]
enum IpAddrKind {
V4,
V6,
}
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
println!("four = {:?}", four);
println!("six = {:?}", six);
}
Enums
Числени стойности
enum IpAddrKind {
V4 = 4,
V6 = 6,
}
let kind = IpAddrKind::V4;
let kind_int = kind as i32;
println!("kind = {}", kind_int);
kind = 4
fn main() {
enum IpAddrKind {
V4 = 4,
V6 = 6,
}
let kind = IpAddrKind::V4;
let kind_int = kind as i32;
println!("kind = {}", kind_int);
}
Enums
Числени стойности
Може и от конкретен тип число
#[repr(u16)]
enum IpAddrKind {
V4 = 4,
V6 = 6,
}
let kind = IpAddrKind::V4;
let kind_int = kind as u16;
println!("kind = {}", kind_int);
kind = 4
fn main() {
#[repr(u16)]
enum IpAddrKind {
V4 = 4,
V6 = 6,
}
let kind = IpAddrKind::V4;
let kind_int = kind as u16;
println!("kind = {}", kind_int);
}
Enums
Числени стойности
Конвертиране наобратно не е позволено (от число към енумерация)
#[repr(u16)]
enum IpAddrKind {
V4 = 4,
V6 = 6,
}
let kind_int = 4_u16;
let kind = kind_int as IpAddrKind;
println!("kind = {}", kind_int);
error[E0605]: non-primitive cast: `u16` as `IpAddrKind` --> src/bin/main_3a15928c30bea3641346b388b8a36f2135c6aae5.rs:9:12 | 9 | let kind = kind_int as IpAddrKind; | ^^^^^^^^^^^^^^^^^^^^^^ an `as` expression can only be used to convert between primitive types or to coerce to a specific trait object For more information about this error, try `rustc --explain E0605`. error: could not compile `rust` (bin "main_3a15928c30bea3641346b388b8a36f2135c6aae5") due to 1 previous error
fn main() {
#[repr(u16)]
enum IpAddrKind {
V4 = 4,
V6 = 6,
}
let kind_int = 4_u16;
let kind = kind_int as IpAddrKind;
println!("kind = {}", kind_int);
}
Enums
Числени стойности
Можем да конвертираме чрез match, но трябва да се погрижим за всички стойностти
#[repr(u16)]
enum IpAddrKind {
V4 = 4,
V6 = 6,
}
let kind_int = 4;
let kind = match kind_int {
4 => IpAddrKind::V4,
6 => IpAddrKind::V6,
_ => panic!("not a valid ip kind"),
};
println!("kind = {}", kind_int);
kind = 4
fn main() {
#[repr(u16)]
enum IpAddrKind {
V4 = 4,
V6 = 6,
}
let kind_int = 4;
let kind = match kind_int {
4 => IpAddrKind::V4,
6 => IpAddrKind::V6,
_ => panic!("not a valid ip kind"),
};
println!("kind = {}", kind_int);
}
Enums
enum- скучно име, идващо от C, където са доста ограничени
Enums
enum- скучно име, идващо от C, където са доста ограничени- По-готини имена са "algebraic datatype (ADT)" и "sum type"
Enums
enum- скучно име, идващо от C, където са доста ограничени- По-готини имена са "algebraic datatype (ADT)" и "sum type"
- Също е известно като "tagged union"
Enums
Данни
Можем да асоциираме данни с всеки вариант
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
fn main() {
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
let loopback = IpAddr::V6(String::from("::1"));
}
Enums
Данни
Данните може да са от различни типове
enum IpAddr {
V4([u8; 4]),
V6([u16; 8]),
}
let home = IpAddr::V4([127, 0, 0, 1]);
let loopback = IpAddr::V6([0, 0, 0, 0, 0, 0, 0, 1]);
fn main() {
enum IpAddr {
V4([u8; 4]),
V6([u16; 8]),
}
let home = IpAddr::V4([127, 0, 0, 1]);
let loopback = IpAddr::V6([0, 0, 0, 0, 0, 0, 0, 1]);
}
Enums
Защо?
- Повечето типове са "контейнери" -- struct, list, tuple
- могат да се използват за взаимно изключващи се стойности
- но не е особено ясно -- човек трябва "да се усети" като чете кода
struct IpAddr {
kind: IpAddrKind,
v4: [u8; 4],
v6: String,
}
// Warning: фалшив Rust код, няма NULL
let home = IpAddr { kind: IpAddrKind::V4, v4: [127, 0, 0, 1], v6: NULL };
let loopback = IpAddr { kind: IpAddrKind::V6, v4: NULL, v6: String::from("::1") };
Enums
Защо?
- enum позволява да изразите "екслузивност" -- или един пакет данни, или друг
- enum позволява да изброите всички възможни ситуации в типовата система
- и компилатора да ви гарантира, че няма други варианти
enum IpAddr {
V4([u8; 4]),
V6(String),
}
fn main() {}
enum IpAddr {
V4([u8; 4]),
V6(String),
}
Enums
Защо?
- Make invalid states unrepresentable
Отклонение
Алгебрични типове данни
Защо се наричат algebraic datatypes?
Отклонение
Алгебрични типове данни
- struct и tuple са product types
- множеството от възможни стойности за
Userе декартовото произведение String1 × String2 × u64
struct User { username: String, email: String, sign_in_count: u64 }
Отклонение
Алгебрични типове данни
- enum са "sum types"
- множестовто от възможни стойности е обединение на множества
V4([u8; 4]) ∪ V6(String)
// ignore
enum IpAddr {
V4([u8; 4]),
V6(String),
}
Отклонение
Алгебрични типове данни
Празната структура и празният кортеж представляват множества с един елемент
struct UnitStruct{}
// set_of_values = { UnitStruct{} }
type UnitTuple = ();
// set_of_values = { () }
fn main() {
struct UnitStruct{}
// set_of_values = { UnitStruct{} }
type UnitTuple = ();
// set_of_values = { () }
}
Отклонение
Алгебрични типове данни
Q: Какво представлява празната енумерация?
Отклонение
Алгебрични типове данни
Q: Какво представлява празната енумерация?
A: Празно множество
enum Empty {}
// set_of_values = ∅
fn main() {
enum Empty {}
// set_of_values = ∅
}
Отклонение
Алгебрични типове данни
- както Rust има "стандартен" тип за празна структура - unit -
()
Отклонение
Алгебрични типове данни
- както Rust има "стандартен" тип за празна структура - unit -
() - така и Rust има "стандартен" тип за празна енумерация - never -
!
Отклонение
Алгебрични типове данни
- както Rust има "стандартен" тип за празна структура - unit -
() - така и Rust има "стандартен" тип за празна енумерация - never -
! - защо?
Отклонение
Алгебрични типове данни
- както Rust има "стандартен" тип за празна структура - unit -
() - така и Rust има "стандартен" тип за празна енумерация - never -
! - защо?
- типа
!няма възможни стойностти
- типа
Отклонение
Алгебрични типове данни
- както Rust има "стандартен" тип за празна структура - unit -
() - така и Rust има "стандартен" тип за празна енумерация - never -
! - защо?
- типа
!няма възможни стойностти - т.е. ако имате премнлива от тип
!- този ред код никога няма да се изпълни
- типа
Отклонение
Алгебрични типове данни
- както Rust има "стандартен" тип за празна структура - unit -
() - така и Rust има "стандартен" тип за празна енумерация - never -
! - защо?
- типа
!няма възможни стойностти - т.е. ако имате премнлива от тип
!- този ред код никога няма да се изпълни
- типа
// Към Rust 1.99, типа `!` все още не е стабилизиран,
// но се очаква тоав да стане скоро.
// За момента можем да използваме наш празен тип.
enum Never {}
let x: Never = loop {};
let x: Never = std::process::exit(0);
let x: Never = panic!("boom");
#![allow(unreachable_code)]
fn main() {
// Към Rust 1.99, типа `!` все още не е стабилизиран,
// но се очаква тоав да стане скоро.
// За момента можем да използваме наш празен тип.
enum Never {}
let x: Never = loop {};
let x: Never = std::process::exit(0);
let x: Never = panic!("boom");
}
Enums
Защо "tagged union"?
Можем да си представим enum-а като комбинация от:
- таг - число - оказва кой вариант е активен
- стойност - union от няколко типа
Enums
Защо "tagged union"?
Можем да си представим enum-а като комбинация от:
- таг - число - оказва кой вариант е активен
- стойност - union от няколко типа
enum Message {
Quit,
Move { x: i64, y: i64 },
Write(String),
ChangeColor(i64, i64, i64),
}
let msg = Message::Quit;
let msg = Message::Move { x: 3, y: 4 };
let msg = Message::Write(String::from("baba"));
let msg = Message::ChangeColor(255, 0, 0);
fn main() {
enum Message {
Quit,
Move { x: i64, y: i64 },
Write(String),
ChangeColor(i64, i64, i64),
}
let msg = Message::Quit;
let msg = Message::Move { x: 3, y: 4 };
let msg = Message::Write(String::from("baba"));
let msg = Message::ChangeColor(255, 0, 0);
}
Enums
Защо "tagged union"?
Можем да си представим enum-а като комбинация от:
- таг - число - оказва кой вариант е активен
- стойност - union от няколко типа
enum Message {
Quit, // unit struct
Move { x: i64, y: i64 }, // struct
Write(String), // tuple struct
ChangeColor(i64, i64, i64), // tuple struct
}
let msg = Message::Quit;
let msg = Message::Move { x: 3, y: 4 };
let msg = Message::Write(String::from("baba"));
let msg = Message::ChangeColor(255, 0, 0);
fn main() {
enum Message {
Quit, // unit struct
Move { x: i64, y: i64 }, // struct
Write(String), // tuple struct
ChangeColor(i64, i64, i64), // tuple struct
}
let msg = Message::Quit;
let msg = Message::Move { x: 3, y: 4 };
let msg = Message::Write(String::from("baba"));
let msg = Message::ChangeColor(255, 0, 0);
}
Enums
Разполагане в паметта
| Вариант | Памет |
|---|---|
Message::Quit
Message::Move { x: i64, y: i64 }
Message::Write(String)
Message::ChangeColor(i64, i64, i64)
|
+–––––––++–––––––––––––––––––––––+ │ 0 ││ │ +–––––––++–––––––+–––––––+–––––––+ │ 1 ││ i64 │ i64 │ │ +–––––––++–––––––+–––––––+–––––––+ │ 2 ││ String │ +–––––––++–––––––+–––––––+–––––––+ │ 3 ││ i64 │ i64 │ i64 │ +–––––––++–––––––+–––––––+–––––––+ |
- 8 байта за дискриминанта
- 24 байта за данните
Enums
C-style enum
Това ни дава алтернативна интерпретация на енумерацията с числени стойности:
#[repr(u16)]задава типа на тага- а вариантите са unit structs (0 байта)
#[repr(u16)]
enum MyEnum {
VariantA = 0,
VariantB = 1,
VariantC = 3,
}
fn main() {
#[repr(u16)]
enum MyEnum {
VariantA = 0,
VariantB = 1,
VariantC = 3,
}
}
Pattern Matching
"Съпоставяне на образци"
Pattern Matching
"Съпоставяне на образци"
- Идея идваща от функционалното програмиране
Pattern Matching
"Съпоставяне на образци"
- Идея идваща от функционалното програмиране
- Може да се ползва с енуми и стойности в тях
Pattern Matching
"Съпоставяне на образци"
- Идея идваща от функционалното програмиране
- Може да се ползва с енуми и стойности в тях
- Използва се чрез
matchоператора
Pattern Matching
fn call(msg: &Message) {
match msg {
Message::Quit => println!("got quit"),
Message::Move {x, y} => println!("got move {x}, {y}"),
Message::Write(s) => println!("got write {s:?}"),
Message::ChangeColor(r, g, b) => println!("got change_color {r}, {g}, {b}"),
}
}
fn main() {
enum Message {
Quit,
Move { x: i64, y: i64 },
Write(String),
ChangeColor(i64, i64, i64),
}
fn call(msg: &Message) {
match msg {
Message::Quit => println!("got quit"),
Message::Move {x, y} => println!("got move {x}, {y}"),
Message::Write(s) => println!("got write {s:?}"),
Message::ChangeColor(r, g, b) => println!("got change_color {r}, {g}, {b}"),
}
}
}
Pattern Matching
match value {
PATTERN1 => expr1,
PATTERN2 => expr2,
PATTERN3 => expr3,
}
Pattern Matching
match value {
PATTERN1 => expr1,
PATTERN2 => expr2,
PATTERN3 => expr3,
}
- проверява дали стойността
valueотговаря на всеки от образцитеPATTERN1,PATTERN2, …
Pattern Matching
match value {
PATTERN1 => expr1,
PATTERN2 => expr2,
PATTERN3 => expr3,
}
- проверява дали стойността
valueотговаря на всеки от образцитеPATTERN1,PATTERN2, … - спира при първото намерено съвпадение и изпълнява израза в дясната част на ръкава
Pattern Matching
match value {
PATTERN1 => expr1,
PATTERN2 => expr2,
PATTERN3 => expr3,
}
- проверява дали стойността
valueотговаря на всеки от образцитеPATTERN1,PATTERN2, … - спира при първото намерено съвпадение и изпълнява израза в дясната част на ръкава
- образците трябва да покриват всички възможни стойности - трябва в поне един от ръкавите да има съвпадение
Pattern Matching
match value {
PATTERN1 => expr1,
PATTERN2 => expr2,
PATTERN3 => expr3,
}
- проверява дали стойността
valueотговаря на всеки от образцитеPATTERN1,PATTERN2, … - спира при първото намерено съвпадение и изпълнява израза в дясната част на ръкава
- образците трябва да покриват всички възможни стойности - трябва в поне един от ръкавите да има съвпадение
matchе израз - стойността му е резултата от изпълнения израз
Pattern Matching
Може да се съпоставя по стойност
let condition = true;
match condition {
true => println!("it's ok"),
false => println!("it's bad"),
}
it's ok
fn main() {
let condition = true;
match condition {
true => println!("it's ok"),
false => println!("it's bad"),
}
}
Pattern Matching
Образеца _ отговаря на всяка възможна стойност
let my_number = 14;
match my_number {
1 => println!("it's one"),
2 => println!("it's two"),
3 => println!("it's three"),
_ => println!("it's something else"),
}
it's something else
fn main() {
let my_number = 14;
match my_number {
1 => println!("it's one"),
2 => println!("it's two"),
3 => println!("it's three"),
_ => println!("it's something else"),
}
}
Pattern Matching
Може да се сравнява с интервали от стойности
let my_number = 14_u32;
match my_number {
0 => println!("none"),
1..3 => println!("small"),
3..=14 => println!("medium"),
15.. => println!("large"),
}
medium
fn main() {
let my_number = 14_u32;
match my_number {
0 => println!("none"),
1..3 => println!("small"),
3..=14 => println!("medium"),
15.. => println!("large"),
}
}
Pattern Matching
Съвпадналата стойност може да бъде присвоена на променлива с @
- основно се използва при съпоставяне с интервал от стойности
let my_number = 14_u32;
match my_number {
0 => println!("none"),
n @ 1..3 => println!("{n} is small"),
n @ 3..=14 => println!("{n} is medium"),
15.. => println!("too large"),
}
14 is medium
fn main() {
let my_number = 14_u32;
match my_number {
0 => println!("none"),
n @ 1..3 => println!("{n} is small"),
n @ 3..=14 => println!("{n} is medium"),
15.. => println!("too large"),
}
}
Pattern Matching
Име (identifier) съвпада на всяка стойност и присвоява стойността на променлива с това име. Все едно var @ _.
let my_number = 14_u32;
match my_number {
0 => println!("it is zero"),
n => println!("it is non-zero"),
}
it is non-zero
fn main() {
let my_number = 14_u32;
match my_number {
0 => println!("it is zero"),
n => println!("it is non-zero"),
}
}
Pattern Matching
Име в образен винаги създава нова променлива, която засенчва съществуващи променливи.
Не може да се използва за съпоставяне със стойността на съществуваща променлива
#![allow(unreachable_patterns)]
let x = 5;
match 123 {
x => println!("it should be five; got x={x}"), // wrong
_ => println!("it's not five"), // wrong
}
it should be five; got x=123
#![allow(unreachable_patterns)]
fn main() {
let x = 5;
match 123 {
x => println!("it should be five; got x={x}"), // wrong
_ => println!("it's not five"), // wrong
}
}
Pattern Matching
Име в образен винаги създава нова променлива, която засенчва съществуващи променливи.
Не може да се използва за съпоставяне със стойността на съществуваща променлива
// #![allow(unreachable_patterns)]
let x = 5;
match 123 {
x => println!("it should be five; got x={x}"), // wrong
_ => println!("it's not five"), // wrong
}
warning: unreachable pattern --> src/bin/main_0992ff311fb9eb93ad5bdedc2e5e35d62833f5ba.rs:7:5 | 6 | x => println!("it should be five; got x={x}"), // wrong | - matches any value 7 | _ => println!("it's not five"), // wrong | ^ no value can reach this | note: there is a binding of the same name; if you meant to pattern match against the value of that binding, that is a feature of constants that is not available for `let` bindings --> src/bin/main_0992ff311fb9eb93ad5bdedc2e5e35d62833f5ba.rs:4:5 | 4 | let x = 5; | ^ = note: `#[warn(unreachable_patterns)]` (part of `#[warn(unused)]`) on by default
fn main() {
let x = 5;
match 123 {
x => println!("it should be five; got x={x}"), // wrong
_ => println!("it's not five"), // wrong
}
}
Pattern Matching
enum FooBar {
Foo,
Bar,
}
let val = FooBar::Foo;
match val {
FooBar::Foo => println!("it's foo"),
FooBar::Bar => println!("it's bar"),
}
it's foo
fn main() {
enum FooBar {
Foo,
Bar,
}
let val = FooBar::Foo;
match val {
FooBar::Foo => println!("it's foo"),
FooBar::Bar => println!("it's bar"),
}
}
Pattern Matching
let coords = (3, 3);
match coords {
(0, 0) => println!("center"),
(0, y) => println!("vertical {y}"),
(x, 0) => println!("horizontal {x}"),
(x, y) => println!("nowhere in particular {x},{y}"),
}
nowhere in particular 3,3
fn main() {
let coords = (3, 3);
match coords {
(0, 0) => println!("center"),
(0, y) => println!("vertical {y}"),
(x, 0) => println!("horizontal {x}"),
(x, y) => println!("nowhere in particular {x},{y}"),
}
}
Pattern Matching
Съпоставяне на масиви и резени
let cake: &[&str] = &["vanilla", "strawberry", "chocolate"];
match cake {
[] => println!("Turns out it's a lie :/"),
[one_item] => println!("One slice is better than nothing"),
_ => println!("Wow, that's a lotta slices!")
}
Wow, that's a lotta slices!
fn main() {
let cake: &[&str] = &["vanilla", "strawberry", "chocolate"];
match cake {
[] => println!("Turns out it's a lie :/"),
[one_item] => println!("One slice is better than nothing"),
_ => println!("Wow, that's a lotta slices!")
}
}
Pattern Matching
.. отговаря на всичко останало от масива
let nums = &[1, 2, 3][..];
match nums {
[] => println!("empty"),
[a] => println!("just {a}"),
[x, ..] => println!("head is {x}"),
}
head is 1
fn main() {
let nums = &[1, 2, 3][..];
match nums {
[] => println!("empty"),
[a] => println!("just {a}"),
[x, ..] => println!("head is {x}"),
}
}
Pattern Matching
.. може да се комбинира с @
let nums = &[1, 2, 3][..];
match nums {
[] => println!("empty"),
[a] => println!("just {a}"),
[x, rest @ ..] => println!("head is {x}, tail is {rest:?}"),
}
head is 1, tail is [2, 3]
fn main() {
let nums = &[1, 2, 3][..];
match nums {
[] => println!("empty"),
[a] => println!("just {a}"),
[x, rest @ ..] => println!("head is {x}, tail is {rest:?}"),
}
}
Pattern Matching
.. работи и в началото или средата.
let nums = &[1, 2, 3, 4][..];
match nums {
[] => println!("empty"),
[a] => println!("just {a}"),
[first, mid @ .., last] => println!("{first} then {mid:?} then {last}"),
}
1 then [2, 3] then 4
fn main() {
let nums = &[1, 2, 3, 4][..];
match nums {
[] => println!("empty"),
[a] => println!("just {a}"),
[first, mid @ .., last] => println!("{first} then {mid:?} then {last}"),
}
}
Pattern Matching
Но може да се среща само веднъж в целия образец
let nums = &[1, 2, 3, 4][..];
match nums {
[] => println!("empty"),
[a] => println!("just {a}"),
[.., mid, ..] => println!("mid is {mid}"),
}
error: `..` can only be used once per slice pattern --> src/bin/main_cc2e630a367d1ea68231c8d5466227007d241c57.rs:6:15 | 6 | [.., mid, ..] => println!("mid is {mid}"), | -- ^^ can only be used once per slice pattern | | | previously used here error: could not compile `rust` (bin "main_cc2e630a367d1ea68231c8d5466227007d241c57") due to 1 previous error
fn main() {
let nums = &[1, 2, 3, 4][..];
match nums {
[] => println!("empty"),
[a] => println!("just {a}"),
[.., mid, ..] => println!("mid is {mid}"),
}
}
Pattern Matching
Още: guards (допълнителни условия)
match value {
PATTERN if CONDITION => EXPR_1,
PATTERN if CONDITION => EXPR_2,
}
Pattern Matching
Още: алтернативност
match value {
PATTERN1 | PATTERN2 => EXPR_1
PATTERN3 | PATTERN4 => EXPR_2
}
match value {
| PATTERN1
| PATTERN2 => EXPR_1
| PATTERN3
| PATTERN4 => EXPR_2
}
Pattern Matching
Ограничения - типовете трябва да съответстват
struct User {
name: &'static str, // <- низ литерал
age: u8
}
let user = User { name: "Пешо", age: 12 };
match user { // ок
User { name: "Пешо", age: _ } => println!("Ко стаа, Пешо"),
User { name: _, age: 12 } => println!("Ко стаа, лапе"),
User { name, .. } => println!("Ко стаа, {name}"),
}
Ко стаа, Пешо
fn main() {
struct User {
name: &'static str, // <- низ литерал
age: u8
}
let user = User { name: "Пешо", age: 12 };
match user { // ок
User { name: "Пешо", age: _ } => println!("Ко стаа, Пешо"),
User { name: _, age: 12 } => println!("Ко стаа, лапе"),
User { name, .. } => println!("Ко стаа, {name}"),
}
}
Pattern Matching
Ограничения - типовете трябва да съответстват
struct User {
name: String, // <- динамичен низ
age: u8
}
let user = User { name: "Пешо".to_string(), age: 12 };
match user { // не е ок
User { name: "Пешо", age: _ } => println!("Ко стаа, Пешо"),
User { name: _, age: 12 } => println!("Ко стаа, лапе"),
User { name, .. } => println!("Ко стаа, {name}"),
}
error[E0308]: mismatched types --> src/bin/main_e00feb4f04a8f7d49f75b302dace00fdbfee96fa.rs:10:18 | 9 | match user { // не е ок | ---- this expression has type `User` 10 | User { name: "Пешо", age: _ } => println!("Ко стаа, Пешо"), | ^^^^^^ expected `String`, found `&str` | help: consider converting the `String` to a `&str` using `.as_str()` | 9 | match user.as_str() { // не е ок | +++++++++ For more information about this error, try `rustc --explain E0308`. error: could not compile `rust` (bin "main_e00feb4f04a8f7d49f75b302dace00fdbfee96fa") due to 1 previous error
fn main() {
struct User {
name: String, // <- динамичен низ
age: u8
}
let user = User { name: "Пешо".to_string(), age: 12 };
match user { // не е ок
User { name: "Пешо", age: _ } => println!("Ко стаа, Пешо"),
User { name: _, age: 12 } => println!("Ко стаа, лапе"),
User { name, .. } => println!("Ко стаа, {name}"),
}
}
Destructuring
Destructuring
let bindings
Повечето от образците, поддържани от match, могат да се използват и за разбиране на стойност в let клаузи.
Дясната страна трябва винаги да може да се съпостави на лявата - irrefutable binding.
let (a, b, c) = (1, true, "three");
let (a, ..) = (1, true, "three");
let (.., c) = (1, true, "three");
let [a, b, c] = [1, 2, 3];
let [a, ..] = [1, 2, 3];
let [.., c] = [1, 2, 3];
fn main() {
let (a, b, c) = (1, true, "three");
let (a, ..) = (1, true, "three");
let (.., c) = (1, true, "three");
let [a, b, c] = [1, 2, 3];
let [a, ..] = [1, 2, 3];
let [.., c] = [1, 2, 3];
}
Destructuring
let bindings
Повечето от образците, поддържани от match, могат да се използват и за разбиране на стойност в let клаузи.
Дясната страна трябва винаги да може да се съпостави на лявата - irrefutable binding.
let User { name: name_var, .. } = user;
println!("{}", name_var);
let User { name, .. } = user;
println!("{}", name);
fn main() {
#[derive(Default)] struct User { name: String, email: String }
let user = User::default();
let User { name: name_var, .. } = user;
println!("{}", name_var);
let user = User::default();
let User { name, .. } = user;
println!("{}", name);
}
Destructuring
arguments
Destructuring може да се прилага и в аргументи на функции
fn goto_v1(coords: (i32, i32)) {
let (x, y) = coords;
/* ... */
}
fn goto_v2((x, y): (i32, i32)) {
/* ... */
}
fn main() {}
fn goto_v1(coords: (i32, i32)) {
let (x, y) = coords;
/* ... */
}
fn goto_v2((x, y): (i32, i32)) {
/* ... */
}
More control flow
if let
if let PATTERN = value {
code()
}
Еквиваленто на долния match.
Но спестява едно ниво на индентация и няколко реда
match value {
PATTERN => {
code()
}
_ => {}
}
More control flow
while let
А защо не и while let:
while let PATTERN = expr() {
code()
}
Еквивалентно на
loop {
match expr() {
PATTERN => {
code()
}
_ => {
break
}
}
}
Option
Option
- Понякога искаме да изразим липсваща стойност.
Option
- Понякога искаме да изразим липсваща стойност.
- Проблема обикновено се решава със специалната стойност
NULL.
Option
- Понякога искаме да изразим липсваща стойност.
- Проблема обикновено се решава със специалната стойност
NULL. - Това води до готини неща като "тройни булеви стойности" и любимите на всички null pointer exceptions.
Option
- Понякога искаме да изразим липсваща стойност.
- Проблема обикновено се решава със специалната стойност
NULL. - Това води до готини неща като "тройни булеви стойности" и любимите на всички null pointer exceptions.
- Тук-таме
NULLсе използва и за "имаше грешка, но¯\_(ツ)_/¯каква"
Option
- Понякога искаме да изразим липсваща стойност.
- Проблема обикновено се решава със специалната стойност
NULL. - Това води до готини неща като "тройни булеви стойности" и любимите на всички null pointer exceptions.
- Тук-таме
NULLсе използва и за "имаше грешка, но¯\_(ツ)_/¯каква" - В Rust няма
NULL!
Option
При възможност за липсваща стойност се използва Option.
Има 2 стойности:
Some(val)None
enum Option<T> {
Some(T),
None,
}
fn main() {}
enum Option {
Some(T),
None,
}
Option
let some_number = Some(5);
let some_string = Some("string");
let absent_number: Option<i32> = None;
println!("{:?}", some_number);
println!("{:?}", some_string);
println!("{:?}", absent_number);
Some(5) Some("string") None
fn main() {
let some_number = Some(5);
let some_string = Some("string");
let absent_number: Option = None;
println!("{:?}", some_number);
println!("{:?}", some_string);
println!("{:?}", absent_number);
}
Option
let some_number: Option<u32> = Some(5);
let some_string: Option<&str> = Some("string");
let absent_number: Option<i32> = None;
println!("{:?}", some_number);
println!("{:?}", some_string);
println!("{:?}", absent_number);
Some(5) Some("string") None
fn main() {
let some_number: Option = Some(5);
let some_string: Option<&str> = Some("string");
let absent_number: Option = None;
println!("{:?}", some_number);
println!("{:?}", some_string);
println!("{:?}", absent_number);
}
Option
Non-zero оптимизация
За много типове нула/null е невалидна стойност (напр &T).
Или съдържат поле, за което е невалидна стойност (напр ptr в String).
Ако такъв тип се използва в enum, компилатора може да използва свободния слот, за да помести другите варианти.
use std::mem;
enum LeanAndMean {
A,
B(String),
}
fn main() {
println!("Without enum: {:?}", mem::size_of::<String>());
println!("With enum: {:?}", mem::size_of::<LeanAndMean>());
}
Without enum: 24 With enum: 24
use std::mem;
enum LeanAndMean {
A,
B(String),
}
fn main() {
println!("Without enum: {:?}", mem::size_of::());
println!("With enum: {:?}", mem::size_of::());
}
Option
Non-zero оптимизация
Това много често се случва при Option.
При всички тези случаи Option е zero cost abstraction.
println!("&T: {:?} vs {:?}", mem::size_of::<&u32>(), mem::size_of::<Option<&u32>>());
println!("Box<T>: {:?} vs {:?}", mem::size_of::<Box<u32>>(), mem::size_of::<Option<Box<u32>>>());
println!("&str: {:?} vs {:?}", mem::size_of::<&str>(), mem::size_of::<Option<&str>>());
println!("String: {:?} vs {:?}", mem::size_of::<String>(), mem::size_of::<Option<String>>());
println!("Vec<_>: {:?} vs {:?}", mem::size_of::<Vec<u32>>(), mem::size_of::<Option<Vec<u32>>>());
&T: 8 vs 8 Box<T>: 8 vs 8 &str: 16 vs 16 String: 24 vs 24 Vec<_>: 24 vs 24
use std::mem;
fn main() {
println!("&T: {:?} vs {:?}", mem::size_of::<&u32>(), mem::size_of::
Result
Друг много често срещан тип е Result.
В Rust няма exceptions - функции които могат да се провалят връщат Result
enum Result<T, E> {
Ok(T),
Err(E),
}
fn main() {}
enum Result {
Ok(T),
Err(E),
}
Option и Result
Извличане на стойност
- най-добре чрез pattern matching
- трябва да се справим със случая когато няма стойност -
NoneприOption - или има грешка -
ErrприResult
let some_number = Some(5);
let double = match some_number {
Some(val) => val + val,
None => 0,
};
println!("{:?}", double);
10
fn main() {
let some_number = Some(5);
let double = match some_number {
Some(val) => val + val,
None => 0,
};
println!("{:?}", double);
}
Option и Result
panic
- при неочаквани грешки, от които програмата не може да се възстанови
- изкарва съобщение за грешка
- и убива програмата
- обикновено - може да се прихване, но това е за специални случаи
panic!("boom")
thread 'main' (4418) panicked at src/bin/main_f7805e0522dc52087809f00b5c53095884d8a7cf.rs:2:1: boom note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
fn main() {
panic!("boom")
}
Option и Result
Извличане на стойност
Option::unwrap(),Result::unwrap()Option::expect(err_msg),Result::expect(err_msg)
Option и Result
Извличане на стойност
Option::unwrap(),Result::unwrap()Option::expect(err_msg),Result::expect(err_msg)- лесен начин да извлечем стойността, ако сме сигурни, че има такава
- в противен случай ще panic-не
let optional_number = Some(5);
let number = optional_number.unwrap();
// e горе-долу еквивалетно на
let number = match optional_number {
Some(n) => n,
None => panic!("`unwrap()` called on `None` value"),
};
fn main() {
let optional_number = Some(5);
let number = optional_number.unwrap();
// e горе-долу еквивалетно на
let number = match optional_number {
Some(n) => n,
None => panic!("`unwrap()` called on `None` value"),
};
}