3 个版本

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

#2457 in Rust 模式

MIT 许可证

14KB
52

::继承

这个(实验性)crate 通过从组合派生委托,提供了获取“类似继承”行为的过程宏。

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 derive,可以完全跳过最后一个实现

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

这个功能与 specialization 功能结合使用特别有趣,你已经在 nightly 中可以使用这个功能。

Cargo.toml 中依赖于 inheritance crate 时,你可以指定你想要使用这个功能

[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(即,结构体和枚举不携带,但 trait 对象 dyn Trait 携带),并且这个 vtable 包含该特性和其超特质的全部方法

想象有一个 .present() 方法,其方法体调用 .name(),并且每次调用 .name() 时,其行为/返回值都会因不同的 Point 而异。

/// 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
~46K SLoC