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 过程宏
每月下载量 63
用于 capnp_conv
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::Readable
、capnp_conv::Writable
和TryFrom<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的group
、enum
、union
和data
类型需要带有类型指定符的字段属性。
组
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: ()
}
枚举
该宏在处理枚举时有两种选项:enum
和 enum_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
,则将跳过这些字段。当从读取器中读取结构体时,如果字段是指针类型(List
,Struct
,Text
,Data),并且该字段在 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_read
或skip
的字段上,将在读取期间将字段设置为调用指定的函数的输出,而不是默认值。
联合体不能包含默认覆盖。
泛型
泛型既支持结构体也支持枚举。
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
时,结构体必须具有T
、Y
和R
,即使它只使用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