#capnp #proto #traits #data #read-write #enums #structs

capnp_conv

提供 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 日

#394 in Rust 模式

Download history 108/week @ 2024-05-02 215/week @ 2024-05-09 240/week @ 2024-05-16 44/week @ 2024-05-23 68/week @ 2024-05-30 43/week @ 2024-06-06 154/week @ 2024-06-13 28/week @ 2024-06-20 15/week @ 2024-06-27 23/week @ 2024-07-04 18/week @ 2024-07-11 11/week @ 2024-07-18 17/week @ 2024-07-25 20/week @ 2024-08-01 38/week @ 2024-08-08 14/week @ 2024-08-15

每月 92 次下载

MIT 许可证

19KB
140

capnp_conv

capnp_conv 宏通过实现所有必要的 Capn' Proto Rust 生成的构建器/读取器调用,简化了 Cap'n Proto 结构体/联合体/联合体/组的 Rust 对应物的转换。

受到 (似乎已废弃的?) 现有 PR by @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。这消除了编写额外枚举定义的需求,但在某些情况下,定义一个单独的枚举很有用,例如,如果需要从枚举中派生特性或使用其他宏。对于这些情况,使用 enum_remote 并与单独定义的 Rust 枚举一起使用。当在 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,但 struct 也可以用包含在 Option<T> 中的字段来表示 capnp 联合,并包含带有 #[capnp_conv)] 属性宏的 #[capnp_conv)] 特性。这消除了为无名联合创建单独项的需求,但可能更难以操作。

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

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) 完全相同,两者在 Rust 中都用 Vec<u8> 来表示。然而,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个未命名的联合体
    • 列表不能将其类型作为其类型
    • 枚举必须要么全部是单元类型,要么是复杂的。不能混合 & 匹配
    • 断言具有类型指定的字段不是原始类型、blob、void或列表(可以在常规解析中完成)
    • 具有 type = "data" 属性的字段必须为类型 Vec<u8>
  • 完成编写测试。需要优先处理。
    • 跳过的字段和默认字段
    • 联合结构表示
  • 确认 as_turbofish() 函数是否足够处理所有可能的案例(特别是嵌套泛型类型?Type1<Type2<T>>

长期目标

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

依赖项

~2MB
~45K SLoC