#capnp #attributes #enums #field #macro #traits #capnp-conv

过程宏 capnp_conv_macros

提供实现capnp读写特征的属性宏

4个版本 (2个破坏性更新)

0.3.1 2024年6月14日
0.3.0 2023年11月25日
0.2.0 2023年11月25日
0.1.0 2023年10月22日

#1602 in 过程宏

Download history 77/week @ 2024-04-14 139/week @ 2024-04-21 31/week @ 2024-04-28 215/week @ 2024-05-05 179/week @ 2024-05-12 191/week @ 2024-05-19 25/week @ 2024-05-26 88/week @ 2024-06-02 137/week @ 2024-06-09 52/week @ 2024-06-16 23/week @ 2024-06-23 14/week @ 2024-06-30 22/week @ 2024-07-07 10/week @ 2024-07-14 12/week @ 2024-07-21 18/week @ 2024-07-28

每月下载量 63
用于 capnp_conv

MIT 许可证

64KB
1K SLoC

capnp_conv

capnp_conv 宏通过实现所有必要的 Capn' Proto Rust 生成的builder/reader调用,促进了Cap'n Proto结构体/联合体/联合/组的Rust对应物之间的转换。

受到(似乎已被放弃的?)现有PR @realcr的启发。

使用方法

以下capnp模式文件直接转换为以下rust文件

struct SomeStruct { }

enum CapnpEnum {
    val1 @0;
    val2 @1;
}

struct CapnpStruct {
  voidVal   @0   :Void;
  i32Val    @1   :Int32;
  textVal   @2   :Text;
  dataVal   @3   :Data;
  structVal @4   :SomeStruct;
  enumVal   @5   :CapnpEnum;
  listVal   @6   :List(SomeStruct);
}
#[capnp_conv(some_struct)]
pub struct SomeStruct {}

#[capnp_conv(capnp_struct)]
pub struct RustStruct {
  void_val: (),
  i32_val: i32,
  text_val: String,
  #[capnp_conv(type = "data")]
  data_val: Vec<u8>,
  struct_val: SomeStruct,
  #[capnp_conv(type = "enum")]
  enum_val: CapnpEnum,
  list_val: Vec<SomeStruct>,
}

capnp_conv过程宏实现了capnp_conv::Readablecapnp_conv::WritableTryFrom<Reader>特性,这些特性处理所有读取/写入操作

fn read(reader: capnp_struct::Reader) -> Result<RustStruct, capnp::Error> {
  RustStruct::read(reader)?
}
fn write(rust_struct: RustStruct, builder: capnp_struct::Builder) {
  rust_struct.write(builder)
}

特殊类型处理

Capnp的groupenumuniondata类型需要带有类型指定符的字段属性。

Capnp的group通过单独的Rust struct表示。

struct CapnpStruct {
    groupVal :group {
        val @0 :Void;
    }
}
#[capnp_conv(capnp_struct)]
pub struct RustStruct {
  #[capnp_conv(type = "group")]
  group_val: RustStructGroup,
}
#[capnp_conv(capnp_struct::group_val)]
pub struct RustStructGroup {
  val: ()
}

枚举

该宏在处理枚举时有两种选项:enumenum_remote。由于生成的 capnp 文件已经包含枚举定义,可以直接使用 enum。这样可以避免编写额外的枚举定义,但有时定义一个独立的枚举是有用的,例如,如果需要派生特例或对枚举使用其他宏。在这种情况下,使用与单独定义的 rust 枚举一起使用的 enum_remote。在 rust 枚举上使用时,capnp_conv 宏会为其 capnp 对应物生成 from/into 特例实现。

enum CapnpEnum {
    val1 @0;
    val2 @1;
}
struct CapnpStruct {
    enumVal       @0 :CapnpEnum;
    enumValRemote @1 :CapnpEnum;
}
#[capnp_conv(CapnpEnum)]
pub enum RustEnum {
  Val1,
  Val2,
}
#[capnp_conv(capnp_struct)]
pub struct RustStruct {
  #[capnp_conv(type = "enum")]
  enum_val: CapnpEnum,
  #[capnp_conv(type = "enum_remote")]
  enum_val_remote: RustEnum,
}

联合体

联合体可以用两种不同的方式表示。一种是通过使用 rust enum,但也可以通过使用包含在 Option<T> 中的字段并包含 #[capnp_conv(union_variant)] 属性宏的 struct 来表示 capnp 联合体。这消除了为无名称联合体创建单独项的需要,但可能更难以操作。

Rust 枚举变体在使用 #[capnp_conv(type = xxx)] 字段属性时具有相同的要求。

struct CapnpStruct {
    namedUnion :union {
        val1 @0 :Void;
        val2 @1 :Void;
    }
    union {
        val1 @1 :Void;
        val2 @2 :Void;
    }
}
#[capnp_conv(capnp_struct)]
pub struct RustStruct {
  #[capnp_conv(type = "union")]
  named_union: RustStructUnion,
  #[capnp_conv(type = "unnamed_union")]
  unnamed_union: RustStructUnamedUnion,
}
#[capnp_conv(capnp_struct::named_union)]
pub enum RustStructUnion {
  Val1(()),
  Val2(()).
}
#[capnp_conv(capnp_struct)]
pub enum RustStructUnamedUnion {
  Val1(()),
  Val2(()).
}

// or

#[capnp_conv(capnp_struct)]
pub struct RustStruct {
  #[capnp_conv(type = "union")]
  named_union: RustStructUnion,
  #[capnp_conv(union_variant)]
  val1: Option<()>,
  #[capnp_conv(union_variant)]
  val2: Option<()>,
}
#[capnp_conv(capnp_struct::named_union)]
pub struct RustStructUnion {
  #[capnp_conv(union_variant)]
  val1: Option<()>,
  #[capnp_conv(union_variant)]
  val2: Option<()>,
}

数据

capnp 的 Data 类型在功能上与 List(UInt8) 相同,它们都用 Vec<u8> 在 rust 中表示。然而,capnpc 为两种类型生成两个不同的结构体来处理读写操作。无法指定哪个 Vec<u8> 被意图表示,这需要在 Data 类型的情况下使用字段属性。

struct CapnpStruct {
    named_union :union {
        list @0 :List(UInt8);
        data @1 :Data;
    }
}
#[capnp_conv(capnp_struct)]
pub struct RustStruct {
  list: Vec<u8>,
  #[capnp_conv(type = "data")]
  data: Vec<u8>,
}

额外功能

capnp_conv 包括其他一些可以通过设置字段属性中的选项来启用的功能

重命名字段

Rust 字段名通常必须与它们的 capnp 对应字段名(转换为适当的 rust 情况)匹配。使用 name 属性,可以解开它们。

struct CapnpStruct {
    capVal @0 :Void;
    capnpUnion :union {
        capVal1 @1 : Void;
        capVal2 @2 : Void;
    }
}
#[capnp_conv(capnp_struct)]
pub struct RustStruct {
  #[capnp_conv(name = "capVal")] //or #[capnp_conv(name = "cap_val")]
  arbitrary_name: (),
  #[capnp_conv(name = "capnp_union")]
  #[capnp_conv(type = "union")]
  rust_union: RustUnnion,
}
#[capnp_conv(capnp_struct::capnp_union)]
pub enum RustUnion {
  #[capnp_conv(name = "capVal1")]
  ArbitraryName1(())
  #[capnp_conv(name = "cap_val2")]
  ArbitraryName2(())
}

可选字段

Option<T> 包装字段表示该字段是可选的。当将结构体写入构建器时,如果可选字段是 None,则将跳过这些字段。当从读取器中读取结构体时,如果字段是指针类型(ListStructTextData),并且该字段在 capnp 消息中未设置,则可选字段将被设置为 None。原始类型和枚举总是被读取并设置为 Some

  • 联合体不能包含可选字段。
  • 组和联合体不能是可选的。
struct CapnpStruct {
    capVal @0 :Void;
}
#[capnp_conv(capnp_struct)]
pub struct RustStruct {
  cap_val: Option<()>,
}

跳过字段

  • #[capnp_conv(skip_write)]添加到字段的属性中,将导致该字段不会被写入。类似于将其设置为可选,使用None

  • #[capnp_conv(skip_read)]添加到字段的属性中,将导致该字段永远不会被读取。在读取过程中,它将被设置为字段类型的默认值(注意字段类型必须实现Default特质),或者如果字段是可选的,则为None。这可以通过设置字段的default属性进一步配置(见下文)。

  • #[capnp_conv(skip)]跳过读取和写入。

  • 联合体不能包含跳过的字段。

默认覆盖字段

#[capnp_conv(default = "path_to_func_to_call")]属性设置到配置为skip_readskip的字段上,将在读取期间将字段设置为调用指定的函数的输出,而不是默认值。

联合体不能包含默认覆盖。

泛型

泛型既支持结构体也支持枚举。

struct CapnpStruct(T,Y) {
  tVal @0 :T;
  list @1 :List(Y);
  unionVal :union {
      tVal @2 :T;
      list @3 :List(Y);
  }
}
#[capnp_conv(capnp_struct)]
pub struct RustStruct<T,Y> {
  t_val: T,
  list: Vec<Y>,
  #[capnp_conv(type = "union")]
  union_val: RustStructUnionVal<T,Y>,
}
#[capnp_conv(capnp_struct::union_val)]
pub enum RustStructUnionVal<T,Y> {
  TVal(T),
  List(Vec<Y>),
}

限制

capnp模式的一个功能在Rust中不易实现,即嵌套结构定义。这通常不是问题,因为它们可以作为扁平化的Rust结构体实现,但结合嵌套的capnp结构体/联合体/组可以访问其所有祖先的所有泛型类型,这可能会对Rust模型造成问题。

例如

struct ParentStruct(T,Y,R) {
    tVal @0 :T;
    yVal @1 :Y;
    rVal @2 :R;
    unionVal :union {
        voidVal @3 :Void,
        tVal    @4 :T,
    }

    struct ChildStruct {
        tVal @0 :T
    }
}

在Rust中定义parent_struct::child_struct时,结构体必须具有TYR,即使它只使用T。Rust不允许结构体有它们未使用的泛型,但我们可以使用PhantomData来克服这一点

pub struct ChildStruct<T,Y,R> {
 t_val: T,
 phantom: PhantomData<*const (Y,R)>,
}

在读取/写入时,PhantomData类型将被自动跳过,即使没有字段属性。

不幸的是,Rust枚举没有“PhantomVariant”,所以对于联合体,可以将任何变体的第二个字段设置为PhantomData

pub enum UnionVal(T,Y,R) {
  VoidVal((), PhantomData<*const (Y,R)>),
  TVal(T),
}

未来工作

短期

  • 添加对顶级Vec读取和写入的支持
  • 添加对anypointer类型的支持
  • 添加更多验证,以便编译器在宏未正确使用时提供更多线索。
    • 联合体必须至少有2个字段(既适用于枚举也适用于结构体联合)
    • 不能有超过1个未命名的联合
    • 列表不能将泛型类型作为其类型
    • 枚举必须全部是单元类型或复杂类型。不能混合使用match
    • 断言具有类型指定符的字段不是原始类型、blob、void或列表(可以在常规解析中完成)
    • 具有 类型 = "数据" 属性的字段必须是 Vec<u8> 类型
  • 完成测试编写。需要优先处理
    • 跳过和默认字段
    • 联合结构表示
  • 确认 as_turbofish() 函数是否足够处理所有可能情况(特别是嵌套泛型类型? Type1<Type2<T>>

长期目标

  • 允许在结构体/枚举中包含其自身类型的字段(具有不同的泛型)的情况中允许装箱字段。
  • 允许结构体包含生命周期和泛型约束,这些约束会传递到生成的实现中。
  • 为表示为 capnp 联合的结构体添加一个方便的 clear_enum_fields 函数,该函数将所有联合字段设置为 None

依赖项

~285–730KB
~17K SLoC