4 个版本
0.3.5 | 2021 年 11 月 26 日 |
---|---|
0.3.4 | 2021 年 11 月 23 日 |
0.2.0 |
|
0.1.0 |
|
#2 in #blanket
27 每月下载量
在 degeneric-macros 中使用
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>;
}
- 您需要将
#[dynamize::dynamize]
属性添加到您的特质中。 - 您为每个关联类型指定一个特质界限。
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<_, _>
。只要实现了IntoIterator
和FromIterator
,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