protobuf-go-lite and the StaRPC Ecosystem: Building Lightweight RPCs Without Reflection

protobuf-go-lite serves as the core protobuf code-generation and runtime library for the StaRPC ecosystem, providing static, reflection-free message handling that enables lightweight RPC implementations with minimal binary size and CPU overhead.

The aperturerobotics/protobuf-go-lite repository delivers a specialized Protocol Buffers compiler plugin and runtime designed for performance-critical applications. It generates Go code capable of marshaling, unmarshaling, sizing, cloning, and comparing protobuf messages without using Go reflection. This foundational message layer powers the StaRPC ecosystem, which implements lightweight RPC services in both Go and TypeScript by combining these efficient serialization primitives with transport logic.

The Architectural Relationship

The relationship between protobuf-go-lite and StaRPC represents a clear separation of concerns: protobuf-go-lite handles the message serialization layer, while StaRPC provides the transport, service definition, and client/server scaffolding.

Static, Reflection-Free Message Handling

At the core of protobuf-go-lite is a code generator that produces methods like MarshalVT(), UnmarshalVT(), SizeVT(), and EqualVT() directly on generated Go structs. These implementations reside in features/marshal/marshal.go and features/unmarshal/unmarshal.go, utilizing static type information rather than the reflect package. This approach yields significantly smaller binary sizes and reduces CPU/memory overhead compared to standard protobuf implementations, which is essential for lightweight RPC scenarios.

StaRPC's Transport Layer

According to the repository's README.md (lines 29-33), the ecosystem description states: "Lightweight Protobuf 3 RPCs are implemented in [StaRPC] for Go and TypeScript." StaRPC imports the message types generated by protobuf-go-lite and uses them to encode and decode RPC payloads over the wire, while handling connection management, service dispatch, and streaming semantics.

Generating Code with protoc-gen-go-lite

To utilize protobuf-go-lite within the StaRPC ecosystem, you first define your service in a .proto file and generate Go code using the protoc-gen-go-lite plugin.

Define a service interface:

syntax = "proto3";

package example;

message Request {
  string payload = 1;
}

message Response {
  string result = 1;
}

service Echo {
  rpc Call(Request) returns (Response);
}

Install the plugin and generate the code:


# Install the plugin (once)

go install github.com/aperturerobotics/protobuf-go-lite/cmd/protoc-gen-go-lite@latest

# Run protoc with the lite plugin

protoc --go-lite_out=. --go-lite_opt=features=marshal+unmarshal+size+equal+clone \
       --go-lite_opt=buildTag=lite example.proto

The generator logic in cmd/protoc-gen-go-lite/main.go and generator/generator.go processes these options to emit example.pb.go containing the optimized methods. The features flag selects which capabilities to include, while buildTag=lite ensures the generated code compiles with appropriate build constraints.

Implementing a StaRPC Service

Once generated, the message types integrate seamlessly with StaRPC's server and client APIs. The generated code includes registration stubs and client constructors that StaRPC uses to wire up the RPC handlers.

Server Implementation

Create a server using the generated registration functions:

package main

import (
	"context"
	"log"
	"net"

	starpc "github.com/aperturerobotics/starpc/go"
	pb "myproject/example" // generated by protobuf-go-lite
)

type echoService struct{}

func (s *echoService) Call(ctx context.Context, req *pb.Request) (*pb.Response, error) {
	return &pb.Response{Result: "echo: " + req.Payload}, nil
}

func main() {
	srv := starpc.NewServer()
	pb.RegisterEchoServer(srv, &echoService{})

	l, err := net.Listen("tcp", ":8080")
	if err != nil {
		log.Fatalf("listen: %v", err)
	}
	if err := srv.Serve(l); err != nil {
		log.Fatalf("serve: %v", err)
	}
}

Client Implementation

Consume the service using the generated client constructor:

package main

import (
	"context"
	"log"

	starpc "github.com/aperturerobotics/starpc/go"
	pb "myproject/example"
)

func main() {
	conn, err := starpc.Dial("tcp", "localhost:8080")
	if err != nil {
		log.Fatalf("dial: %v", err)
	}
	client := pb.NewEchoClient(conn)

	resp, err := client.Call(context.Background(), &pb.Request{Payload: "hello"})
	if err != nil {
		log.Fatalf("rpc error: %v", err)
	}
	log.Println("Response:", resp.Result)
}

In this flow, protobuf-go-lite provides the only protobuf-specific code through the generated *.pb.go file and its helper methods. StaRPC consumes these types via features/marshal/marshal.go and features/unmarshal/unmarshal.go to handle payload encoding, while features/json/message.go provides optional JSON serialization helpers for REST-bridge scenarios.

Key Source Files in the Repository

The following files in aperturerobotics/protobuf-go-lite define the static generation system that StaRPC consumes:

Summary

  • protobuf-go-lite generates reflection-free Go code for protobuf message handling, located in aperturerobotics/protobuf-go-lite
  • StaRPC builds upon this foundation to provide lightweight RPC transport and service scaffolding for Go and TypeScript
  • The generator creates methods like MarshalVT() and UnmarshalVT() (defined in features/marshal/marshal.go and features/unmarshal/unmarshal.go) that operate without reflection
  • Use protoc-gen-go-lite with flags --go-lite_opt=features=marshal+unmarshal+size+equal+clone to generate StaRPC-compatible message types
  • The separation of message layer (protobuf-go-lite) and transport layer (StaRPC) enables high-performance, low-overhead RPC systems

Frequently Asked Questions

What is the primary advantage of using protobuf-go-lite over standard Go protobuf libraries?

protobuf-go-lite eliminates Go reflection from the serialization path, generating static methods like MarshalVT() and SizeVT() that directly access struct fields. According to the source code in features/marshal/marshal.go and features/unmarshal/unmarshal.go, this produces smaller binaries and reduces CPU and memory overhead compared to the standard library's reflection-based approach, making it ideal for resource-constrained RPC services.

How does StaRPC utilize protobuf-go-lite generated code?

StaRPC imports the message types generated by protobuf-go-lite and uses them as the wire format for RPC calls. While protobuf-go-lite handles the encoding and decoding via methods like UnmarshalVT(), StaRPC manages the transport layer, connection pooling, and service routing. The README.md explicitly positions StaRPC as the ecosystem that implements lightweight Protobuf 3 RPCs using this library.

Can protobuf-go-lite be used independently of StaRPC?

Yes. protobuf-go-lite functions as a standalone protobuf code generator and runtime. You can use the generated message types with any transport system or store them directly. However, the library is optimized for and commonly paired with StaRPC to form a complete lightweight RPC stack.

Which generator features should be enabled for full StaRPC compatibility?

Enable the complete feature set using --go-lite_opt=features=marshal+unmarshal+size+equal+clone. This ensures the generated code includes MarshalVT() for encoding, UnmarshalVT() for decoding, SizeVT() for buffer pre-allocation, and equality/comparison methods. These methods are invoked by StaRPC during request/response processing to ensure efficient wire protocol handling.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →