Wire Types

Define a network message format that can evolve without breaking existing consumers. Every service that communicates over a wire needs this.

Hew

Wire types live in the same .hew file as your code. Field numbers (@N) ensure binary compatibility across versions. #[json(camelCase)] controls JSON serialization naming. The compiler enforces that you don't reuse or remove field numbers.

wire_types.hew
#[json(camelCase)]
wire type UserEvent {
    user_id: i32 @1;
    user_age: i32 @2;
}
// Adding a new field later:
// email: String @3;  <- existing consumers ignore @3
fn main() {
    let event = UserEvent {
        user_id: 42,
        user_age: 30,
    };
    println(f"User {event.user_id}, age {event.user_age}");
}

Go + Protobuf

Protobuf definitions live in a separate .proto file using a different language. A code generation step (protoc) produces Go structs before you can compile. Three files, two languages, one build tool.

user_event.proto
// user_event.proto — separate file, separate language
syntax = "proto3";
message UserEvent {
    int32 user_id = 1;
    int32 user_age = 2;
}
build step
# Generate Go code from .proto definition
protoc --go_out=. user_event.proto
main.go
// Use the generated code
import pb "myapp/proto"

event := &pb.UserEvent{UserId: 42, UserAge: 30}
data, err := proto.Marshal(event)
if err != nil {
    log.Fatal(err)
}
var decoded pb.UserEvent
proto.Unmarshal(data, &decoded)

Rust + serde

Serde gives you JSON serialization with derive macros, but no field-number stability for binary evolution. For binary wire compatibility, you need the protobuf toolchain (same as Go) plus a crate like prost.

wire_types.rs
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct UserEvent {
    user_id: i32,
    user_age: i32,
}
// No field numbering — JSON only, no binary wire stability
// For binary stability, need prost crate + .proto file

fn main() {
    let event = UserEvent {
        user_id: 42,
        user_age: 30,
    };
    let json = serde_json::to_string(&event).unwrap();
    let decoded: UserEvent =
        serde_json::from_str(&json).unwrap();
}

What this shows

Developer experience

Hew eliminates the .proto → codegen → import pipeline. Wire types are defined alongside the code that uses them — one file, one compilation step. No separate build tool, no generated code to keep in sync.

Debugging

In protobuf, reusing a field number with a different type causes silent data corruption — the wrong field gets populated with garbled data, and you only notice downstream. Proto3 ignores unknown fields gracefully, but the compiler won't catch a reused number across services. Hew's compiler checks field numbers at compile time, catching reuse and removal before the code ships.

Trade-offs

Protobuf has mature tooling, cross-language support (Java, Python, C++, and more), and an IDL-first design that forces schema documentation. Teams working across multiple languages benefit from a shared .proto definition. Hew's wire types are currently Hew-only — they work well for Hew-to-Hew communication but don't generate bindings for other languages.