14 releases
0.3.4 | Jul 12, 2024 |
---|---|
0.3.3 | Jul 9, 2024 |
0.2.5 | Jul 2, 2024 |
0.2.3 | Jun 30, 2024 |
0.1.2 | Jun 27, 2024 |
#1547 in Procedural macros
1,056 downloads per month
Used in 8 crates
(5 directly)
59KB
1K
SLoC
dyn_derive
Inherit and derive object-unsafe traits for dynamic Rust.
Introduction
Object safety is a property of traits in Rust that determines whether the trait can be used as a trait object. However, the requirement for object safety is quite strict, limiting the expressiveness of the type system.
For example, you cannot simply write:
// 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.
}
This crate provides a procedural macro for transforming object-unsafe traits into object-safe ones:
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!
}
Although there are still some limitations, this technique works smoothly in my scenarios.
Supertraits
Supertraits is also required to be object-safe if the trait needs to be used as a trait object. However, many useful traits are not object-safe, such as Clone
and PartialEq
.
To tackle this issue, this crate transforms the supertraits into object-safe ones, so that they can be used as supertraits and be derived for your custom types.
Basic Example
Below is a basic example of how to use this 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);
}
Non-Derivable Traits
Taking the Add
trait as an example:
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")
}
Supported Traits
The following std traits are supported:
Clone
Neg
,Not
Add
,Sub
,Mul
,Div
,Rem
BitAnd
,BitOr
,BitXor
,Shl
,Shr
AddAssign
,SubAssign
,MulAssign
,DivAssign
,RemAssign
BitAndAssign
,BitOrAssign
,BitXorAssign
,ShlAssign
,ShrAssign
PartialEq
,Eq
,PartialOrd
,Ord
More std traits and custom traits may be supported in the future.
Methods
Note: This part is not yet complete.
In Rust, associate functions can be divided into two types: methods and non-methods, depending on whether their first parameter is named 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);
}
This crate supports both methods and non-methods, but they are treated differently. Methods and non-methods are separated into two traits, namely instance and constructor. They are both object-safe.
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>);
}
The original Foo
trait (which may or may not be object-safe) can be wrapped by Instance
and Constructor
types in order to be used as an instance or constructor.
impl FooInstance for ::dyn_std::Instance<Foo> {}
impl FooConstructor for ::dyn_std::Constructor<Foo> {}
If you are developing a library, you may write code like this:
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()));
}
}
And the user of your library may write code like this:
let mut registry = Registry(HashMap::new());
registry.register::<CustomFooImpl>("custom");
Specification
A trait should satisfy the all following requirements to be transformed into an object-safe trait by the #[dyn_trait]
attribute.
Supertraits
All supertraits must be:
- either object-safe,
- or one of the above std traits.
Sized
will be automatically removed from the supertraits for instance and constructor traits, but retained for the original trait.
Associated Constants
It must not have any associated constants.
Associated Types
It must not have any associated types with generics.
Associated Functions
Receiver Types
Receiver types are types that can be used as the receiver of a method call. The following types can be used as receiver types:
Self
&Self
&mut Self
Box<Self>
Note that Rc<Self>
, Arc<Self>
, and Pin<P>
(where P
is receiver) are not currently supported.
Parameters Types
All the parameters must be of the following types:
- types that does not contain
Self
, - receiver types,
- tuples of valid parameter types,
- monads such as
Option<T>
,Result<T, E>
,Vec<T>
(whereT
,E
are valid parameter types), &dyn
,&mut dyn
,Box<dyn>
ofFn
,FnMut
,FnOnce
(where all the parameters are valid non-referencing parameter types).
The following types are valid parameter types:
(Self, &Self, Box<Self>)
HashMap<i32, HashMap<i32, &Self>>
Result<Vec<Box<dyn Fn(Self) -> Self>>, Option<Self>>
The following types are NOT valid parameter types:
&[Self]
Pin<Arc<Self>>
&dyn Fn(&mut Self)
Return Types
The return type must be a non-referencing parameter type.
Generics
Not have any type parameters (although lifetime parameters are allowed).
impl Trait
is considered as a type parameter, thus it is not allowed.
Credits
The crate is inspired by the following crates:
License
MIT.
Dependencies
~235–680KB
~16K SLoC