<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ilya Korolev</title>
    <description>The latest articles on DEV Community by Ilya Korolev (@antako).</description>
    <link>https://dev.to/antako</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4058295%2F8e7bb23a-9d6d-4391-b504-b1c9128ca356.jpg</url>
      <title>DEV Community: Ilya Korolev</title>
      <link>https://dev.to/antako</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/antako"/>
    <language>en</language>
    <item>
      <title>One contract. Any format. Zero mapping</title>
      <dc:creator>Ilya Korolev</dc:creator>
      <pubDate>Sat, 01 Aug 2026 18:50:04 +0000</pubDate>
      <link>https://dev.to/antako/one-contract-any-format-zero-mapping-293c</link>
      <guid>https://dev.to/antako/one-contract-any-format-zero-mapping-293c</guid>
      <description>&lt;h2&gt;
  
  
  CONTRACT: One Schema Instead of N×M Serializers
&lt;/h2&gt;

&lt;p&gt;Any C++ project where data doesn't stay inside a single process sooner or&lt;br&gt;
later runs into the same thing: the same struct needs to be shown in debug&lt;br&gt;
output, written to a binary protocol, handed off via protobuf to an&lt;br&gt;
external service, and logged as JSON. Without a shared mechanism, this&lt;br&gt;
turns into a scatter of hand-written mappings for every type: &lt;code&gt;toJson&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;toProto&lt;/code&gt;, &lt;code&gt;debugPrint&lt;/code&gt;, &lt;code&gt;writeBinary&lt;/code&gt;. Every schema change has to be&lt;br&gt;
synchronized by hand across all of them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;N data classes × M formats = N×M hand-written mappings
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We're not the only ones in C++ who've hit exactly this wall. Other&lt;br&gt;
libraries address the same problem:&lt;br&gt;
&lt;a href="https://github.com/getml/reflect-cpp" rel="noopener noreferrer"&gt;reflect-cpp&lt;/a&gt; (C++20 reflection,&lt;br&gt;
JSON/BSON/CBOR/msgpack/TOML/XML/YAML/Avro/Cap'n Proto and others),&lt;br&gt;
&lt;a href="https://github.com/GerHobbelt/serde-cpp" rel="noopener noreferrer"&gt;serde-cpp&lt;/a&gt; (inspired by Rust's&lt;br&gt;
serde), and the older Boost.Serialization/Cereal. The&lt;br&gt;
question is what CONTRACT actually adds on top of "one schema, many&lt;br&gt;
formats," given that the idea itself isn't new.&lt;/p&gt;
&lt;h2&gt;
  
  
  Not Just Another Serializer
&lt;/h2&gt;

&lt;p&gt;CONTRACT solves N×M differently than a serialization library would:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;N contracts + M adapters
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The schema is declared once, right in the C++ struct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;uint64_t&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;paid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;CONTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;CONTRACT(...)&lt;/code&gt; doesn't serialize anything by itself. It gives you a&lt;br&gt;
stable list of fields: ID, name, type, traversal order. A serializer can&lt;br&gt;
use that list, or something else entirely can: a validator, a schema&lt;br&gt;
exporter, an audit dump. CONTRACT is not runtime reflection, not a&lt;br&gt;
generic serializer, and not a schema-first code generator. The core is&lt;br&gt;
responsible for what a field is and how to get to it, not for what it&lt;br&gt;
should look like on the wire. That's already the adapter's job.&lt;/p&gt;

&lt;p&gt;This is where CONTRACT and reflect-cpp differ: reflect-cpp&lt;br&gt;
answers the question "how do I serialize a struct into N formats."&lt;br&gt;
CONTRACT answers a different question: "how do I give a struct a stable&lt;br&gt;
contract that serialization may use - or may not."&lt;/p&gt;
&lt;h2&gt;
  
  
  Before / After
&lt;/h2&gt;

&lt;p&gt;Without a shared schema, the &lt;code&gt;Order&lt;/code&gt; from the example above would look&lt;br&gt;
roughly like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Order&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;uint64_t&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;paid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;toJson&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ostringstream&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;customer&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;amount&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;",&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;paid&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paid&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"true"&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"false"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"}"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ostream&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"Order{id="&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;", customer="&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;customer&lt;/span&gt;
            &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;", amount="&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;amount&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;", paid="&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;paid&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="s"&gt;"}"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;writeBinary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;uint8_t&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* ... */&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plus a separate &lt;code&gt;order.proto&lt;/code&gt;, &lt;code&gt;protoc&lt;/code&gt;, generated &lt;code&gt;.pb.h&lt;/code&gt;/&lt;code&gt;.pb.cc&lt;/code&gt;, and a&lt;br&gt;
hand-written &lt;code&gt;toProto&lt;/code&gt;/&lt;code&gt;fromProto&lt;/code&gt; between &lt;code&gt;Order&lt;/code&gt; and &lt;code&gt;OrderProto&lt;/code&gt;. Four&lt;br&gt;
formats - four places where you have to remember to make any schema&lt;br&gt;
change.&lt;/p&gt;

&lt;p&gt;With CONTRACT, &lt;code&gt;Order&lt;/code&gt; is declared once (see above), and after that the&lt;br&gt;
format is just a choice of adapter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;adapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;binary_out&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;proto_out&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Order&lt;/code&gt; doesn't change at all in this code - only what you pass it&lt;br&gt;
through changes.&lt;/p&gt;

&lt;p&gt;The difference shows the other way too: adding a new field is one line&lt;br&gt;
in &lt;code&gt;CONTRACT(...)&lt;/code&gt;. In the "before" version, it's an edit in several&lt;br&gt;
places at once: &lt;code&gt;toJson&lt;/code&gt;, &lt;code&gt;debugPrint&lt;/code&gt;, &lt;code&gt;writeBinary&lt;/code&gt;, the &lt;code&gt;.proto&lt;/code&gt; file,&lt;br&gt;
and the hand-written &lt;code&gt;toProto&lt;/code&gt;/&lt;code&gt;fromProto&lt;/code&gt;. And it's easy to forget one&lt;br&gt;
of them.&lt;/p&gt;
&lt;h2&gt;
  
  
  What We Wanted From the Model
&lt;/h2&gt;

&lt;p&gt;Even this simple example already shows a model broader than "one&lt;br&gt;
declaration instead of N mappings." We wanted:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The contract to be a stable schema, declared once in the C++ type
itself, independent of format:

&lt;ul&gt;
&lt;li&gt;ID&lt;/li&gt;
&lt;li&gt;name&lt;/li&gt;
&lt;li&gt;type&lt;/li&gt;
&lt;li&gt;field access method.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A field to not need to be a physical struct member:

&lt;ul&gt;
&lt;li&gt;reuse a schema through inheritance (&lt;code&gt;BASE&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;compute a value on the fly (&lt;code&gt;PROPERTY&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;reference data the type doesn't own (&lt;code&gt;REFERENCE&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Format and behavior to belong entirely to the adapter, not the core -
serialization here is just one of the possible roles, not the only
one:

&lt;ul&gt;
&lt;li&gt;serialization (protobuf, JSON, compact, binary, ...)&lt;/li&gt;
&lt;li&gt;validation&lt;/li&gt;
&lt;li&gt;schema export&lt;/li&gt;
&lt;li&gt;audit dumping.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;A separate attribute layer to sit on top of fields - policy that
different adapters interpret each in their own way:

&lt;ul&gt;
&lt;li&gt;security&lt;/li&gt;
&lt;li&gt;check&lt;/li&gt;
&lt;li&gt;unit.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;None of it to add excess runtime overhead.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  The Core of the Contract
&lt;/h2&gt;

&lt;p&gt;At the base is what we wanted as the first point - a stable schema with&lt;br&gt;
a field's ID, name, type, and access method. In practice, this is a&lt;br&gt;
small compile-time API that everything else is built on top of:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;field_count&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                 &lt;span class="c1"&gt;// how many fields&lt;/span&gt;
&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;field_at&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                 &lt;span class="c1"&gt;// field descriptor by index&lt;/span&gt;
&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;dispatch_field_by_id&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// find a field by id&lt;/span&gt;
&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;type_name&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Order&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;                   &lt;span class="c1"&gt;// "Order"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A field descriptor carries the ID, name, and access method&lt;br&gt;
(&lt;code&gt;get&lt;/code&gt;/&lt;code&gt;set&lt;/code&gt;/&lt;code&gt;ref&lt;/code&gt;). That's enough for an adapter to build anything&lt;br&gt;
around it, from a wire codec to a debug dump, without ever reaching&lt;br&gt;
directly into the struct itself.&lt;/p&gt;

&lt;p&gt;By the way, why an ID at all instead of just a name: it's not only about&lt;br&gt;
wire size (a name is longer, slower to compare when packing) - the real&lt;br&gt;
danger is elsewhere. If the same identifier, whether a name or a number,&lt;br&gt;
gets reused for a field with an incompatible type, old and new code will&lt;br&gt;
start interpreting the same bytes differently. For this case, a contract&lt;br&gt;
can explicitly reserve an ID (&lt;code&gt;contract::schema::reserved_id(...)&lt;/code&gt;) -&lt;br&gt;
today that's a purely declarative marker; no adapter checks it yet.&lt;/p&gt;
&lt;h2&gt;
  
  
  Contract Flexibility: BASE, PROPERTY, and REFERENCE
&lt;/h2&gt;

&lt;p&gt;This is the second point: a field doesn't have to be a physical struct&lt;br&gt;
member. CONTRACT has three mechanisms for that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;BASE(Type, offset)&lt;/code&gt;&lt;/strong&gt; plugs another C++ type's contract in as part of&lt;br&gt;
the current one, through regular inheritance, with an ID offset so the&lt;br&gt;
base type's fields don't collide with the derived type's fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;uint64_t&lt;/span&gt; &lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;CONTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Header&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;CONTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Event&lt;/code&gt; gets &lt;code&gt;request_id&lt;/code&gt; at ID 101 (100 + 1) and its own &lt;code&gt;name&lt;/code&gt; at ID 1.&lt;br&gt;
The shared part of the schema is declared once in &lt;code&gt;Header&lt;/code&gt; and reused,&lt;br&gt;
rather than copied into every type that needs it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;PROPERTY(name, id, type)&lt;/code&gt;&lt;/strong&gt; is a contract field with no physical&lt;br&gt;
struct member behind it - instead there's a &lt;code&gt;contract_get&lt;/code&gt;/&lt;code&gt;contract_set&lt;/code&gt;&lt;br&gt;
pair:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;Metric&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;raw_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;CONTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Metric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;PROPERTY&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;doubled_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;contract_get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;contract_fields&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;doubled_count&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;raw_count&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;contract_set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;contract_fields&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;doubled_count&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
         &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;raw_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Adapters see &lt;code&gt;doubled_count&lt;/code&gt; as an ordinary field: they read and write&lt;br&gt;
it the same way they do &lt;code&gt;raw_count&lt;/code&gt;, even though no such field exists in&lt;br&gt;
&lt;code&gt;Metric&lt;/code&gt;'s memory at all. The value is computed on the fly through&lt;br&gt;
&lt;code&gt;contract_get&lt;/code&gt;/&lt;code&gt;contract_set&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;REFERENCE(name, id)&lt;/code&gt;&lt;/strong&gt; is the third kind of field: a contract field&lt;br&gt;
that references data the struct doesn't own. This mechanism is used in&lt;br&gt;
CONTRACT's structured logger, so the value isn't copied on the hot path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;payload_field&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string_view&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;CONTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload_field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;REFERENCE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;value&lt;/code&gt; is a reference, not a copy; the adapter reads it as an ordinary&lt;br&gt;
contract field, but the log call doesn't pay for allocating or copying&lt;br&gt;
the logged value.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Adapter Ecosystem
&lt;/h2&gt;

&lt;p&gt;This is where the third point comes into play - format and behavior&lt;br&gt;
belong to the adapter, not the core. CONTRACT currently has six adapter&lt;br&gt;
families, and not all of them are symmetric on read/write. Below, in&lt;br&gt;
decreasing order of significance and completeness:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Adapter&lt;/th&gt;
&lt;th&gt;Write&lt;/th&gt;
&lt;th&gt;Read&lt;/th&gt;
&lt;th&gt;Comment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;protobuf&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;complete: wire-compatible with real protobuf, beats libprotobuf in 20/28 measurements (&lt;a href="https://github.com/antako76/Contract/blob/main/article/en/protobuf-adapter-performance.md" rel="noopener noreferrer"&gt;separate article&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;binary&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;complete: native layout with no wire overhead, the fastest option - but not a cross-platform format by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;compact&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;complete: its own compact wire format, the only one today that actually skips unknown fields on read&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;write-only, but with security modes (redact/omit) - structured logging is built on it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;structured logging&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;a thin layer on top of the JSON adapter for logs, not a separate wire format&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;console/debug&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;human-readable debug output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YAML&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;✓&lt;/td&gt;
&lt;td&gt;read-only: a strict config reader, not an export format - CONTRACT can't write YAML yet&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The architecture is the same across all of them: &lt;code&gt;contract&lt;/code&gt; knows the&lt;br&gt;
fields and their identity and knows nothing about format, &lt;code&gt;io&lt;/code&gt; works&lt;br&gt;
with bytes and a cursor. But &lt;code&gt;writer&lt;/code&gt;/&lt;code&gt;reader&lt;/code&gt; and &lt;code&gt;codec&amp;lt;T&amp;gt;&lt;/code&gt; already&lt;br&gt;
belong to a specific adapter and know its wire rules - each format has&lt;br&gt;
its own.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Attribute Layer
&lt;/h2&gt;

&lt;p&gt;The fourth point was a separate attribute layer on top of fields,&lt;br&gt;
attached to a field in the list right next to its ID and interpreted by&lt;br&gt;
each adapter in its own way. The set of vocabularies is extensible - a new&lt;br&gt;
one can be added without touching the core; today &lt;code&gt;security&lt;/code&gt; and &lt;code&gt;check&lt;/code&gt;&lt;br&gt;
actually work.&lt;/p&gt;

&lt;p&gt;Take a typical authorization event with PII and a secret inside it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;AuthEvent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;user_email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;uint64_t&lt;/span&gt; &lt;span class="n"&gt;duration_ns&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;CONTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AuthEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;security&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;sensitive&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;security&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;security&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;no_log&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;security&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;encrypt&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;duration_ns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same &lt;code&gt;AuthEvent&lt;/code&gt;, without a single &lt;code&gt;if&lt;/code&gt; in the business logic,&lt;br&gt;
behaves differently depending on the adapter. Console/debug and JSON&lt;br&gt;
currently take &lt;code&gt;secret&lt;/code&gt;/&lt;code&gt;no_log&lt;/code&gt;/&lt;code&gt;sensitive&lt;/code&gt; into account - each with&lt;br&gt;
its own default - and the example below shows how to turn on the right&lt;br&gt;
mode through &lt;code&gt;options&lt;/code&gt;. In binary, &lt;code&gt;encrypt()&lt;/code&gt; today is a key-based&lt;br&gt;
obfuscation mechanism rather than cryptographic encryption. This kind of&lt;br&gt;
per-field policy is&lt;br&gt;
possible with ordinary serializers too (protobuf has its own field&lt;br&gt;
options); what's different about CONTRACT is that the same attribute is&lt;br&gt;
understood the same way by several independently implemented adapters -&lt;br&gt;
not that it's fundamentally unreachable for the rest.&lt;/p&gt;

&lt;p&gt;Here's what that looks like in a structured log (a simplified version of&lt;br&gt;
&lt;code&gt;examples/logging.cpp&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;SecretPayment&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;uint64_t&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string_view&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;CONTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SecretPayment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;security&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;adapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;security_mode&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;redact&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opt&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;SecretPayment&lt;/span&gt; &lt;span class="n"&gt;secret_payment&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"tok_live_123"&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"payment_sensitive"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Captured sensitive payment metadata"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret_payment&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A fragment of the output (see &lt;code&gt;examples/logging.cpp&lt;/code&gt; for the full line):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"payment_sensitive"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"payment"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nl"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;redacted&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;token&lt;/code&gt; went into the log as &lt;code&gt;"&amp;lt;redacted&amp;gt;"&lt;/code&gt; because the calling code&lt;br&gt;
decided so through &lt;code&gt;opt.json.secret&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Not at the Cost of Speed
&lt;/h2&gt;

&lt;p&gt;And last, the fifth point: none of this should create extra runtime&lt;br&gt;
overhead. One declaration instead of N×M is, first and foremost, about&lt;br&gt;
convenience, but it doesn't come at the expense of performance: CONTRACT's&lt;br&gt;
protobuf adapter was compared against real libprotobuf across 14&lt;br&gt;
scenarios. CONTRACT came out faster in 20 measurements out of 28.&lt;br&gt;
Details, methodology, and two clearly documented exceptions are in&lt;br&gt;
&lt;a href="https://github.com/antako76/Contract/blob/main/article/en/protobuf-adapter-performance.md" rel="noopener noreferrer"&gt;a separate article about the protobuf adapter&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  What CONTRACT Doesn't Do
&lt;/h2&gt;

&lt;p&gt;So as not to create the impression that this is a solution for&lt;br&gt;
everything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;not runtime reflection - field traversal is unrolled at compile time.&lt;/li&gt;
&lt;li&gt;not a generic serializer - the format and its rules belong entirely to
the adapter; the core doesn't choose or dictate a format.&lt;/li&gt;
&lt;li&gt;not a schema-first code generator - there's no separate schema file or
generation step; the schema is the C++ struct itself.&lt;/li&gt;
&lt;li&gt;not a place for buffers, SQL, or third-party runtime code - that's the
responsibility of a specific adapter, not the core.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Example: A Config From YAML + Debug Output
&lt;/h2&gt;

&lt;p&gt;Last but not least - code from the repository&lt;br&gt;
(&lt;code&gt;examples/yaml_file_read.cpp&lt;/code&gt;): the same contract is read by the YAML&lt;br&gt;
adapter and printed by the debug adapter.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;PaymentConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="kt"&gt;uint32_t&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;CONTRACT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PaymentConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;adapters&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;reader&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;file_buffer_input&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;file_buffer_input&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"payment_config.yaml"&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;PaymentConfig&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="n"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;contract&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;cout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given this &lt;code&gt;payment_config.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;payment&lt;/span&gt;
&lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
&lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;payments&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the output - taken from the built binary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PaymentConfig:
  service: "payment"  # #1 std::string
  port: 8080          # #2 u32
  enabled: true       # #3 bool
  tags:               # #4 std::vector&amp;lt;std::string&amp;gt;, size=3
    - "api"           # [0]
    - "payments"      # [1]
    - "production"    # [2]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same &lt;code&gt;PaymentConfig&lt;/code&gt;, the same contract. Each field's ID and type&lt;br&gt;
make it into the output on their own, without a single line of code&lt;br&gt;
written specifically for formatting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Macro, Not C++26 Reflection
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Won't reflection eliminate the need for CONTRACT entirely?&lt;/strong&gt; C++26&lt;br&gt;
reflection can enumerate a struct's members without a macro. That will&lt;br&gt;
most likely genuinely simplify declaring and implementing the contract -&lt;br&gt;
less hand-written text for enumerating physical fields. But the model&lt;br&gt;
itself isn't going anywhere: CONTRACT still has to define what a stable&lt;br&gt;
ID is that doesn't change across schema evolution (see above), what an&lt;br&gt;
attribute-as-policy is (&lt;code&gt;security::secret()&lt;/code&gt;, &lt;code&gt;schema::reserved_id()&lt;/code&gt;),&lt;br&gt;
and what counts as a field when there's no physical member behind it&lt;br&gt;
(&lt;code&gt;PROPERTY&lt;/code&gt;). And this doesn't touch the adapters at all: they've worked&lt;br&gt;
with an already-assembled contract, and will keep working that way no&lt;br&gt;
matter how the schema was declared - macro or reflection. Reflect-cpp&lt;br&gt;
already shows today what plain reflection is missing for this model: it&lt;br&gt;
has neither a stable ID, nor an attribute layer, nor computed fields.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Aren't macros themselves just bad practice?&lt;/strong&gt; Partly fair: text&lt;br&gt;
substitution with no scoping is a real cost. But we specifically fought&lt;br&gt;
unreadable compile errors: make a typo and give two fields the same ID,&lt;br&gt;
and you get a clear &lt;code&gt;static_assert&lt;/code&gt;&lt;br&gt;
(&lt;code&gt;"CONTRACT field ids must be unique after BASE offsets are applied"&lt;/code&gt;),&lt;br&gt;
not a page of template garbage. But &lt;code&gt;CONTRACT(...)&lt;/code&gt; isn't a macro that&lt;br&gt;
hides logic or control flow; it generates declarative field descriptors,&lt;br&gt;
the same way &lt;code&gt;Q_OBJECT&lt;/code&gt; does in Qt, &lt;code&gt;TEST(...)&lt;/code&gt; does in gtest, or&lt;br&gt;
&lt;code&gt;BOOST_DESCRIBE_STRUCT&lt;/code&gt; does in Boost.Describe. And until static&lt;br&gt;
reflection becomes mainstream, it's the most practical tool for&lt;br&gt;
declaring a field's metadata once, in the C++ type itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;The point of CONTRACT is simple: declare the schema once, next to the&lt;br&gt;
type, and then do whatever you want with the data - write it to a binary&lt;br&gt;
protocol or protobuf, read a config from YAML, print it to debug output&lt;br&gt;
or a structured log - with no pile of service functions per task and no&lt;br&gt;
hand-written per-format field mapping. And none of that costs you speed:&lt;br&gt;
the adapters are fast.&lt;/p&gt;

&lt;p&gt;That's the whole point, in the end: one contract, any format, zero&lt;br&gt;
mapping.&lt;/p&gt;

&lt;p&gt;Code - &lt;a href="https://github.com/antako76/Contract" rel="noopener noreferrer"&gt;github.com/antako76/Contract&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>opensource</category>
      <category>performance</category>
      <category>systemdesign</category>
    </item>
  </channel>
</rss>
