Skip to content

Working with Cap'n Proto Types

Louis Thibault edited this page Sep 1, 2022 · 6 revisions

Data Model

The Go types you generated in the previous section are actually around a []byte buffer, with getters and setters operate by indexing the buffer at specific offsets. So technically speaking, there there is no (un)marshal step in Cap'n Proto. Converting a generated type to and from []byte is a simple matter of wrapping and unwrapping the buffer: a constant-time operation on the order of nanoseconds.

In this sense, operations like Marshal and Unmarshal are misnomers. Nevertheless, we use this terminology for two reasons:

  1. it is familiar to most Go developers and it conveys a pattern for effective use; and,
  2. it provides an obvious place for hooking in Cap'n Proto's packed encoding functionality.

Packed encoding is a simple, blisteringly fast compression method similar to the one used by Protocol Buffers.

In this section, we will show you how to "marshal" and "unmarshal" generated types to and from bytes. This is suitable for such things as writing capnp types to a file, or sending a single objet over the network, e.g. in an HTTP request. If you want to stream multiple objects over the network, you'll need a way of "framing" the stream, i.e. separating the byte stream into different objects. For this, we use the Encoder and Decoder types, which we will introduce further below.

Marshalling and Unmarshalling

// TODO

Streaming

The Encoder and Decoder types allow you to stream generated types to and from any io.Writer or io.Reader, respectively.

Here's

package main

import (
    "os"

    "foo/books"
    "capnproto.org/go/capnp/v3"
)

func main() {
    // Create a new Arena for a books.Book type.  The Arena wraps the underlying
    // buffer, providing a low-level access API.  You probably won't ever need to
    // interact with it directly.
    arena := capnp.SingleSegment(nil)

    // Make a brand new empty message.  A Message allocates Cap'n Proto structs within
    // its arena.
    msg, seg, err := capnp.NewMessage(arena)
    if err != nil {
        panic(err)
    }

    // Create a new Book struct.  Every message must have a root struct.
    book, err := books.NewRootBook(seg)
    if err != nil {
        panic(err)
    }
    book.SetTitle("War and Peace")
    book.SetPageCount(1440)

    // Create a new encoder that streams messages to stdout.
    // You can also use NewPackedEncoder if you want to compress
    // the data.
    encoder := capnp.NewEncoder(os.Stdout)

    // Send the book's underlying *capnp.Message 
    err = encoder.Encode(msg)
    if err != nil {
        panic(err)
    }
}

These datatypes can also be read from byte-streams, as follows:

package main

import (
    "fmt"
    "os"

    "foo/books"
    "capnproto.org/go/capnp/v3"
)

func main() {
    // Read the message from stdin.
    msg, err := capnp.NewDecoder(os.Stdin).Decode()
    if err != nil {
        panic(err)
    }

    // Extract the root struct from the message.
    book, err := books.ReadRootBook(msg)
    if err != nil {
        panic(err)
    }

    // Access fields from the struct.
    title, err := book.Title()
    if err != nil {
        panic(err)
    }
    pageCount := book.PageCount()
    fmt.Printf("%q has %d pages\n", title, pageCount)
}

In addition, each type has a .Message() method that returns a capnp.Message, which can be directly marshaled into []bytes using Message.Marshal, and unmarshaled with a corresponding call to Message.Unmarshal.

Lastly, packed encodings are supported via the following methods: