Skip to content

한국어 | English

Reference index

02. Messaging

This category covers Message (frame ownership), the receive envelope types (Received, TopicMessage, SubscriptionEvent), and the shared send/request/reply operation-builder interfaces every socket type's entry point returns. RoutingID is documented in the Core category, since it is a package-wide value type, not specific to messaging. The exact signatures are owned by internal/native/message.go, received.go, topic_message.go, subscription_event.go, and operations.go, re-exported as aliases through contracts/messaging.go.


Message

Owns one native zlink frame — the unit every send, request, reply, and receive API moves.

empty, err := contracts.NewMessage(nil)
sized, err := contracts.NewMessageWithSize(4096)
fromBytes, err := contracts.NewMessage(payload)
fromString, err := contracts.NewMessageString("payload")

Options.

Member Meaning
NewMessage(data []byte) copies data; nil/empty produces a zero-length message
NewMessageWithSize(size int) writable, zero-initialized storage
NewMessageString(value string) UTF-8 encode then copy
Data() []byte a view valid only until Close; do not retain across goroutines or past the message's lifetime — use Bytes() for that
Bytes() []byte snapshot copy of the payload
Size() payload length in bytes
IsEmpty() whether Size() is zero
Text() / String() both decode Data() as UTF-8
CopyTo(destination []byte) (int, error) copies the payload into a caller-provided slice; errors if destination is too small
TryCopyTo(destination []byte) bool non-erroring variant of CopyTo, discards the error
RefCount() int diagnostic only; returns 0 on failure rather than propagating an error
Clone() / Copy() equivalent — both return (*Message, error), an independent payload copy
Close() error releases the message

Completion result. Every member is synchronous. Close() is idempotent (a nil receiver or already-closed message returns nil); the caller must call it explicitly — there is no finalizer. Sending a message via a MoveMessage-family builder call transfers its native frame to the socket and marks it closed on the Go side (see the operation-builder shape below); sending via a plain Message-family call leaves the caller's Message still owned and still requiring Close().

When to use. Use NewMessageWithSize/NewMessage to build an outbound payload; use Clone() when an independent copy is needed rather than transferring ownership through MoveMessage. Prefer Data() over Bytes() on the hot path when the slice is consumed before the next recv call — Bytes() always allocates a copy.


Received

A received message envelope: its routing metadata, opaque reply token (for a ROUTER request), and request/reply exchange), and message parts. Reuse one instance across recv calls to avoid a per-receive allocation — the socket's receive method resets and repopulates it.

var received contracts.Received
ok, err := router.Recv(&received, contracts.RecvFlagsNone)
if _, replyable := received.ReplyToken(); ok && replyable {
    received.Reply().Message(reply).Submit(ctx)
}

Options. No public constructor — callers declare a zero-value Received{} and pass its address to a socket's receive method (Sockets category), which populates it.

Member Meaning
RoutingID() RoutingID / HasRoutingID() bool the peer routing id and whether it's present
ReplyToken() (ReplyToken, bool) the opaque reply capability and whether it is present
IsSinglePart() bool whether Parts() has exactly one element
Parts() []*Message every message part this envelope holds
FirstPart() (*Message, error) the first part, without ownership transfer — the part remains owned by Received
Reply() ReplyOp starts the shared reply builder; only valid when ReplyToken() is present
Send() SendOp starts the shared send builder, addressed back to this envelope's captured source route
Close() error closes every retained part; safe to call on a nil receiver

Completion result. All members are synchronous. Close() (and the receive method's own reset step) closes every previously retained part before discarding them — reusing a Received without closing the prior contents never leaks native frames.

When to use. Reuse one Received across a receive loop by reference rather than allocating a new one per message. Use Reply() rather than reconstructing the destination route by hand — the routing id and reply token are encapsulated by this builder.


TopicMessage

A received publish: its topic, source routing id, and message parts. Reuse one instance across subscribe-receive calls the same way as Received.

var published contracts.TopicMessage
ok, err := sub.Subscribe(&published, contracts.RecvFlagsNone)
topic := published.Topic()

Options. No public constructor — declare a zero-value TopicMessage{}.

Member Meaning
RoutingID() RoutingID / HasRoutingID() bool the publisher's routing id and whether it's present
Topic() string the topic this publish was sent on
IsSinglePart() bool / Parts() []*Message / FirstPart() (*Message, error) / Close() error same shape as Received

Completion result. Synchronous; same close-then-reuse contract as Received.

When to use. Reuse one instance across a subscribe-receive loop the same way as Received.


SubscriptionEvent

Reports one subscriber's subscribe or unsubscribe, as observed by an XPUB socket.

var evt contracts.SubscriptionEvent
ok, err := xpub.ReceiveSubscriptionEvent(&evt, contracts.RecvFlagsNone)

Options. No public constructor — declare a zero-value SubscriptionEvent{}. All members are value receivers, not pointer.

Member Meaning
RoutingID() RoutingID / HasRoutingID() bool the subscriber's routing id and whether it's present
Subscribed() bool true for a subscribe, false for an unsubscribe
Topic() string the topic that was subscribed or unsubscribed

Completion result. Synchronous; no Close() — this type owns no native resources of its own (unlike Received/TopicMessage, which own message parts).

When to use. Use on an XPUB socket's subscription-event receive path (Sockets category) to observe subscriber churn.


Send / request / reply operation-builder shape

The fluent builder interfaces every socket type's Send/Publish/Request/Reply entry point (Sockets category) returns to accumulate parts, flags, and a terminal submit. Every stage is a distinct Go interface (SendOpSendSubmitOp, RequestOpRequestSubmitOp, ReplyOpReplySubmitOp) — calling a stage-appropriate method returns the next interface in the chain, so a caller cannot call Submit before at least one part has been added.

err := dealer.Send().Message(part1).Message(part2).Submit(ctx)

parts, err := dealer.Request().
    Message(payload).
    Timeout(5 * time.Second).
    Submit(ctx)

err := received.Reply().Message(reply).Submit(ctx)

Options. Every terminal Submit takes a context.Context as its first argument — a cancelled or deadline-exceeded context short-circuits the call with that context's Err() before the native submit runs, which no other language's operation builder does.

Stage Member Meaning
SendOp .Message(*Message) / .MoveMessage(*Message) / .Bytes([]byte)SendSubmitOp start the chain; MoveMessage transfers the message's ownership to the socket on a successful submit
SendSubmitOp same three add methods + .Submit(ctx context.Context) error add parts, completion-backed terminal
RequestOp .Message / .BytesRequestSubmitOp start the request chain
RequestSubmitOp .Timeout(time.Duration) adds a reply-wait timeout
RequestSubmitOp terminal .Submit(ctx context.Context) ([]*Message, error) returns the completion-backed reply parts directly
context.Context passed to send/request/reply Submit bounds the Go waiter; it is not a Core cancel after successful native submit
ReplyOp .Message(*Message)ReplySubmitOp starts the reply chain
ReplySubmitOp .Message(...) / .Submit(ctx context.Context) error flag-free synchronous reply terminal

Completion result. SendSubmitOp.Submit and ReplySubmitOp.Submit return error. RequestSubmitOp.Submit returns ([]*Message, error) after the socket completion queue yields a terminal result; the caller closes every reply message. Every builder is single-use — a second Submit returns an error rather than resubmitting.

When to use. Call Submit(ctx) and handle its direct result in the owning goroutine. Use Received.Reply()/Send() rather than reconstructing the destination route by hand — the routing id and reply token for Reply are encapsulated by the builder.


See internal/native/message.go, received.go, operations.go, and the Go binding spec for the full rationale.