Паттерны и конструкция match

Последнее обновление: 06.05.2021

Паттерны представляют шаблоны, с которыми сравнивается некоторое значение. Например, возьмем конструкцию match. Ее формальное определение:

match значение{
	паттерн1 => действия1,
	паттерн2 => действия2,
	паттерн3 => действия3
}

Оператор match принимает некоторое значение и сравнивает его с набором паттернов. Если значение соответствует одному из паттернов, то выполняются действия, предусмотренные для данного паттерна.

Возьмем простейший пример:

fn main(){
     
     let n = 2;

    match n {
        1 => println!("один"),
        2 => println!("два"),
        3 => println!("три"),
        _ => println!("непонятно")
    }
}

Здесь значение переменной n сравнивается с набором паттернов, в качестве которых выступают обычные числа. В данном случае n = 2, поэтому это значение соответствует паттерну "2", следовательно будет выполняться выражение println!("два").

Однако паттерны необязательно должны представлять отдельные значения типа чисел или строк. Паттерны в Rust одновременно более гибкий и в то же время более мощный инструмент. Рассмотрим отдельные случаи применения паттернов.

Диапазон значений

В качестве паттерна может выступать набор значений. Например, с помощью оператора | мы можем указать два альтернативных выражения, которым должно соответствовать значение:

fn main(){
     
     let n = 2;

    match n {
        1 | 2 =>  println!("один или два"),
        3 => println!("три"),
        _ => println!("непонятно")
    }
}

Шаблон 1 | 2 указывает, что значение должно соответствовать либо числу 1, либо числу 2.

С помощью оператора последовательности ..= можно задать диапазон значений:

fn main(){
     
     let n = 5;

    match n {
        1..=4 =>  println!("от одного до четырех"),
        5..=9 => println!("от пяти до девяти"),
        _ => println!("непонятно")
    }
}

Шаблон 1..=4 указывает, что значение должно находиться в диапазоне от 1 до 4 включительно.

Паттерны и кортежи

Конструкция match также позволяет сопоставить значения кортежа с некоторым набором шаблонов и выполнить соответствующие действия:

fn main() {
 
    let user = ("Bob", 37);
	match user{
		("Tom", 36) => println!("name: Tom, age: 36"),
		("Bob", 37) => println!("name: Bob, age: 37"),
		_ => println!("Undefined")
	}
}

Здесь кортеж сопоставлятся с набором шаблонов, которые представляют кортежи. Поскольку в данном случае значения кортежа user соответствует шаблону ("Bob", 37), то будет выполняться выражение println!("name: Bob, age: 37")

При этом при помощи шаблонов можно сравнивать только с частью значений, а также можно получать некоторые значения в переменные. Например:

fn main() {
 
    let user = ("Bob", 37);
	match user{
		("Tom", 36) => println!("name: Tom, age: 36"),
		(name, 37) => println!("name: {}", name),
		_ => println!("Undefined")
	}
}

Шаблон (name, 37) представляет шаблон, которому соответствует любой кортеж из двух элементов, где второй компонент представляет число 37. И если кортеж соответствует этому шаблону, его первый компонент помещается в переменную name, которую мы можем использовать в действиях, предусмотренных для этого шаблона.

Более того мы можем целиком уйти от конкретных значений:

fn main(){
     
    let user = ("Bob", 37);
	match user{
		("Tom", age) => println!("Pattern1. Name: Tom, age: {}", age),
		(name, 22) => println!("Pattern2. Name: {}, age: 22", name),
		(name, age) => println!("Pattern3. Name: {}, age: {}", name, age),
	}
}

Два первых шаблона в конструкции match используют по одной переменной либо для первого, либо для второго компонента кортежа. Тогда как третий шаблон использует две переменных - (name, age) и соответствует любому кортежу, который имеет два компонента. Поскольку этот шаблон покрывает все возможные кортежи с двумя элементами, нам не надо определять шаблон _=> для случаев, которые не вписываются в предыдущие шаблоны.

Шаблоны и структуры

Аналогично кортежам в конструкциях match можно использовать структуры. Например, мы можем определить для структуры шаблон с конкретными значениями:

struct Person { name: String, age: u8 }
fn main(){
  
	let bob = Person{ name: "Bob".to_string(), age: 41};
	let _username = "Bob".to_string();
	
	match bob{
		Person{name: _username, age: 41} => println!("Name: Bob, age: 41"),
		_ => println!("Undefined person"),
	}
}

Здесь объект структуры Person сравнивается с шаблоном Person{name: _username, age: 41}, то есть с некоторой структурой, где поле name равно переменной _username, а поле age - числу 41.

Обратите внимание, что в шаблонах нельзя использовать вызовы функций, поэтому шаблон выглядит так:

Person{name: _username, age: 41}

А не так:

Person{name: "Bob".to_string(), age: 41}

Также в шаблонах для структур можно определять переменные:

struct Person { name: String, age: u8 }
fn main(){
     
    let tom = Person{ name: "Tom".to_string(), age: 36};
	let _username = "Bob".to_string();
	match tom{
		Person{name: _username, age: 41} => println!("Name: Bob, age: 41"),
		Person{name, age: 36} => println!("Name: {}, age: 36", name),
		Person{name, age} => println!("Name: {}, age: {}", name, age)
	}
}

Здесь первый шаблон, как и в предыдущем примере устанавливает конкретные значения.

Второй шаблон - Person{name, age: 36} соответствует объектам структуры Person, у которых поле age равно 36. При этом значение поля name будет помещаться в переменную name

Третий шаблон - Person{name, age} соответствует любой структуре Person. При этом значения ее полей будут помещаться в переменные name и age. При этом названия переменных совпадают с названиями соответствующих полей структуры. Если названия переменных не совпадают, то мы должны явным образом указать, как они соотносятся с полями структуры:

match tom{
	Person{name: _username, age: 41} => println!("Name: Bob, age: 41"),
	Person{name, age: 36} => println!("Name: {}, age: 36", name),
	Person{name: person_name, age: person_age} => println!("Name: {}, age: {}", person_name, person_age)
}

Шаблоны и массивы

Похожим образом можно использовать шаблоны для сравнения массивов:

fn main(){
     
     let numbers = [1, 7, 3];
	 
	match numbers {
        [1, 2, 3] =>  println!("Pattern1. [1, 2, 3]"),
        [x, 2, 3] =>  println!("Pattern2. [{}, 2, 3]", x),
        [x, y, 3] =>  println!("Pattern3. [{}, {}, 3]", x, y),
        [x, y, z] =>  println!("Pattern4. [{}, {}, {}]", x, y, z),
    }
}

Здесь конструкция match сравнивает массив numbers с набором шаблонов, которые содержат как конкретные значения, так и переменные. В данном случае массив numbers соответствует третьему шаблону - [x, y, 3]. Этот шаблон соответствует любому массиву, третий элемент которого равен 3.

Перечисления и шаблоны

В случае с перечислениями шаблоны позволяют получить значения констант перечисления в переменные:

enum DayTime{
	
	Morning(String),
	Evening(String)
}
fn main(){
 
	let date_time = DayTime::Morning("Доброе утро".to_string());
	match date_time {
	
		DayTime::Morning(message)=> println!("{}", message),
		DayTime::Evening(message)=> println!("{}", message)
	}
}

Перечисление DayTime определяет две константы, которые хранят значение типа String.

Конструкция match предусматривает для объекта перечисления два шаблона, в каждом из которых определяется переменная message. В эту переменную мы можем получить значение константы.

В данном случае значение переменной date_time будет соответствовать шаблону DayTime::Morning(message). В итоге на консоль будет выведено сообщение "Доброе утро".

При это константы перечисления могут представлять разные типы:

enum DayTime{
	
	Morning(u8, u8),
	Evening(String)
}

В данном случае константа Morning хранит сразу два значения типа u8 (условно первый и последний утренние часы). Соответственно при определении переменной необходимо передать два значения:

let dt = DayTime::Morning(0, 12);

Тогда шаблон для этой константы может предоставить две переменные - для каждого отдельного значения константы:

enum DayTime{
	
	Morning(u8, u8),
	Evening(String)
}
fn main(){
 
	let morning = DayTime::Morning(0, 12);
	match morning {
	
		DayTime::Morning(6, end)=> println!("Утро: с 6 до {}", end),
		DayTime::Morning(start, end)=> println!("Утренние часы: с {} до {}", start, end),
        DayTime::Evening(message)=> println!("{}", message),
	}
}

Игнорирование значений

Если нам не нужны какие-то значения, вместо них также можно определить прочерк:

enum DayTime{
	
	Morning(u8, u8),
	Evening(String)
}
fn main(){
 
	let morning = DayTime::Morning(5, 12);
	match morning {
	
		DayTime::Morning(_, end)=> println!("Утренние часы заканчиваются в {} часов", end),
		DayTime::Evening(message)=> println!("{}", message)
	}
}

В данном случае мы не используем первое значение константы DayTime::Morning, однако для каждого значения константы нам надо что-то указать, например, конкретное значение или имя переменной. А прочерк позволяет выйти из этой ситуации и вообще не использовать это значение.

То же самое можно проделать и в шаблонах для других типов. Например, для структур:

struct Person { name: String, age: u8, height: f32 }

fn main(){
 
	let bob = Person{
        name: "Bob".to_string(),
        age: 33,
        height: 1.70
    };
	match bob {
	
		Person{name, age, height: _} => println!("name: {}  age: {}", name, age)
	}
}

В данном случае мы не собираемся использовать в конструкции match поле height, поэтому в шаблоне для этого поля указывается прочерк: Person{name, age, height: _}.

Пример с кортежами:

fn main(){
 
	let user = ("Tom", 36);
	match user {
		
		("Bob", age) => println!("Name: Bob, age: {}", age),
		(name, _) => println!("Name: {}", name)
	}
}

Если структура, кортеж, перечисление или массив содержат много значений подряд, которые нам не нужны и мы хотим их пропустить, то можно использовать оператор ... Например:

struct Person { name: String, age: u8, height: f32 }

fn main(){
 
	let bob = Person{
        name: "Bob".to_string(),
        age: 33,
        height: 1.70
    };
	match bob {
	
		Person{name, ..} => println!("name: {}", name)
	}
}

Шаблон Person{name, ..} позволяет получить значение поля name, остальные поля структуры игнорируются.

Другой пример - получим только первый и последний элементы массива:

fn main(){
 
	let numbers = [2, 3, 4, 5, 6, 7, 8];
	
	match numbers {
	
		[first,.., last] => println!("first: {}, last: {}", first, last)
	}
}
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850