#struct #macro-derive #compile-time #proc-macro #duck #field #constduck

过程宏 constduck-procmacro

#[derive(ConstDuck)] 宏的实现

1 个不稳定版本

0.1.0 2021年11月13日

#9#duck


constduck 中使用

MPL-2.0 许可证

6KB
111

crates.io docs

constduck:编译时鸭子类型和反射

constduck 提供了一个过程宏,可以启用对任意结构体类型的编译时鸭子类型和反射。它支持三个主要功能

  • 使用字段名访问任何结构体的字段
  • 从字段到值的映射中构造实例
  • 在编译时反映字段类型

使用方法

在结构体上派生 ConstDuck

#![feature(adt_const_params)]
use constduck::*;

#[derive(ConstDuck)]
struct Donald {
    money: i64,
}

访问字段

当派生 ConstDuck 时,为结构体的所有字段实现了 Field<"fieldname"> 特性。您可以使用该特性编写泛型实现。例如

fn deduct_money<N, T: Field<"money", Ty = N>>(t: &mut T) 
    where N: Clone,
        N: Sub<N, Output = N>,
        N: From<i8> {
    t.set(t.get().clone() - N::from(5i8));
}

deduct_money 将适用于任何具有字段 money 并派生 ConstDuck 的结构体。

您只需为上面的示例编写一个自定义特性。并非总是可以编写自定义特性。例如,考虑以下宏

macro_rules! make_getter {
    ($struct:ident.$field:ident) => {
        impl $struct {
            pub fn $field(&self) -> &/* What to write here? */ {
                &self.$field
            }
        }
    }
}

struct Foo {
    bar: String,
    baz: u32,
}

make_getter!(Foo.bar);

在这种情况下,函数定义需要一个返回类型,但您没有足够的信息来指定类型。使用 constduck,您可以像这样编写此宏

macro_rules! make_getter {
    ($struct:ident.$field:ident) => {
        impl<T> $struct
            where Self: Field<{ stringify!($field) }, Ty = T> {
            pub fn $field(&self) -> &T {
                <Self as Field<{ stringify!($field) }>>::get(self)
            }
        }
    }
}

#[derive(ConstDuck)]
struct Foo {
    bar: String,
    baz: u32,
}

make_getter!(Foo.bar);

构造实例

如果您在宏中存储新结构体的任意表达式,则不能使用表达式的类型作为字段的类型。例如

macro_rules! create_struct {
    ($struct:ident; $($name:ident: $value:expr),*) => {{
        // For some good reason we want to generate a separate wrapper struct first and return that instead of collecting directly into `$struct`.
        struct WrapperStruct<$($name,)*> {
            $($name: $name,)*
        }

        impl<$($name,)*> From<WrapperStruct<$($name,)*>> for $struct {
            fn from(wrapper: WrapperStruct<$($name,)*>) -> Self {
                // wrapper.counter is not guaranteed to be a u32
                // So we cannot do this:
                $struct {
                    counter: wrapper.counter,
                    text: wrapper.text,
                }
            }
        }

        WrapperStruct {
            $($name: $value,)*
        }
    }}
}

我们无法在这里编写 From 的正确实现,因为通常无法将“类型必须有两个字段 counter: u32text: String” 表达为一个约束。

通过为包装结构体实现 WithField,我们可以在宏中像这样编写实现

impl<$($name,)*> From<WrapperStruct<$($name,)*>> for $struct 
    where $struct: ConstructFrom<WrapperStruct<$($name,)*>> {
    fn from(wrapper: WrapperStruct<$($name,)*>) -> Self {
        $struct::construct(wrapper)
    }
}

请参考完整的示例,见 constduck/examples/construct-instances.rs

在编译时反映字段类型

使用 ConstDuck::Reflect,您可以为任何类型(如 #[derive(..)]))实现特质,无需使用procmacro。请参考 constduck/examples/debug-print.rs 中的示例。

(不)稳定性

此项目需要Rust夜间版本,并使用不完整的 adt_const_params 功能。您可能会遇到ICEs。当前API在添加对元组和枚举的支持时可能会破坏。

许可证

constduck 采用Mozilla公共许可证2.0(MPL2.0)。请参阅LICENSE 文件。

依赖关系

~1.5MB
~35K SLoC