You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[API] Allocation-free hex encoding for TraceId / SpanId #3544
Exporters that emit trace/span IDs as hex strings currently have only one option: trace_id.to_string() / span_id.to_string(), both of which heap-allocate a String on every call. On hot export paths this is two small allocations per record/span just to format already-known fixed-size data.
This affects OS-native exporters that pass IDs as a single counted UTF-8 field to the underlying logging substrate. OTLP and stdout do not hit this — OTLP serializes raw bytes, and stdout writes Display directly to the formatter.
It would be useful for opentelemetry::trace::TraceId / SpanId to expose an allocation-free way to obtain the lowercase hex representation.
Before opening a PR, I'd like your direction on the shape.
Proposal: add TraceId::to_hex() / SpanId::to_hex() returning small owned wrappers (TraceIdHex([u8; 32]) / SpanIdHex([u8; 16])) that expose the lowercase hex as &str (Deref/as_str) and &[u8] (as_bytes), plus Display. const fn, zero new deps, output identical to the current Display/LowerHex. as_bytes() is the zero-cost "single counted UTF-8 field" for the OS-native exporter case.
If you'd rather keep the surface smaller, the alternatives are an encode_hex(&mut buf) -> &str (uuid encode_lower style, no new types) or a plain const fn to_hex_bytes() -> [u8; N].
One call I need from you: materializing &str from the byte buffer is either
(B) str::from_utf8_unchecked(..) — zero-cost and const, the uuid/const-hex idiom; the unsafe is fully contained (private field, single constructor, only ASCII ever written); or
(A) str::from_utf8(..).expect(..) — unsafe-free; cost is a UTF-8 scan of ≤32 ASCII bytes on the &str path only (no alloc, branch never taken), and as_bytes() callers never hit it.
I lean (B) for the zero-cost/const &str. Flagging that the codebase is otherwise unsafe-free (the only unsafe anywhere is one unavoidable pin-projection in -http), so if you'd rather hold that line, (A) is a fine fallback and can be upgraded to (B) later without a breaking change.
Any preference on the shape and on (A) vs (B)? I'll send the PR once you confirm.
Hi! I'd like to work on this. The cleanest approach would be adding a to_hex() method on TraceId and SpanId that writes into a stack-allocated [u8; 32] / [u8; 16] array and returns a str ref into it, avoiding any heap allocation on the hot export path. Would the team be open to that API shape, or would you prefer a formatting wrapper type? Happy to prototype and open a PR.
Activity
bryantbiggs commentedon Jun 8, 2026
Before opening a PR, I'd like your direction on the shape.
Proposal: add
TraceId::to_hex()/SpanId::to_hex()returning small owned wrappers (TraceIdHex([u8; 32])/SpanIdHex([u8; 16])) that expose the lowercase hex as&str(Deref/as_str) and&[u8](as_bytes), plusDisplay.const fn, zero new deps, output identical to the currentDisplay/LowerHex.as_bytes()is the zero-cost "single counted UTF-8 field" for the OS-native exporter case.If you'd rather keep the surface smaller, the alternatives are an
encode_hex(&mut buf) -> &str(uuidencode_lowerstyle, no new types) or a plainconst fn to_hex_bytes() -> [u8; N].One call I need from you: materializing
&strfrom the byte buffer is eitherstr::from_utf8_unchecked(..)— zero-cost andconst, theuuid/const-hexidiom; theunsafeis fully contained (private field, single constructor, only ASCII ever written); orstr::from_utf8(..).expect(..)—unsafe-free; cost is a UTF-8 scan of ≤32 ASCII bytes on the&strpath only (no alloc, branch never taken), andas_bytes()callers never hit it.I lean (B) for the zero-cost/const
&str. Flagging that the codebase is otherwiseunsafe-free (the onlyunsafeanywhere is one unavoidable pin-projection in-http), so if you'd rather hold that line, (A) is a fine fallback and can be upgraded to (B) later without a breaking change.Any preference on the shape and on (A) vs (B)? I'll send the PR once you confirm.
BRGOVIND commentedon Jun 14, 2026
Hi! I'd like to work on this. The cleanest approach would be adding a to_hex() method on TraceId and SpanId that writes into a stack-allocated [u8; 32] / [u8; 16] array and returns a str ref into it, avoiding any heap allocation on the hot export path. Would the team be open to that API shape, or would you prefer a formatting wrapper type? Happy to prototype and open a PR.