Перейти к содержанию

Дженерики - как их использовать и реализовать в Rust

Posted on:26 февраля 2023 г. at 13:08

Example Dynamic OG Image link

Когда мы хотим написать код для нескольких контекстов, мы используем дженерики. Это дает нам примитивы для объявления шаблонов, которые позволяют нам меньше фокусироваться на конкретных типах. Это позволяет нам писать более краткий и чистый код за счет уменьшения дублирования кода и обеспечения безопасности типов.

Универсальная функция

В универсальном определении мы записываем параметр типа между открытыми и закрытыми угловыми скобками после имени. В Rust дженерик также описывает все, что принимает один или несколько параметров универсального типа <T>. T представляет любой тип данных.

Синтаксис:

pub fn function_name<T>(param:T)

T был указан в качестве параметра универсального типа с использованием <T>. Параметр может быть любого типа.

Пример:

fn main() {
    let sum_int_value = addition_of_values(3,5);
    println!("Addition of Integer values : {:?}",sum_int_value);
    let sum_float_value = addition_of_values(3.1,5.5);
    print!("Addition of Float values : {:?}",sum_float_value);
}

Характеристики дженериков

Трейты также могут быть общими. Они могут содержать методы или абстрактные методы. Определения характеристик - это способ группировки сигнатур методов для определения набора поведений, необходимых для достижения некоторой цели.

Синтаксис:

trait some_trait {
   //abstract method
   fn method1(&self);
   
   fn method2(&self){
     //contents of method
   }
}

Пример:

pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

Универсальный тип T, указанный в качестве типа параметров элемента, ограничивает функцию.

Универсальная структура

Чтобы определить универсальную структуру, мы записываем параметр type после имени структуры, между открытыми и закрытыми угловыми скобками. Чтобы определить структуру, мы вводим ключевое слово struct и даем имя всей структуре.

Синтаксис:

struct STRUCT_NAME<T> {
    field:T,
    field:T,
}

Затем, внутри фигурных скобок, мы определяем имена и типы фрагментов данных, которые называем полями.

Пример:

fn main() {
    let int_point = Point {
        num1:4,
        num2:5
    };
    println!("Integer points : {:?}",int_point);
    
    let float_point = Point {
        num1:4.5,
        num2:5.5
    };
    println!("Floating points : {:?}",float_point);
}
#[derive(Debug)]
struct Point<T> {
    num1: T,
    num2: T,
}

Универсальное перечисление

Перечисления позволяют вам определить тип путем перечисления его возможных вариантов. Rust по умолчанию предоставляет нам два перечисления:

Option<T>

Перечисление опций представляет собой необязательное значение. Прежде всего, каждый параметр имеет Some и содержит значение, или None и не содержит значения.

Синтаксис:

enum Option<T> {
    Some(T),
    None,
}

Пример:

fn main() {
    let result = find_max_value(5,0);
    match result{
        Some(_) => println!("Num1 is maximum"),
        None => println!("Num2 is maximum")
    }
}

fn find_max_value<T: PartialOrd + std::ops::Rem<Output = T>>(num1:T,num2:T) -> Option<T> {
    match num1 > num2 {
        true => Some(num1),
        false => None
    }
}

Result<T,E>

Перечисление результатов представляет либо успех (Ok), либо неудачу (Err). Иногда важно объяснить, почему операция завершилась неудачей. В этом случае мы должны использовать Result перечисление.

Синтаксис:

enum Result<T,E> {
    Ok(T),
    Err(E),
}

Ok(значение): указывает, что операция выполнена успешно. Значение имеет тип T. Err(почему): указывает, что операция завершилась неудачей. Почему относится к типу E.

Пример:

fn main() {
    let result = find_max_value(5,5);
    match result{
        Ok(value) => println!("Maximum value is : {}",value),
        Err(error) => println!("Error Message : {}",error)
    }
}

fn find_max_value(num1:i32,num2:i32) -> Result<i32,String> {
    match num1 > num2 {
        true => Ok(num1),
        false => Err("Equal numbers".to_string())
    }
}

Итак, надеюсь, вы получите представление о том, как дженерики используются в Rust.