#继承 #oop #repetition #避免 # #过程宏 #derive-debug

inheritance-proc-macro

使用面向对象继承避免Rust中的代码重复

2个版本

0.0.1-alpha.22019年10月18日
0.0.1-alpha2019年9月15日

#避免中排名11

每月下载量22

MIT许可MIT

18KB
465

::继承

这个(实验性)crate提供过程宏,通过从组合派生委托来实现“继承-like”行为。

Repository Latest version Documentation License

展示

想象有一些Point类型和一些行为/关联方法

#[derive(Debug, Clone, Copy, PartialEq)]
struct Point {
    x: f32,

    y: f32,
}

impl Point {
    fn x (self: &'_ Self) -> f32
    {
        self.x
    }

    fn y (self: &'_ Self) -> f32
    {
        self.y
    }

    fn name (self: &'_ Self) -> Option<&'_ str>
    {
        Some("Point")
    }
}

现在想象你想要一个新的"Point-like"类型,但具有额外的属性(以及可能的一些覆盖行为)

# struct Point;
struct NamedPoint {
    name: String,

    point: Point,
}

首先,成为"Point-like"需要将固有方法抽象为一个特质

#[derive(Debug, Clone, Copy, PartialEq)]
struct Point {
    x: f32,

    y: f32,
}

trait IsPoint {
    fn x (self: &'_ Self) -> f32;

    fn y (self: &'_ Self) -> f32;

    fn name (self: &'_ Self) -> Option<&'_ str>;
}

impl IsPoint for Point {
    fn x (self: &'_ Self) -> f32
    {
        self.x
    }

    fn y (self: &'_ Self) -> f32
    {
        self.y
    }

    fn name (self: &'_ Self) -> Option<&'_ str>
    {
        Some("Point")
    }
}

现在我们希望NamedPoint实现IsPoint

# #[derive(Debug,Clone,Copy,PartialEq)]struct Point{x:f32, y:f32}trait IsPoint{fn x(&self)->f32;fn y(&self)->f32;fn name(&self)->Option<&str>;}impl IsPoint for Point{fn x(&self)->f32{self.x}fn y(&self)->f32{self.y}fn name(&self)->Option<&str>{Some("Point")}}
struct NamedPoint {
    name: String,

    point: Point,
}

impl IsPoint for NamedPoint {
    #[inline]
    fn x (self: &'_ Self) -> f32
    {
        self.point.x()
    }

    #[inline]
    fn y (self: &'_ Self) -> f32
    {
        self.point.y()
    }

    #[inline]
    fn name (self: &'_ Self) -> Option<&'_ str>
    {
        self.point.name()
    }
}

这导致编写非常重复且无趣(委托)代码...

进入::inheritance

通过在IsPoint特质上放置一个#[inheritable]属性,以及在NamedPoint结构体上放置一个Inheritance派生,可以完全跳过最后一个实现

use ::inheritance::{inheritable, Inheritance};

#[derive(Debug, Clone, Copy, PartialEq)]
struct Point {
    x: f32,

    y: f32,
}

#[inheritable]
trait IsPoint {
    fn x (self: &'_ Self) -> f32;

    fn y (self: &'_ Self) -> f32;

    fn name (self: &'_ Self) -> Option<&'_ str>;
}

impl IsPoint for Point {
    fn x (self: &'_ Self) -> f32
    {
        self.x
    }

    fn y (self: &'_ Self) -> f32
    {
        self.y
    }

    fn name (self: &'_ Self) -> Option<&'_ str>
    {
        Some("Point")
    }
}

#[derive(Inheritance)]
struct NamedPoint {
    name: String,

    #[inherits(IsPoint)]
    point: Point,
}

nightly Rust

这个特性与nightly中的specialization特性特别有趣,你现在已经在nightly中使用了。

当在Cargo.toml中依赖inheritancecrate时,你可以指定你想要使用这个特性

[dependencies]
inheritance = { version = "...", features = ["specialization"] }

然后你将能够覆盖一些自动生成的委托方法

# use::inheritance::{inheritable, Inheritance};#[derive(Debug,Clone,Copy,PartialEq)]struct Point{x:f32, y:f32}#[inheritable]trait IsPoint{fn x(&self)->f32;fn y(&self)->f32;fn name(&self)->Option<&str>;}impl IsPoint for Point{fn x(&self)->f32{self.x}fn y(&self)->f32{self.y}fn name(&self)->Option<&str>{Some("Point")}}
#[derive(Inheritance)]
struct NamedPoint {
    name: String,

    #[inherits(IsPoint)]
    point: Point,
}

# #[cfg(feature = "specialization")]
impl IsPoint for NamedPoint {
    fn name (self: &'_ Self) -> Option<&'_ str>
    {
        Some(&*self.name)
    }
}

进一步

新类型

可以对新类型字段使用#[inherits(...)]字段属性,这使得它成为新类型的完美工具

# use::inheritance::{inheritable, Inheritance};#[derive(Debug,Clone,Copy,PartialEq)]struct Point{x:f32, y:f32}#[inheritable]trait IsPoint{fn x(&self)->f32;fn y(&self)->f32;fn name(&self)->Option<&str>;}impl IsPoint for Point{fn x(&self)->f32{self.x}fn y(&self)->f32{self.y}fn name(&self)->Option<&str>{Some("Point")}}
#[derive(Inheritance)]
struct AnonymousPoint (
    #[inherits(IsPoint)]
    Point,
);

# #[cfg(feature = "specialization")]
impl IsPoint for AnonymousPoint {
    fn name (self: &'_ Self) -> Option<&'_ str>
    {
        None
    }
}

多重“继承”

具有一个带有 #[derive(Inheritance)] 注解的结构体可以拥有多个 #[inherits(...)] 注解,无论是相同的字段还是多个不同的字段。因为每个“继承”的特性实现都只是将那个特性的方法委派到装饰过的字段上。如果您尝试从多个字段中“继承”相同的特性,那么经典的一致性规则将阻止它

# use::inheritance::{inheritable, Inheritance};#[derive(Debug,Clone,Copy,PartialEq)]struct Point{x:f32, y:f32}#[inheritable]trait IsPoint{fn x(&self)->f32;fn y(&self)->f32;fn name(&self)->Option<&str>;}impl IsPoint for Point{fn x(&self)->f32{self.x}fn y(&self)->f32{self.y}fn name(&self)->Option<&str>{Some("Point")}}
#[derive(Debug, Clone, Copy, PartialEq)]
enum Color {
    Red,
    Green,
    Blue,
}

#[inheritable]
trait Colored {
    fn color (self: &'_ Self) -> Color;
}

impl Colored for Color {
    #[inline]
    fn color (self: &'_ Self) -> Color
    {
        *self
    }
}

#[derive(Inheritance)]
struct ColoredPoint {
    #[inherits(IsPoint)]
    point: Point,

    #[inherits(Colored)]
    pigments: Color,
}

虚函数派发

“虚拟”方法的理念,类似于C++,是对象始终携带一个包含一些方法的vtable,这些方法被标记为 virtual(而其他方法则不包含在内)。然而,在Rust中,“对象”有时会携带一个vtable(即,structenum不携带,但特例对象(dyn Trait)携带),并且这个vtable包含特性的所有方法和其超特性的方法

想象一个具有 .present() 方法的结构,该方法的主体调用 .name(),并且每个不同的 Point 都会改变由 .name() 调用返回的行为/值。

/// somewhere in the code
fn present (self: &'_ Self) -> Cow<'static, str>
// where Self : IsPoint
{
    if let Some(name) = self.name() {
        Cow::from(format!("{}({}, {})", name, self.x(), self.y()))
    } else {
        Cow::from("<anonymous>")
    }
}

let point = Point { x: 42., y: 27. };
assert_eq!(
    &point.present() as &str,
    "Point(42.0, 27.0)"
);

let anonymous_point = AnonymousPoint(point);
assert_eq!(
    &anonymous_point.present() as &str,
    "<anonymous>"
);

let named_point = NamedPoint { name: "Carré", point };
assert_eq!(
    &named_point.present() as &str,
    "Carré(42.0, 27.0)"
);
  • 在C++这样的语言中,这可以通过将 .name() 方法标记为 virtual 来实现,并且让 .present() 成为基类的方法(不一定是 virtual 本身)。

  • 在Rust中,上述策略无法实现;必须使用一个函数/方法,该函数/方法对 IsPoint 类型的实现者进行多态,使用

    • 动态派发(C++中 virtual 方法的机制)

      # use::inheritance::{inheritable, Inheritance};#[derive(Debug,Clone,Copy,PartialEq)]struct Point{x:f32, y:f32}#[inheritable]trait IsPoint{fn x(&self)->f32;fn y(&self)->f32;fn name(&self)->Option<&str>;}impl IsPoint for Point{fn x(&self)->f32{self.x}fn y(&self)->f32{self.y}fn name(&self)->Option<&str>{Some("Point")}}use::std::borrow::Cow;
      impl dyn IsPoint + '_ {
          fn present (self: &'_ Self) -> Cow<'static, str>
          {
              if let Some(name) = self.name() {
                  Cow::from(format!("{}({}, {})", name, self.x(), self.y()))
              } else {
                  Cow::from("<anonymous>")
              }
          }
      }
      
    • 静态派发;然后要获取类似方法语法,需要使用辅助特例

      # use::inheritance::{inheritable, Inheritance};#[derive(Debug,Clone,Copy,PartialEq)]struct Point{x:f32, y:f32}#[inheritable]trait IsPoint{fn x(&self)->f32;fn y(&self)->f32;fn name(&self)->Option<&str>;}impl IsPoint for Point{fn x(&self)->f32{self.x}fn y(&self)->f32{self.y}fn name(&self)->Option<&str>{Some("Point")}}use::std::borrow::Cow;
      trait Present
      where
          Self : IsPoint,
      {
          fn present (self: &'_ Self) -> Cow<'static, str>
          {
              if let Some(name) = self.name() {
                  Cow::from(format!("{}({}, {})", name, self.x(), self.y()))
              } else {
                  Cow::from("<anonymous>")
              }
          }
      }
      impl<T : ?Sized> Present for T
      where
          T : IsPoint,
      {}
      

目前该宏不针对功能齐全的“虚拟”方法,所以还无法直接 覆盖 .present()

由于这是一个实验性的包,我会尝试探索这个可能性...

依赖项

~2MB
~45K SLoC