14次发布
| 0.3.4 | 2024年7月12日 |
|---|---|
| 0.3.3 | 2024年7月9日 |
| 0.2.5 | 2024年7月2日 |
| 0.2.3 | 2024年6月30日 |
| 0.1.2 | 2024年6月27日 |
#1429 in 过程宏
830次每月下载
在 2 crates 中使用
59KB
1K SLoC
dyn_derive
为动态Rust继承和派生对象不安全特质。
简介
对象安全性 是Rust中特质的属性,用于确定特质是否可以作为特质对象使用。然而,对象安全性的要求相当严格,限制了类型系统的表达能力。
例如,您不能简单地编写
// Clone is not object-safe
// PartialEq is not object-safe
pub trait Foo: Clone + PartialEq {
// This method is not object-safe
fn adjust(self) -> Self;
}
#[derive(Clone, PartialEq)]
pub struct Bar {
meta: Box<dyn Foo>, // The trait `Foo` cannot be made into an object.
}
此crate提供了一种过程宏,用于将对象不安全特质转换为对象安全特质
use dyn_derive::*;
#[dyn_trait]
pub trait Foo: Clone + PartialEq {
fn adjust(self) -> Self;
}
#[derive(Clone, PartialEq)]
pub struct Bar {
meta: Box<dyn Foo>, // Now it works!
}
尽管还有一些限制,但这项技术在我的场景中运行良好。
超特质
如果特质需要作为特质对象使用,则超特质也必须是对象安全的。但是,许多有用的特质不是对象安全的,例如 Clone 和 PartialEq。
为了解决这个问题,此crate将超特质转换为对象安全特质,以便它们可以作为超特质使用,并为您的自定义类型派生。
基本示例
以下是如何使用此crate的基本示例
use std::fmt::Debug;
use dyn_derive::*;
#[dyn_trait]
pub trait Foo: Debug + Clone + PartialEq {
fn answer(&self) -> i32 {
42
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct MetaImpl;
impl Foo for MetaImpl {}
#[derive(Debug, Clone, PartialEq)]
pub struct Bar {
meta: Box<dyn Foo>,
}
fn main() {
let foo1 = Bar { meta: Box::new(MetaImpl) };
let foo2 = Bar { meta: Box::new(MetaImpl) };
assert_eq!(foo1, foo2);
let foo3 = foo1.clone();
assert_eq!(foo3.meta.answer(), 42);
}
不可派生特质
以 Add 特质为例
use std::fmt::Debug;
use std::ops::Add;
use dyn_derive::*;
#[dyn_trait]
pub trait Foo: Debug + Add {}
#[derive(Debug)]
pub struct MetaImpl(String);
impl Foo for MetaImpl {}
impl Add for MetaImpl {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self(self.0 + &rhs.0)
}
}
pub struct Bar {
pub meta: Box<dyn Foo>,
}
impl Add for Bar {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
// `Box<dyn Foo>` can be added!
meta: self.meta + rhs.meta,
}
}
}
fn main() {
let foo1 = Bar { meta: Box::new(MetaImpl("114".into())) };
let foo2 = Bar { meta: Box::new(MetaImpl("514".into())) };
let foo3 = foo1 + foo2;
println!("{:?}", foo3.meta); // MetaImpl("114514")
}
支持的特质
以下std特质受支持
CloneNeg,NotAdd,Sub,Mul,Div,RemBitAnd,BitOr,BitXor,Shl,ShrAddAssign,SubAssign,MulAssign,DivAssign,RemAssignBitAndAssign,BitOrAssign,BitXorAssign,ShlAssign,ShrAssignPartialEq,Eq,PartialOrd,Ord
未来可能会支持更多的std特性和自定义特性。
方法
注意:这部分尚未完成。
Rust中,关联函数可以分为两种类型:方法和非方法,这取决于它们是否具有命名参数self。
trait Foo: Sized {
// These are methods.
fn method_1(&self);
fn method_2(self) -> Self;
// These are non-methods.
fn method_3() -> Option<Vec<Self>>;
fn method_4(this: &mut Self, that: Self);
}
此crate支持方法和非方法,但它们被处理方式不同。方法和非方法被分为两个特质,即实例和构造函数。它们都是对象安全的。
trait FooInstance {
fn method_1(&self);
fn method_2(self: Box<Self>) -> Box<dyn FooInstance>;
}
trait FooConstructor {
fn method_3(&self) -> Option<Vec<Box<dyn FooInstance>>>;
fn method_4(&self, this: &mut dyn FooInstance, that: Box<dyn FooInstance>);
}
原始的Foo特质(可能或可能不是对象安全的)可以通过Instance和Constructor类型包裹,以便用作实例或构造函数。
impl FooInstance for ::dyn_std::Instance<Foo> {}
impl FooConstructor for ::dyn_std::Constructor<Foo> {}
如果您正在开发库,您可以编写如下代码
use std::collections::HashMap;
use dyn_std::Constructor;
struct Registry(HashMap<String, Box<dyn FooConstructor>>);
impl Registry {
fn register<T: Foo>(&mut self, name: impl Into<String>) {
self.0.insert(name.into(), Box::new(Constructor::<T>::new()));
}
}
而您的库的用户可以编写如下代码
let mut registry = Registry(HashMap::new());
registry.register::<CustomFooImpl>("custom");
规范
一个特质必须满足以下所有要求,才能通过#[dyn_trait]属性转换为对象安全的特质。
超特质
所有超特质必须是
- 对象安全的
- 或者是上述标准特质的其中之一
Sized将从实例和构造函数特质中自动删除,但保留在原始特质中。
关联常量
它不得有任何关联常量。
关联类型
它不得有任何带有泛型的关联类型。
关联函数
接收器类型
接收器类型是可以用作方法调用接收者的类型。以下类型可以用作接收器类型
Self&Self&mut SelfBox<Self>
请注意,Rc<Self>、Arc<Self>和Pin<P>(其中P是接收器)目前不支持。
参数类型
所有参数都必须是以下类型
- 不包含
Self的类型 - 接收器类型
- 有效参数类型的元组
- 如
Option<T>、Result<T, E>、Vec<T>(其中T、E是有效参数类型)等类型的单子 &dyn、&mut dyn、Box<dyn>类型Fn、FnMut、FnOnce(其中所有参数都是有效的非引用参数类型)。
以下类型是有效的参数类型
(Self, &Self, Box<Self>)
HashMap<i32, HashMap<i32, &Self>>
Result<Vec<Box<dyn Fn(Self) -> Self>>, Option<Self>>
以下类型不是有效的参数类型
&[Self]
Pin<Arc<Self>>
&dyn Fn(&mut Self)
返回类型
返回类型必须是非引用参数类型。
泛型
没有任何类型参数(尽管允许生命周期参数)。
impl Trait被视为类型参数,因此不允许。
致谢
该项目受到以下crate的启发
许可证
MIT.
依赖
~280–730KB
~17K SLoC