The rope: assembly is chaining, never memcpy (L1 views)

A rope is the L1 composite — an ordered chain of views over possibly different segments, forming one logical byte sequence (CONTEXT.md §Rope / assembly). A static header segment plus a live DMA payload segment become one payload by chaining them. append links; total_length sums what the links already hold. Nothing is copied, and “assembly” and “reassembly” both mean exactly this.

Rope scatter-gather shows the same type at the egress end; this page is the composition itself.

What to notice

  • A view converts to a one-link rope implicitly. The trivial case is meant to be free, and the single-link value is the hot path — a rope-valued vertex slot costs what a view-valued slot cost.

  • only() is the consumer’s explicit “this is one segment”. Zero copy, and debug-asserted. A consumer that cannot promise contiguity asks for materialize() instead, which is the visible choice between a refcount bump and one flatten copy.

  • The first two links live in inline storage. A one- or two-link chain allocates nothing at all; the third link spills the chain to the heap. That threshold is a cost tuning knob, not a limit — nothing refuses a longer rope, and a caller that knows its final count can try_reserve it up front.

  • operator+ takes its left operand by value. The example checks that the original rope still has its three links afterwards: concatenation chains, it does not consume.

  • A rope is not a list of TLVs. It composes storage; opt.PL composes meaning (structured vs opaque). The two axes are independent, which is why a link boundary may fall mid-header — see the lazy view.

  • Nothing here is conditional — the target builds and runs under every CI leg.

Source

 1/*
 2 * SPDX-License-Identifier: Apache-2.0
 3 * SPDX-FileCopyrightText: Copyright 2026 avatarsd LLC
 4 */
 5
 6/**
 7 * @file
 8 * @brief ONE CONCEPT — a rope is an ordered chain of views; assembly is chaining, never copy.
 9 *
10 * A **rope** is the L1 composite: an ordered chain of views over possibly different segments,
11 * forming one logical byte sequence (`CONTEXT.md` §Rope / assembly). A static header segment
12 * plus a live DMA payload segment become one payload by CHAINING them — `append` links, it
13 * does not `memcpy`, and `total_length` sums what the links already hold.
14 *
15 * Two spellings matter to a first-contact reader. `only()` is the consumer's explicit "this
16 * value is one segment" — the single-link hot path, zero copy. And a rope keeps its first two
17 * links in inline storage, so a one- or two-link chain allocates nothing at all; the third
18 * link spills the chain to the heap. That threshold is a cost tuning knob, not a limit —
19 * nothing refuses a longer rope.
20 *
21 * Runs under ctest as `example_view_rope_compose`; returns non-zero on any failed check.
22 */
23
24#include <cstddef>
25#include <cstdio>
26#include <optional>
27
28#include "libtracer/tracer.hpp"
29
30namespace {
31
32/** @brief Report expectation @p what and record a failure on @p ok. */
33void check(bool& ok, bool cond, const char* what) {
34    std::printf("  [%s] %s\n", cond ? "ok" : "FAIL", what);
35    ok = ok && cond;
36}
37
38/** @brief A fresh 4-byte heap segment filled with @p fill, as a whole-segment view. */
39tr::view::view_t block(std::byte fill) {
40    tr::view::segment_ptr_t seg = tr::view::heap_alloc(4);
41    for (std::byte& b : seg->bytes) b = fill;
42    return tr::view::view_t::over(std::move(seg));
43}
44
45}  // namespace
46
47int main() {
48    bool ok = true;
49
50    tr::view::rope_t one{block(std::byte{0xAA})};  // a view converts to a one-link rope
51    check(ok, one.link_count() == 1, "one link");
52    check(ok, one.total_length() == 4, "four logical bytes");
53    check(ok, one.only().bytes()[0] == std::byte{0xAA},
54          "only() is the single-segment fast path — zero copy, no flatten");
55
56    one.append(block(std::byte{0xBB}));
57    check(ok, one.link_count() == 2 && one.total_length() == 8,
58          "appending chains a second segment into the same logical sequence");
59    check(ok, one.all_host(), "both links are CPU-addressable");
60
61    // The third link spills the chain to the heap. An optimization threshold, not a bound.
62    one.append(block(std::byte{0xCC}));
63    std::printf("rope: %zu links, %zu logical bytes, still zero byte copies\n", one.link_count(),
64                one.total_length());
65    check(ok, one.link_count() == 3 && one.total_length() == 12,
66          "a third link chains just as well");
67
68    tr::view::rope_t tail{block(std::byte{0xDD})};
69    const tr::view::rope_t joined = one + tail;  // concat = chaining, both operands intact
70    check(ok, joined.link_count() == 4 && joined.total_length() == 16,
71          "operator+ chains two ropes");
72    check(ok, one.link_count() == 3, "leaving the left operand's own chain untouched");
73    return ok ? 0 : 1;
74}

See also: views module · views & ownership reference · composition axes.