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 模式
每月 92 次下载
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::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
。这消除了编写额外枚举定义的需求,但在某些情况下,定义一个单独的枚举很有用,例如,如果需要从枚举中派生特性或使用其他宏。对于这些情况,使用 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
,则将跳过这些字段。当从读取器读取结构体时,如果可选字段是指针类型(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个未命名的联合体
- 列表不能将其类型作为其类型
- 枚举必须要么全部是单元类型,要么是复杂的。不能混合 & 匹配
- 断言具有类型指定的字段不是原始类型、blob、void或列表(可以在常规解析中完成)
- 具有
type = "data"
属性的字段必须为类型Vec<u8>
- 完成编写测试。需要优先处理。
- 跳过的字段和默认字段
- 联合结构表示
- 确认 as_turbofish() 函数是否足够处理所有可能的案例(特别是嵌套泛型类型?
Type1<Type2<T>>
)
长期目标
- 允许在结构体/枚举中包含自身类型(具有不同泛型)的字段的情况,允许boxed字段。
- 允许结构体包含生命周期和泛型约束,这些约束会传递到生成的impls中。
- 为表示capnp联合的结构体添加一个便利函数
clear_enum_fields
,该函数将所有联合字段设置为None
。
依赖项
~2MB
~45K SLoC