385 lines
12 KiB
Go
385 lines
12 KiB
Go
/*
|
|
Package capnp is a Cap'n Proto library for Go.
|
|
https://capnproto.org/
|
|
|
|
Read the Getting Started guide for a tutorial on how to use this
|
|
package. https://github.com/capnproto/go-capnproto2/wiki/Getting-Started
|
|
|
|
Generating code
|
|
|
|
capnpc-go provides the compiler backend for capnp.
|
|
|
|
# First, install capnpc-go to $PATH.
|
|
go install zombiezen.com/go/capnproto2/capnpc-go
|
|
# Then, generate Go files.
|
|
capnp compile -I$GOPATH/src/zombiezen.com/go/capnproto2/std -ogo *.capnp
|
|
|
|
capnpc-go requires two annotations for all files: package and import.
|
|
package is needed to know what package to place at the head of the
|
|
generated file and what identifier to use when referring to the type
|
|
from another package. import should be the fully qualified import path
|
|
and is used to generate import statement from other packages and to
|
|
detect when two types are in the same package. For example:
|
|
|
|
using Go = import "/go.capnp";
|
|
$Go.package("main");
|
|
$Go.import("zombiezen.com/go/capnproto2/example");
|
|
|
|
For adding documentation comments to the generated code, there's the doc
|
|
annotation. This annotation adds the comment to a struct, enum or field so
|
|
that godoc will pick it up. For example:
|
|
|
|
struct Zdate $Go.doc("Zdate represents a calendar date") {
|
|
year @0 :Int16;
|
|
month @1 :UInt8;
|
|
day @2 :UInt8 ;
|
|
}
|
|
|
|
Messages and Segments
|
|
|
|
In Cap'n Proto, the unit of communication is a message. A message
|
|
consists of one or more segments -- contiguous blocks of memory. This
|
|
allows large messages to be split up and loaded independently or lazily.
|
|
Typically you will use one segment per message. Logically, a message is
|
|
organized in a tree of objects, with the root always being a struct (as
|
|
opposed to a list or primitive). Messages can be read from and written
|
|
to a stream.
|
|
|
|
The Message and Segment types are the main types that application code
|
|
will use from this package. The Message type has methods for marshaling
|
|
and unmarshaling its segments to the wire format. If the application
|
|
needs to read or write from a stream, it should use the Encoder and
|
|
Decoder types.
|
|
|
|
Pointers
|
|
|
|
The type for a generic reference to a Cap'n Proto object is Ptr. A Ptr
|
|
can refer to a struct, a list, or an interface. Ptr, Struct, List, and
|
|
Interface (the pointer types) have value semantics and refer to data in
|
|
a single segment. All of the pointer types have a notion of "valid".
|
|
An invalid pointer will return the default value from any accessor and
|
|
panic when any setter is called.
|
|
|
|
In previous versions of this package, the Pointer interface was used
|
|
instead of the Ptr struct. This interface and functions that use it are
|
|
now deprecated. See https://github.com/capnproto/go-capnproto2/wiki/New-Ptr-Type
|
|
for details about this API change.
|
|
|
|
Data accessors and setters (i.e. struct primitive fields and list
|
|
elements) do not return errors, but pointer accessors and setters do.
|
|
There are a few reasons that a read or write of a pointer can fail, but
|
|
the most common are bad pointers or allocation failures. For accessors,
|
|
an invalid object will be returned in case of an error.
|
|
|
|
Since Go doesn't have generics, wrapper types provide type safety on
|
|
lists. This package provides lists of basic types, and capnpc-go
|
|
generates list wrappers for named types. However, if you need to use
|
|
deeper nesting of lists (e.g. List(List(UInt8))), you will need to use a
|
|
PointerList and wrap the elements.
|
|
|
|
Structs
|
|
|
|
For the following schema:
|
|
|
|
struct Foo @0x8423424e9b01c0af {
|
|
num @0 :UInt32;
|
|
bar @1 :Foo;
|
|
}
|
|
|
|
capnpc-go will generate:
|
|
|
|
// Foo is a pointer to a Foo struct in a segment.
|
|
// Member functions are provided to get/set members in the
|
|
// struct.
|
|
type Foo struct{ capnp.Struct }
|
|
|
|
// Foo_TypeID is the unique identifier for the type Foo.
|
|
// It remains the same across languages and schema changes.
|
|
const Foo_TypeID = 0x8423424e9b01c0af
|
|
|
|
// NewFoo creates a new orphaned Foo struct, preferring placement in
|
|
// s. If there isn't enough space, then another segment in the
|
|
// message will be used or allocated. You can set a field of type Foo
|
|
// to this new message, but usually you will want to use the
|
|
// NewBar()-style method shown below.
|
|
func NewFoo(s *capnp.Segment) (Foo, error)
|
|
|
|
// NewRootFoo creates a new Foo struct and sets the message's root to
|
|
// it.
|
|
func NewRootFoo(s *capnp.Segment) (Foo, error)
|
|
|
|
// ReadRootFoo reads the message's root pointer and converts it to a
|
|
// Foo struct.
|
|
func ReadRootFoo(msg *capnp.Message) (Foo, error)
|
|
|
|
// Num returns the value of the num field.
|
|
func (s Foo) Num() uint32
|
|
|
|
// SetNum sets the value of the num field to v.
|
|
func (s Foo) SetNum(v uint32)
|
|
|
|
// Bar returns the value of the bar field. This can return an error
|
|
// if the pointer goes beyond the segment's range, the segment fails
|
|
// to load, or the pointer recursion limit has been reached.
|
|
func (s Foo) Bar() (Foo, error)
|
|
|
|
// HasBar reports whether the bar field was initialized (non-null).
|
|
func (s Foo) HasBar() bool
|
|
|
|
// SetBar sets the value of the bar field to v.
|
|
func (s Foo) SetBar(v Foo) error
|
|
|
|
// NewBar sets the bar field to a newly allocated Foo struct,
|
|
// preferring placement in s's segment.
|
|
func (s Foo) NewBar() (Foo, error)
|
|
|
|
// Foo_List is a value with pointer semantics. It is created for all
|
|
// structs, and is used for List(Foo) in the capnp file.
|
|
type Foo_List struct{ capnp.List }
|
|
|
|
// NewFoo_List creates a new orphaned List(Foo), preferring placement
|
|
// in s. This can then be added to a message by using a Set function
|
|
// which takes a Foo_List. sz specifies the number of elements in the
|
|
// list. The list's size cannot be changed after creation.
|
|
func NewFoo_List(s *capnp.Segment, sz int32) Foo_List
|
|
|
|
// Len returns the number of elements in the list.
|
|
func (s Foo_List) Len() int
|
|
|
|
// At returns a pointer to the i'th element. If i is an invalid index,
|
|
// this will return an invalid Foo (all getters will return default
|
|
// values, setters will fail).
|
|
func (s Foo_List) At(i int) Foo
|
|
|
|
// Foo_Promise is a promise for a Foo. Methods are provided to get
|
|
// promises of struct and interface fields.
|
|
type Foo_Promise struct{ *capnp.Pipeline }
|
|
|
|
// Get waits until the promise is resolved and returns the result.
|
|
func (p Foo_Promise) Get() (Foo, error)
|
|
|
|
// Bar returns a promise for that bar field.
|
|
func (p Foo_Promise) Bar() Foo_Promise
|
|
|
|
|
|
Groups
|
|
|
|
For each group a typedef is created with a different method set for just the
|
|
groups fields:
|
|
|
|
struct Foo {
|
|
group :Group {
|
|
field @0 :Bool;
|
|
}
|
|
}
|
|
|
|
generates the following:
|
|
|
|
type Foo struct{ capnp.Struct }
|
|
type Foo_group Foo
|
|
|
|
func (s Foo) Group() Foo_group
|
|
func (s Foo_group) Field() bool
|
|
|
|
That way the following may be used to access a field in a group:
|
|
|
|
var f Foo
|
|
value := f.Group().Field()
|
|
|
|
Note that group accessors just convert the type and so have no overhead.
|
|
|
|
Unions
|
|
|
|
Named unions are treated as a group with an inner unnamed union. Unnamed
|
|
unions generate an enum Type_Which and a corresponding Which() function:
|
|
|
|
struct Foo {
|
|
union {
|
|
a @0 :Bool;
|
|
b @1 :Bool;
|
|
}
|
|
}
|
|
|
|
generates the following:
|
|
|
|
type Foo_Which uint16
|
|
|
|
const (
|
|
Foo_Which_a Foo_Which = 0
|
|
Foo_Which_b Foo_Which = 1
|
|
)
|
|
|
|
func (s Foo) A() bool
|
|
func (s Foo) B() bool
|
|
func (s Foo) SetA(v bool)
|
|
func (s Foo) SetB(v bool)
|
|
func (s Foo) Which() Foo_Which
|
|
|
|
Which() should be checked before using the getters, and the default case must
|
|
always be handled.
|
|
|
|
Setters for single values will set the union discriminator as well as set the
|
|
value.
|
|
|
|
For voids in unions, there is a void setter that just sets the discriminator.
|
|
For example:
|
|
|
|
struct Foo {
|
|
union {
|
|
a @0 :Void;
|
|
b @1 :Void;
|
|
}
|
|
}
|
|
|
|
generates the following:
|
|
|
|
func (s Foo) SetA() // Set that we are using A
|
|
func (s Foo) SetB() // Set that we are using B
|
|
|
|
Similarly, for groups in unions, there is a group setter that just sets
|
|
the discriminator. This must be called before the group getter can be
|
|
used to set values. For example:
|
|
|
|
struct Foo {
|
|
union {
|
|
a :group {
|
|
v :Bool
|
|
}
|
|
b :group {
|
|
v :Bool
|
|
}
|
|
}
|
|
}
|
|
|
|
and in usage:
|
|
|
|
f.SetA() // Set that we are using group A
|
|
f.A().SetV(true) // then we can use the group A getter to set the inner values
|
|
|
|
Enums
|
|
|
|
capnpc-go generates enum values as constants. For example in the capnp file:
|
|
|
|
enum ElementSize {
|
|
empty @0;
|
|
bit @1;
|
|
byte @2;
|
|
twoBytes @3;
|
|
fourBytes @4;
|
|
eightBytes @5;
|
|
pointer @6;
|
|
inlineComposite @7;
|
|
}
|
|
|
|
In the generated capnp.go file:
|
|
|
|
type ElementSize uint16
|
|
|
|
const (
|
|
ElementSize_empty ElementSize = 0
|
|
ElementSize_bit ElementSize = 1
|
|
ElementSize_byte ElementSize = 2
|
|
ElementSize_twoBytes ElementSize = 3
|
|
ElementSize_fourBytes ElementSize = 4
|
|
ElementSize_eightBytes ElementSize = 5
|
|
ElementSize_pointer ElementSize = 6
|
|
ElementSize_inlineComposite ElementSize = 7
|
|
)
|
|
|
|
In addition an enum.String() function is generated that will convert the constants to a string
|
|
for debugging or logging purposes. By default, the enum name is used as the tag value,
|
|
but the tags can be customized with a $Go.tag or $Go.notag annotation.
|
|
|
|
For example:
|
|
|
|
enum ElementSize {
|
|
empty @0 $Go.tag("void");
|
|
bit @1 $Go.tag("1 bit");
|
|
byte @2 $Go.tag("8 bits");
|
|
inlineComposite @7 $Go.notag;
|
|
}
|
|
|
|
In the generated go file:
|
|
|
|
func (c ElementSize) String() string {
|
|
switch c {
|
|
case ElementSize_empty:
|
|
return "void"
|
|
case ElementSize_bit:
|
|
return "1 bit"
|
|
case ElementSize_byte:
|
|
return "8 bits"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
|
|
Interfaces
|
|
|
|
capnpc-go generates type-safe Client wrappers for interfaces. For parameter
|
|
lists and result lists, structs are generated as described above with the names
|
|
Interface_method_Params and Interface_method_Results, unless a single struct
|
|
type is used. For example, for this interface:
|
|
|
|
interface Calculator {
|
|
evaluate @0 (expression :Expression) -> (value :Value);
|
|
}
|
|
|
|
capnpc-go generates the following Go code (along with the structs
|
|
Calculator_evaluate_Params and Calculator_evaluate_Results):
|
|
|
|
// Calculator is a client to a Calculator interface.
|
|
type Calculator struct{ Client capnp.Client }
|
|
|
|
// Evaluate calls `evaluate` on the client. params is called on a newly
|
|
// allocated Calculator_evaluate_Params struct to fill in the parameters.
|
|
func (c Calculator) Evaluate(
|
|
ctx context.Context,
|
|
params func(Calculator_evaluate_Params) error,
|
|
opts ...capnp.CallOption) *Calculator_evaluate_Results_Promise
|
|
|
|
capnpc-go also generates code to implement the interface:
|
|
|
|
// A Calculator_Server implements the Calculator interface.
|
|
type Calculator_Server interface {
|
|
Evaluate(Calculator_evaluate_Call) error
|
|
}
|
|
|
|
// Calculator_evaluate_Call holds the arguments for a Calculator.evaluate server call.
|
|
type Calculator_evaluate_Call struct {
|
|
Ctx context.Context
|
|
Options capnp.CallOptions
|
|
Params Calculator_evaluate_Params
|
|
Results Calculator_evaluate_Results
|
|
}
|
|
|
|
// Calculator_ServerToClient is equivalent to calling:
|
|
// NewCalculator(capnp.NewServer(Calculator_Methods(nil, s), s))
|
|
// If s does not implement the Close method, then nil is used.
|
|
func Calculator_ServerToClient(s Calculator_Server) Calculator
|
|
|
|
// Calculator_Methods appends methods from Calculator that call to server and
|
|
// returns the methods. If methods is nil or the capacity of the underlying
|
|
// slice is too small, a new slice is returned.
|
|
func Calculator_Methods(methods []server.Method, s Calculator_Server) []server.Method
|
|
|
|
Since a single capability may want to implement many interfaces, you can
|
|
use multiple *_Methods functions to build a single slice to send to
|
|
NewServer.
|
|
|
|
An example of combining the client/server code to communicate with a locally
|
|
implemented Calculator:
|
|
|
|
var srv Calculator_Server
|
|
calc := Calculator_ServerToClient(srv)
|
|
result := calc.Evaluate(ctx, func(params Calculator_evaluate_Params) {
|
|
params.SetExpression(expr)
|
|
})
|
|
val := result.Value().Get()
|
|
|
|
A note about message ordering: when implementing a server method, you
|
|
are responsible for acknowledging delivery of a method call. Failure to
|
|
do so can cause deadlocks. See the server.Ack function for more details.
|
|
*/
|
|
package capnp // import "zombiezen.com/go/capnproto2"
|