Обобщенные типы

Что такое обобщенные типы

обобщённые типы данных - generics. Это абстрактные подставные типы на место которых возможно поставить какой-либо конкретный тип или другое свойство.

#![allow(unused)]
fn main() {
fn largest_i32(list: &[i32]) -> &i32 {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

fn largest_char(list: &[char]) -> &char {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

}

собственно суть обобщения - как из нескольких функций сделать одну, которая понимает какого типа ей передали данные

Для параметризации типов данных в новой объявляемой функции нам нужно дать имя обобщённому типу — так же, как мы это делаем для аргументов функций. Можно использовать любой идентификатор для имени параметра типа, но мы будем использовать T, потому что по соглашению имена параметров в Rust должны быть короткими (обычно длиной в один символ), а именование типов в Rust делается в нотации UpperCamelCase.

#![allow(unused)]
fn main() {
fn largest<T>(list: &[T]) -> &T {
    let mut largest = &list[0];

    for item in list {
        if item > largest {
            largest = item;
        }
    }

    largest
}

}
  • функция largest является обобщённой по типу T
  • имеет один параметр с именем list, который является срезом значений с типом данных T
  • возвращает значение этого же типа T

Для того чтобы сравнивать символы нужно добавить типаж: std::cmp::PartialOrd

#![allow(unused)]
fn main() {
fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
}

и вот теперь будет работать со всеми типами, которые можно сравнить

Обобщение типов в структурах

struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let integer = Point { x: 5, y: 10 };
    let float = Point { x: 1.0, y: 4.0 };
}

а это структура с разными типами

struct Point<T, U> {
    x: T,
    y: U,
}

fn main() {
    let both_integer = Point { x: 5, y: 10 };
    let both_float = Point { x: 1.0, y: 4.0 };
    let integer_and_float = Point { x: 5, y: 4.0 };
}

Обобщение в определении перечислений

#![allow(unused)]
fn main() {
enum Option<T> {
    Some(T),
    None,
}
}

или

#![allow(unused)]
fn main() {
enum Result<T, E> {
    Ok(T),
    Err(E),
}
}

В определении методов

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let p = Point { x: 5, y: 10 };

    println!("p.x = {}", p.x());
}

мы должны объявить T сразу после impl

может быть все сложнее

struct Point<X1, Y1> {
    x: X1,
    y: Y1,
}

impl<X1, Y1> Point<X1, Y1> {
    fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 5, y: 10.4 };
    let p2 = Point { x: "Hello", y: 'c' };

    let p3 = p1.mixup(p2);

    println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}

Типажи

Типаж сообщает компилятору Rust о функциональности, которой обладает определённый тип и которой он может поделиться с другими типами. Можно использовать типажи, чтобы определять общее поведение абстрактным способом. Мы можем использовать ограничение типажа (trait bounds) чтобы указать, что общим типом может быть любой тип, который имеет определённое поведение.

Определение типажа

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

#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize(&self) -> String;
}
}
  • бъявляем типаж с использованием ключевого слова trait
  • название
  • pub позволяет крейтам, зависящим от нашего крейта, тоже использовать наш крейт
  • Внутри фигурных скобок объявляются сигнатуры методов

Каждый тип, реализующий данный типаж, должен предоставить своё собственное поведение для данного метода

Реализация типажа

#![allow(unused)]
fn main() {
pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct SocialPost {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub repost: bool,
}

impl Summary for SocialPost {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

}

Использование типажа в бинарном крейте

use aggregator::{SocialPost, Summary};

fn main() {
    let post = SocialPost {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        repost: false,
    };

    println!("1 new post: {}", post.summarize());
}

Поведение по умолчанию

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

#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

}

а в экземпляре типа объявить impl Summary for NewsArticle {} пустышку, но этого достаточно, чтобы к типу подтянулись все методы типажа имеющие по умолчанию определение

Если у типажа несколько методов, то в impl типа достаточно объявить хотя бы один, чтобы пользоваться всеми остальными, имеющими определение по умолчанию

Типажи как параметры