Обсуждение статьи "Есть ли ООП в Rust?"


#1

#2

Выложил статью. Предлагаю читать-комментировать.


#3

В статье предлагается реализовать трейт Figure вручную для Square, Rectangle.
Такие трейты можно делать автоматически выводимыми:

trait Figure: Area + fmt::Display { }
impl<T: Area + fmt::Display> Figure for T { }

Тогда не придется писать

impl Figure for Rectangle

#4

Спасибо за замечание.

Видимо, это то, что называется “Blanket Impl”.


#5

А как правильно организовать фаловую структуру для проекта с таким наследованием?
В классических языках обычно один класс - один файл, где реализован весь его функционал. А тут появляется еще и типаж, который содержит в себе часть функционала класса - получается распыление кода. Или я что-то не понимаю, и можно обособить код, относящийся к конкретному классу?


#6

Смотрите пример в репозитории.


#7

Вариант с динамическим преобразованием к общему типажу-объекту, чтобы не дублировать функционал при наличии нескольких веток иерархии:

mod figures {
    use std::fmt;
    
    pub trait Fig {
        fn as_rect(&self) -> Option<&Rect> {
            None
        }
        
        fn as_ellip(&self) -> Option<&Ellip> {
            None
        }
        
        fn area(&self) -> f64 {
            if let Some(rect) = self.as_rect() {
                rect.width() * rect.length()
            } else if let Some(ellip) = self.as_ellip() {
                ::std::f64::consts::PI * ellip.a() * ellip.b()
            } else {
                0.
            }
        }
    }
    
    pub trait Rect {
        fn width(&self) -> f64;
        fn length(&self) -> f64;
    }
    
    pub trait Ellip {
        fn a(&self) -> f64;
        fn b(&self) -> f64;
    }
    
    /**
     * Rectangle
    **/
    
    pub struct Rectangle {
        width: f64,
        length: f64,
    }
    
    impl Rectangle {
        pub fn new(width: f64, length: f64) -> Option<Rectangle> {
            if width > 0. && length > 0. {
                Some(Rectangle { length, width })
            } else {
                None
            }
        }
    }
    
    impl Rect for Rectangle {
        fn length(&self) -> f64 {
            self.length
        }
        
        fn width(&self) -> f64 {
            self.width
        }
    }
    
    impl Fig for Rectangle {
        fn as_rect(&self) -> Option<&Rect> {
            Some(self)
        }
    }
    
    impl fmt::Display for Rectangle {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "прямоугольник({}, {})", self.width, self.length)
        }
    }
    
    /**
     * Square
    **/
    
    pub struct Square {
        side: f64,
    }
    
    impl Square {
        pub fn new(side: f64) -> Option<Square> {
            if side > 0. {
                Some(Square { side })
            } else {
                None
            }
        }
    }
    
    impl Rect for Square {
        fn length(&self) -> f64 {
            self.side
        }
        
        fn width(&self) -> f64 {
            self.side
        }
    }
    
    impl Fig for Square {
        fn as_rect(&self) -> Option<&Rect> {
            Some(self) 
        }
    }
    
    impl fmt::Display for Square {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "квадрат({})", self.side)
        }
    }
    
    /**
     * Ellipse
    **/
    
    pub struct Ellipse {
        a: f64,
        b: f64,
    }

    impl Ellipse {
        pub fn new(a: f64, b: f64) -> Option<Ellipse> {
            if a > 0. && b > 0. {
                Some( Ellipse { a, b } )
            } else {
                None
            }
        }
    }
    
    impl Ellip for Ellipse {
        fn a(&self) -> f64 {
            self.a
        }
        
        fn b(&self) -> f64 {
            self.b
        }
    }
    
    impl Fig for Ellipse {
        fn as_ellip(&self) -> Option<&Ellip> {
            Some(self) 
        }
    }
    
    impl fmt::Display for Ellipse {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "эллипс({}, {})", self.a, self.b)
        }
    }

    /**
     * Circle
    **/
    
    pub struct Circle {
        radius: f64,
    }
    
    impl Circle {
        pub fn new(radius: f64) -> Option<Circle> {
            if radius > 0. {
                Some( Circle { radius } )
            } else {
                None
            }
        }
    }
    
    impl Ellip for Circle {
        fn a(&self) -> f64 {
            self.radius
        }
        
        fn b(&self) -> f64 {
            self.radius
        }
    }
    
    impl Fig for Circle {
        fn as_ellip(&self) -> Option<&Ellip> {
            Some(self) 
        }
    }
    
    impl fmt::Display for Circle {
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
            write!(f, "круг({})", self.radius)
        }
    }
}

use std::fmt;
use figures::{Fig, Rectangle, Square, Ellipse, Circle};

trait Figure: Fig + fmt::Display {}
impl<T: Fig + fmt::Display> Figure for T {}

fn print_figures_and_areas(figures: &[&Figure]) {
    for f in figures.iter() {
        println!("Площадь {} равна {}", f, f.area());
    }
}

fn main() {
    let rect1 = Rectangle::new(3., 5.).unwrap();
    let rect2 = Rectangle::new(4., 6.).unwrap();

    let sq1 = Square::new(8.).unwrap();
    let sq2 = Square::new(4.).unwrap();

    let ellipse1 = Ellipse::new(1., 2.).unwrap();
    let ellipse2 = Ellipse::new(2., 4.).unwrap();

    let circle1 = Circle::new(1.).unwrap();
    let circle2 = Circle::new(2.).unwrap();

    print_figures_and_areas(
        &[&rect1, &rect2, &sq1, &sq2, &ellipse1, &ellipse2, &circle1, &circle2]
    );
}

https://play.rust-lang.org/?gist=e815824b01c58d7e2043b51b817d188c&version=stable&mode=debug