From 55891d73cfbc5359b84c30625c37144e54c9da36 Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 25 Mar 2024 15:45:37 +0100 Subject: [PATCH] proto: add examples for Size, MarshalAppend (regarding allocations) Hopefully this gives users a better understanding of the MarshalAppend entrypoint and what it can be used for, as well as the typical Size usage. Change-Id: I26c9705c3d1dbfea5f30820d41ccabbb88fbb772 Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/573361 Reviewed-by: Lasse Folger Auto-Submit: Michael Stapelberg LUCI-TryBot-Result: Go LUCI Reviewed-by: Cassondra Foesch Reviewed-by: Damien Neil --- proto/encode.go | 5 ++++- proto/encode_test.go | 29 +++++++++++++++++++++++++++++ proto/size_test.go | 18 ++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 proto/size_test.go diff --git a/proto/encode.go b/proto/encode.go index d8f0dbe40..5f69539cb 100644 --- a/proto/encode.go +++ b/proto/encode.go @@ -72,7 +72,7 @@ type MarshalOptions struct { // Marshal returns the wire-format encoding of m. // -// This is the most convenient entry point for encoding a Protobuf message. +// This is the most common entry point for encoding a Protobuf message. // // See the [MarshalOptions] type if you need more control. func Marshal(m Message) ([]byte, error) { @@ -120,6 +120,9 @@ func emptyBytesForMessage(m Message) []byte { // MarshalAppend appends the wire-format encoding of m to b, // returning the result. +// +// This is a less common entry point than [Marshal], which is only needed if you +// need to supply your own buffers for performance reasons. func (o MarshalOptions) MarshalAppend(b []byte, m Message) ([]byte, error) { // Treat nil message interface as an empty message; nothing to append. if m == nil { diff --git a/proto/encode_test.go b/proto/encode_test.go index 388c85dbe..73b3b1817 100644 --- a/proto/encode_test.go +++ b/proto/encode_test.go @@ -309,3 +309,32 @@ func ExampleMarshal() { // Output: 125ns encoded into 2 bytes of Protobuf wire format: // 10 7d } + +// This example illustrates how to marshal (encode) many Protobuf messages into +// wire-format encoding, using the same buffer. +// +// MarshalAppend will grow the buffer as needed, so over time it will grow large +// enough to not need further allocations. +// +// If unbounded growth of the buffer is undesirable in your application, you can +// use [MarshalOptions.Size] to determine a buffer size that is guaranteed to be +// large enough for marshaling without allocations. +func ExampleMarshalOptions_MarshalAppend_sameBuffer() { + var m proto.Message + + opts := proto.MarshalOptions{ + // set e.g. Deterministic: true, if needed + } + + var buf []byte + for i := 0; i < 100000; i++ { + var err error + buf, err = opts.MarshalAppend(buf[:0], m) + if err != nil { + panic(err) + } + // cap(buf) will grow to hold the largest m. + + // write buf to disk, network, etc. + } +} diff --git a/proto/size_test.go b/proto/size_test.go new file mode 100644 index 000000000..d46479ed3 --- /dev/null +++ b/proto/size_test.go @@ -0,0 +1,18 @@ +// Copyright 2024 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package proto_test + +import ( + "google.golang.org/protobuf/proto" +) + +// Checking if [Size] returns 0 is an easy way to recognize empty messages: +func ExampleSize() { + var m proto.Message + if proto.Size(m) == 0 { + // No fields set (or, in proto3, all fields matching the default); + // skip processing this message, or return an error, or similar. + } +}