- 参考 https://www.jianshu.com/p/f1110b22cb5c
编码方案, Cap'n Proto编码的数据格式跟在内存里面的布局是一致的,所以可以直接将编码好的structure直接字节存放到硬盘上面。
点击(此处)折叠或打开
-
Cap'n Proto的编码是方案是独立于任何平台的,但在现在的CPU上面(小端序)会有更高的性能。数据的组织类似compiler组织struct:固定宽度,固定偏移,以及合适的内存对齐,对于可变的数组使用pointer嵌入,而pointer也是使用的偏移存放而不是绝对地址。整数使用的是小端序,因为多数现代CPU都是小端序的。
-
-
其实如果熟悉C或者C++的结构体,就可以知道Cap'n Proto的编码方式就跟struct的内存布局差不多。
-
-
点击(此处)折叠或打开
- 跟Protobuf一样,Cap'n Proto也需要定义描述文件,然后通过capnp的编译器编译成特定语言的对象使用.
- 示例
-
@0xdbb9ad1f14bf0b36; # unique file ID, generated by `capnp id`
-
-
struct Person {
-
name @0 :Text;
-
birthdate @3 :Date;
-
-
email @1 :Text;
-
phones @2 :List(PhoneNumber);
-
-
struct PhoneNumber {
-
number @0 :Text;
-
type @1 :Type;
-
-
enum Type {
-
mobile @0;
-
home @1;
-
work @2;
-
}
-
}
-
}
-
-
struct Date {
-
year @0 :Int16;
-
month @1 :UInt8;
-
day @2 :UInt8;
-
}
-
-
类型是定义在名字后面的,通常来说,对于一个变量来说,我们可能{BANNED}最佳关注的是它的名字,一个好的命名,就很容易让大家知道是干啥的。譬如上面的name一看就知道是表示的用户的名字。这点跟c语言是反的,它是先类型,在变量名,不过很多后续的语言,譬如go,rust等都是先名字,再类型了。
- @N用来给struct里面的field进行编号,编号从0开始,而且必须是连续的(这点跟Protobuf不一样)。上面birthdate虽然看起来在email和phones的前面,但是它的编号较大,实际编码的时候会放到后面。
点击(此处)折叠或打开
-
使用 #进行注释,注释应该跟在定义的后面,或者新启一行:
-
struct Date {
-
# A standard Gregorian calendar date.
-
-
year @0 :Int16;
-
# The year. Must include the century.
-
# Negative value indicates BC.
-
-
month @1 :UInt8; # Month number, 1-12.
-
day @2 :UInt8; # Day number, 1-30.
-
}
-
-
内置类型
-
Void: Void
-
Boolean: Bool
-
Integers: Int8, Int16, Int32, Int64
-
Unsigned integers: UInt8, UInt16, UInt32, UInt64
-
Floating-point: Float32, Float64
-
Blobs: Text, Data
-
Lists: List(T)
-
-
Void只有一个可能的值,使用0 bits进行编码,通常很少使用,但是可以作为union的member。
-
Text通常是UTF-8编码的,使用NULL结尾的字符串。
-
Data是任意二进制数据。
- List是一个泛型类型,我们可以用特定类型去特化实现,譬如List(Int32)就是一个Int32的List
结构体其实类似于c的struct,field的有名字,有类型定义,同时需要编号:
struct Person { name @0 :Text; email @1 :Text; }
Field也可以有默认值:
foo @0 :Int32 = 123; bar @1 :Text = "blah"; baz @2 :List(Bool) = [ true, false, false, true ]; qux @3 :Person = (name = "Bob", email = "bob@example.com"); corge @4 :Void = void; grault @5 :Data = 0x"a1 40 33";
点击(此处)折叠或打开
-
Union是定义在struct里面同一个位置的一组fields,一次只能允许一个field被设置,我们使用不一样的tag来获知当前哪个field被设置了,不同于c里面的union,它不是类型,只是简单的fields聚合。
-
struct Person {
-
# ...
-
-
employment :union {
-
unemployed @4 :Void;
-
employer @5 :Company;
-
school @6 :School;
-
selfEmployed @7 :Void;
-
# We assume that a person is only one of these.
-
}
-
}
-
-
union可以没有名字,但是一个struct里面{BANNED}最佳多只能包含一个没名字的union:
-
-
Union里面的field需要跟struct的field一起编号。
-
我们在上面的union中使用了Void类型,这个类型没有任何额外的信息,仅仅是为了跟其他状态区分。
-
通常,当一个struct初始化的时候,在union里面具有{BANNED}最佳小number field会被默认的设置,如果不想默认设置任何field,我们可以用在union里面的{BANNED}最佳小number定义一个unset的field。
-
我们可以将当前存在的field加入一个新的union,并且不会破坏当前数据的兼容性。
-
-
-
group将一组fields封装到特定的作用域里面:
-
-
struct Person {
-
# ...
-
-
# Note: This is a terrible way to use groups, and meant
-
# only to demonstrate the syntax.
-
address :group {
-
houseNumber @8 :UInt32;
-
street @9 :Text;
-
city @10 :Text;
-
country @11 :Text;
-
}
-
}
-
-
group 里面的fields仍然是struct的fields,需要跟其他struct的fields一起编号。
-
- group 的用法一般用在 union 里边,用于后期扩展.
点击(此处)折叠或打开
-
动态类型域
-
Struct可以定义field的类型为AnyPointer,类似于c里面的void*.
-
-
枚举
-
Enum就是一组符号值的集合:
-
enum Rfc3092Variable {
-
foo @0;
-
bar @1;
-
baz @2;
-
qux @3;
-
# ...
-
}
-
-
Enum的成员必须从0开始编号,在c语言里面,enum通常都是数字类型的,但是在Cap'n Proto里面,它还可以是其他值
-
-
-
-
Interface是一组methods的集合,各个method可以有参数,有返回值,methods也必须从0开始编号。Interface支持继承,同样也支持多继承
-
-
interface Node {
-
isDirectory @0 () -> (result :Bool);
-
}
-
-
interface Directory extends(Node) {
-
list @0 () -> (list: List(Entry));
-
struct Entry {
-
name @0 :Text;
-
node @1 :Node;
-
}
-
-
create @1 (name :Text) -> (file :File);
-
mkdir @2 (name :Text) -> (directory :Directory);
-
open @3 (name :Text) -> (node :Node);
-
delete @4 (name :Text);
-
link @5 (name :Text, node :Node);
-
}
-
-
interface File extends(Node) {
-
size @0 () -> (size: UInt64);
-
read @1 (startAt :UInt64 = 0, amount :UInt64 = 0xffffffffffffffff)
-
-> (data: Data);
-
# Default params = read entire file.
-
-
write @2 (startAt :UInt64, data :Data);
-
truncate @3 (size :UInt64);
-
}
-
-
泛型
-
我们可以定义泛型的struct或者interface
-
struct Map(Key, Value) {
-
entries @0 :List(Entry);
-
struct Entry {
-
key @0 :Key;
-
value @1 :Value;
-
}
-
}
-
-
struct People {
-
byName @0 :Map(Text, Person);
-
# Maps names to Person instances.
-
}
-
定义了一个泛型的Map,然后在People里面用Text,Person作为参数来特化这个Map,如果我们了解c++的模板,就可以知道他们差不多。
-
-
-
泛型方法
-
interface也可以提供泛型method:
-
interface Assignable(T) {
-
# A generic interface, with non-generic methods.
-
get @0 () -> (value :T);
-
set @1 (value :T) -> ();
-
}
-
-
interface AssignableFactory {
-
newAssignable @0 [T] (initialValue :T)
-
-> (assignable :Assignable(T));
-
# A generic method.
-
}
-
定义了一个泛型的interface,然后在对应的factory里面,创建这个interface的method就是泛型的method。
-
-
-
const来定义常量
-
const pi :Float32 = 3.14159;
-
const bob :Person = (name = "Bob", email = "bob@example.com");
-
const secret :Data = 0x"9f98739c2b53835e 6720a00907abd42f";
-
-
const foo :Int32 = 123;
-
const bar :Text = "Hello";
-
const baz :SomeStruct = (id = .foo, message = .bar);
-
-
通常常量都都定义在全局scope里面,我们通过.来进行引用获取
-
-
-
嵌套,作用域以及别名
-
我们可以在struct或者interface里面嵌套常量,别名或者新的类型定义。
-
-
struct Foo {
-
struct Bar {
-
#...
-
}
-
bar @0 :Bar;
-
}
-
-
struct Baz {
-
bar @0 :Foo.Bar;
-
}
-
-
struct Qux {
-
using Foo.Bar;
-
bar @0 :Bar;
-
}
-
-
struct Corge {
-
using T = Foo.Bar;
-
bar @0 :T;
-
}
-
-
-
import导入其他文件的类型定义
-
struct Foo {
-
# Use type "Baz" defined in bar.capnp.
-
baz @0 :import "bar.capnp".Baz;
-
}
-
-
也可以直接使用using来设置别名
-
using Bar = import "bar.capnp";
-
-
struct Foo {
-
# Use type "Baz" defined in bar.capnp.
-
baz @0 :Bar.Baz;
-
}
-
using import "bar.capnp".Baz;
-
-
struct Foo {
-
baz @0 :Baz;
-
}
-
-
-
唯一ID
-
每个Cap'n Proto文件都必须有唯一的一个64bit ID,使用capnp id生成。譬如{BANNED}最佳开始例子里面的file ID
-
# file ID
- @0xdbb9ad1f14bf0b36