4 个版本

0.3.5 2021 年 11 月 26 日
0.3.4 2021 年 11 月 23 日
0.2.0 2021 年 11 月 19 日
0.1.0 2021 年 11 月 18 日

#2 in #blanket

27 每月下载量
degeneric-macros 中使用

MIT 许可证

71KB
1.5K SLoC

动态化

为了将特质转换为特质对象,特质必须是对象安全的,并且必须指定所有关联类型的值。然而,有时您可能希望特质对象能够包含具有不同关联类型值的特质实现。这个 crate 提供了一个过程宏来实现这一点。

以下代码示例说明了动态化如何帮助您

trait Client {
    type Error;

    fn get(&self, url: String) -> Result<Vec<u8>, Self::Error>;
}

impl Client for HttpClient { type Error = HttpError; ...}
impl Client for FtpClient { type Error = FtpError; ...}

let client: HttpClient = ...;
let object = &client as &dyn Client;

上述代码的最后一行编译失败,原因如下

错误[E0191]:关联类型 Error(来自特质 Client)的值必须指定

要使用 dynamize,您只需对特质进行一些小的修改

#[dynamize::dynamize]
trait Client {
    type Error: Into<SuperError>;

    fn get(&self, url: String) -> Result<Vec<u8>, Self::Error>;
}
  1. 您需要将 #[dynamize::dynamize] 属性添加到您的特质中。
  2. 您为每个关联类型指定一个特质界限。

Dynamize 会为您定义一个新的特质,其名称与您的特质相同,但以 Dyn 前缀开头,例如 Client 变为 DynClient

let client: HttpClient = ...;
let object = &client as &dyn DynClient;

然后可以使用新的 "动态化" 特质,而无需指定关联类型值。

这是如何工作的?

对于上述示例,dynamize 生成了以下代码

trait DynClient {
    fn get(&self, url: String) -> Result<Vec<u8>, SuperError>;
}

impl<__to_be_dynamized: Client> DynClient for __to_be_dynamized {
    fn get(&self, url: String) -> Result<Vec<u8>, SuperError> {
        Client::get(self, url).map_err(|x| x.into())
    }
}

如您所见,在动态化特质中,关联类型被替换为 Into 界定的目标类型。然而,魔法发生在之后:dynamize 生成一个通配实现:每个实现 Client 的类型自动也实现了 DynClient

实际上这是如何工作的?

关联类型的目标类型通过查看其特质界限来确定

  • 如果第一个特质界限是 Into<T>,则目标类型是 T

  • 否则,目标类型是所有特质界限的封装特质对象
    例如,Error + Send 变为 Box<dyn Error + Send>
    (为此,第一个特质界限需要是对象安全的)

Dynamize可以将返回类型中的关联类型动态化

  • 例如,fn example(&self) -> Self::A
  • 回调参数,例如 fn example<F: Fn(Self::A)>(&self, f: F)

Dynamize还理解如果你将关联类型包装在以下类型中

  • 元组
  • Option<_>
  • Result<_, _>
  • some::module::Result<_>(具有固定错误类型的类型别名)
  • &mutdynIterator<Item =_>
  • Vec<_>VecDeque<_>LinkedList<_>HashSet<K>BinaryHeap<K>BTreeSet<K>HashMap<K, _>BTreeMap<K, _>
    (对于K,只有满足Into界限的关联类型才有效,因为它们需要Eq

请注意,由于这些类型是递归解析的,因此可以任意嵌套,例如以下内容也有效

fn example(&self) -> Result<Vec<Self::Item>, Self::Error>;

Dynamize如何处理方法泛型?

为了对象安全,方法不得有泛型,因此dynamize简单地将它们移动到特质定义中。对于以下源代码

#[dynamize::dynamize]
trait Gen {
    type Result: std::fmt::Display;

    fn foo<A>(&self, a: A) -> Self::Result;
    fn bar<A, B>(&self, a: A, b: B) -> Self::Result;
    fn buz(&self) -> Self::Result;
}

dynamize生成以下特质

trait DynGen<A, B> {
    fn foo(&self, a: A) -> Box<dyn std::fmt::Display + '_>;
    fn bar(&self, a: A, b: B) -> Box<dyn std::fmt::Display + '_>;
    fn buz(&self) -> Box<dyn std::fmt::Display + '_>;
}

如果两个方法类型参数具有相同的名称,dynamize将强制它们具有相同的界限,并且只将参数添加一次到特质中。

请注意,在动态特质的调用中,现在调用buz方法需要您指定泛型类型,尽管该方法实际上并不需要它们。您可以通过将原始特质分成两部分来避免这种情况,即把buz方法移动到另一个特质中,该特质可以单独动态化。

动态化支持异步操作

动态化默认支持异步操作。然而,由于Rust目前还不支持在特质中使用异步函数,您需要额外使用其他库,如async-trait,例如

#[dynamize::dynamize]
#[dyn_trait_attr(async_trait)]
#[blanket_impl_attr(async_trait)]
#[async_trait]
trait Client: Sync {
    type Error: std::error::Error + Send;

    async fn get(&self, url: String) -> Result<Vec<u8>, Self::Error>;
}
  • #[dyn_trait_attr(foo)]#[foo]附加到动态化特质
  • #[blanket_impl_attr(foo)]#[foo]附加到泛型实现

请注意,#[dynamize]属性必须放在#[async_trait]属性之前,因为dynamize必须在async_trait之前运行。

动态化超特质

在Rust中,宏仅对传入的输入操作;它无法访问周围源代码。这也意味着#[dynamize]宏无法知道哪些其他特质已被动态化。当您想要动态化具有动态超特质的特质时,您必须使用#[dynamized(...)]属性来通知dynamize

#[dynamize::dynamize]
trait Client {
    type Error: std::error::Error;

    fn get(&self, url: String) -> Result<Vec<u8>, Self::Error>;
}

#[dynamize::dynamize]
#[dynamized(Client)]
trait ClientWithCache: Client {
    type Error: std::error::Error;

    fn get_with_cache<C: Cache>(
        &self,
        url: String,
        cache: C,
    ) -> Result<Vec<u8>, <Self as ClientWithCache>::Error>;
}

这导致DynClientWithCache具有动态化的DynClient超特质。

上述代码中,两个特质都有独立的关联类型。因此,一个特质可以为一个特质实现一个Error类型,而为另一个特质实现另一个Error类型。如果您不希望这种情况发生,可以将第二个特质修改为

#[dynamize::dynamize]
#[dynamized(Client)]
#[convert = |x: <Self as Client>::Error| -> Box<dyn std::error::Error + '_> {Box::new(x) as _}]
trait ClientWithCache: Client {
    fn get_with_cache<C: Cache>(
        &self,
        url: String,
        cache: C,
    ) -> Result<Vec<u8>, <Self as Client>::Error>;
}

请注意,我们已经删除了相关类型,现在通过指定Self as Client使用超特质的关联类型。然而,由于#[dynamize]属性无法在ClientWithCache特质中知道关联类型,我们还需要添加一个#[convert = ...]属性来告诉dynamize如何转换<Self as Client>::Error>

使用dynamize与其它集合

Dynamize可以自动识别标准库中的集合,如Vec<_>HashMap<_, _>。只要实现了IntoIteratorFromIterator,Dynamize也可以与其它集合类型一起工作,例如,可以这样使用indexmap

#[dynamize::dynamize]
#[collection(IndexMap, 2)]
trait Trait {
    type A: Into<String>;
    type B: Into<i32>;

    fn example(&self) -> IndexMap<Self::A, Self::B>;
}

传入的数字告诉dynamize期望多少泛型类型参数。

  • 对于1,dynamize期望:Type<A>: IntoIterator<Item=A> + FromIterator<A>
  • 对于2,dynamize期望:Type<A,B>: IntoIterator<Item=(A,B)> + FromIterator<(A,B)>
  • 对于3,dynamize期望:Type<A,B,C>: IntoIterator<Item=(A,B,C)> + FromIterator<(A,B,C)>
  • 等等...

依赖关系

~1.5MB
~35K SLoC