среда, 22 января 2020 г.

Обобщенные типы в Rust.

Мы уже говорили про "генерики"(generic types), например, когда обсуждали Объекты в Rust. Сегодня мы поговорим о них более подробно.

Помните, я писал, что в Rust нет перегрузки (overload) функций, ну т. е. мы не можем создать несколько функций с одним и тем же именем, но с разными типами для параметров и возвращаемого значения, что могло быть удобно. Но это и не нужно, когда мы можем сделать тоже самое с помощью генерик функции, что делает возможным ее использование с любыми типами - именно то, что мы и хотели при перегрузке функции.

Объявить “общий” (generic) тип можно с помощью “<” и “>” , в которые мы заключаем любое имя для одного или нескольких (через запятую) общих типов, например <T> или <K, V>. После чего мы можем использовать эти имена внутри функции, в качестве заглушек, на месте которых может быть любой базовый или пользовательский тип.
 fn foo<T>(x: T) -> T { x }

Чтобы вызвать такую функцию, мы можем явно указать с помощью “::<i32>”, какой конкретный тип мы хотим использовать вместо общего типа. foo::<i32>(5); Или не указывать, и тогда Rust определит (inferr) его неявно, по типу значений для параметров и возвращаемого значения. foo(5); В данном случае значение “5” имеет тип “i32”.

Мы можем задать ограничение (trait bound) для общего типа, которое будет требовать, чтобы все конкретные типы имплементировали характеристику, т.е. гарантировали в своем составе определенные методы. Это делается с помощью “:Trait” или “:Trait1 + Trait2”, если характеристик несколько.  
 fn foo<T: Copy>(x: T) -> T { x }

Такое ограничение можно задать другим способом, с помощью “where T:Trait” или “where T: Trait1 + Trait2”. fn foo<T >(x: T) -> T where T: Copy + Clone { x } В данном случае конкретные типы должны поддерживать копирование.

Есть еще один способ задать ограничение, с помощью “impl Trait” вместо параметров и/или возвращаемого значения, когда нам не нужны имена-заглушки, но нужно гарантировать наличие характерных методов. Fn foo(x: impl Display)-> impl Display{ x } В данном случае тип будет выведен неявно на базе типа значения для параметра или возвращаемого значения с соблюдением условия, что этот тип имеет характеристику Display.

Дальше рассмотрим генерики в контексте пользовательских типов. Общий тип может быть использован для полей структуры и задается с помощью <T> после имени структуры. При создании такой структуры мы можем указать конкретный тип явно “::<i32>" или позволить Rustу вывести его.

Чтобы для такой структуры определить методы, которые могут использовать общий тип, нужно определить блок имплементации с помощью impl<T> , тем самым определяя имя заглушку для этого пространства имен (блока). Таким образом мы связываем заглушку структуры с заглушкой для методов.

Определение ограничений по характеристикам для методов такое-же, как и для обычных функций. Тут стоит отметить момент, при котором при определении ограничения для генерик-структуры, может потребовать соблюдение этого же ограничения для конкретного типа, который может быть использован для создания этой структуры. Например, мы можем сравнивать (PartialEq) такие структуры друг с другом при условии, что конкретные типы, использованные в этих структурах, также могут быть сравнимы. Смотрим пример.


point.rs
---------
use std::cmp::Ordering;

#[derive(Debug)]
pub struct Point<T> {
    pub x: T,
    pub y: T,
}

impl Point<i32>{
    //..
}

impl<T> Point<T> {
    pub fn from(_x: T, _y: T) -> Point<T> {
        Point {
            x: _x,
            y: _y,
        }
    }
    pub fn set(&mut self, _x: T, _y: T) {
        self.x = _x;
        self.y = _y;
    }
    pub fn bigger(p1: Point<T>, p2: Point<T>) -> Option<Point<T>> where T: PartialOrd + PartialEq {
        if p1 == p2 {
            None
        } else if p1 < p2 {
            Some(p2)
        } else {
            Some(p1)
        }
    }
}

impl<T> PartialEq for Point<T> where T: PartialEq {
    fn eq(&self, other: &Self) -> bool {
        if self.x == other.x && self.y == other.y {
            true
        } else {
            false
        }
    }
}

impl<T> PartialOrd for Point<T> where T: PartialOrd {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        self.x.partial_cmp(&other.x)
    }
}

main.rs
-----------
mod point;
use point::*;

fn main() {
    let p1 = Point { x: 5.0, y: 5.0 };
    //let p1 = Point::<f64> { x: 5.0, y: 5.0 };
    //let p1:Point<f64> = Point { x: 5.0, y: 5.0 };
    let mut p2 = Point::<f64>::from(0.1, 0.1);
    p2.set(10.1, 10.1);
    //println!("Bigger point is: {:?}", Point::<f64>::bigger(p1, p2).unwrap());
    println!("Bigger point is: {:?}", Point::bigger(p1, p2).unwrap());
}

Перегуд В.

Комментариев нет:

Отправить комментарий