<?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: Matti | funkyposts</title>
    <description>The latest articles on DEV Community by Matti | funkyposts (@mahush).</description>
    <link>https://dev.to/mahush</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%2F3819062%2F9b4cb86c-0b56-4bc1-9a17-df4bbd789d09.png</url>
      <title>DEV Community: Matti | funkyposts</title>
      <link>https://dev.to/mahush</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mahush"/>
    <language>en</language>
    <item>
      <title>Mastering State in Modern C++: Making It Protected</title>
      <dc:creator>Matti | funkyposts</dc:creator>
      <pubDate>Thu, 25 Jun 2026 06:12:06 +0000</pubDate>
      <link>https://dev.to/mahush/mastering-state-in-modern-c-making-it-protected-5584</link>
      <guid>https://dev.to/mahush/mastering-state-in-modern-c-making-it-protected-5584</guid>
      <description>&lt;h1&gt;
  
  
  Mastering State in Modern C++: Making It Protected
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Explicit state with protected evolution&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/mahush/mastering-state-in-modern-c-making-it-explicit-7o"&gt;Earlier&lt;/a&gt;, we made state explicit by passing it through pure functions. This naturally allows state and functions to be composed flexibly. Any function can consume or evolve any combination of state, and any state can participate in multiple functions.&lt;/p&gt;

&lt;p&gt;But some state must be adjusted carefully. As systems grow, the logic that evolves a state often becomes scattered across the codebase. Different pieces of code start modifying the same state, and those modifications become implicitly dependent on one another. Understanding or changing the evolution of that state then requires reasoning across many locations at once.&lt;/p&gt;

&lt;p&gt;A snake body is a simple domain-level example. It must remain a connected chain. Movement must preserve that structure, and dead snakes must not move. If any code can arbitrarily modify body segments, those rules become hard to keep consistent.&lt;/p&gt;

&lt;p&gt;The usual object-oriented solution would be to evolve that state through class methods. But we still want state evolution to remain explicit.&lt;/p&gt;

&lt;p&gt;How can we protect state evolution without hiding it?&lt;/p&gt;

&lt;h2&gt;
  
  
  Protecting State Modules
&lt;/h2&gt;

&lt;p&gt;As in a &lt;a href="https://dev.to/mahush/mastering-state-in-modern-c-making-it-encapsulated-27e5"&gt;previous post&lt;/a&gt;, we group state together with the pure functions that operate on it. But this time the focus is different. Previously, the module localized the &lt;em&gt;meaning&lt;/em&gt; of state. Now it localizes the &lt;em&gt;evolution&lt;/em&gt; of state.&lt;/p&gt;

&lt;p&gt;I call this a Protecting State Module. Its purpose is to protect state evolution by preserving validity over time.&amp;nbsp;That includes what must always be true about the state, and how the state may change. So it becomes useful when state has invariants or restricted transitions.&lt;/p&gt;

&lt;p&gt;Because a Protecting State Module owns only state evolution and not state meaning, other functions may depend on the state and build their logic on top of it. The restriction is not about reading, but only about directly modifying it.&lt;/p&gt;

&lt;p&gt;Here is the pattern in a visual nutshell:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flwa97v82ok6igzk3dnmb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flwa97v82ok6igzk3dnmb.png" alt=" " width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  An Example: Protecting Snakes
&lt;/h2&gt;

&lt;p&gt;Let’s return to the &lt;a href="https://github.com/mahush/funkysnakes/tree/v0.1.1" rel="noopener noreferrer"&gt;funkysnakes&lt;/a&gt; project and look at a snake itself.&lt;/p&gt;

&lt;p&gt;The game stores snakes as part of its domain model. This makes the concept available throughout the game. To ensure that snake evolution remains valid, snakes are implemented as a &lt;em&gt;Protecting State Module&lt;/em&gt;, here called &lt;code&gt;snake_model&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The module defines the &lt;code&gt;Snake&lt;/code&gt; type together with the query functions &lt;code&gt;head&lt;/code&gt;, &lt;code&gt;tail&lt;/code&gt; and &lt;code&gt;alive&lt;/code&gt; to enable read access to the current snake state. This allows any part of the game logic to depend on the concept of a snake in a read-only way.&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;namespace&lt;/span&gt; &lt;span class="n"&gt;snake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;snake_model&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Snake&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nl"&gt;private:&lt;/span&gt;
  &lt;span class="n"&gt;Point&lt;/span&gt; &lt;span class="n"&gt;head_&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;Point&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tail_&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;alive_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;friend&lt;/span&gt; &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;head&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;Snake&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;friend&lt;/span&gt; &lt;span class="k"&gt;const&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;Point&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;tail&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;Snake&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;friend&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;alive&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;Snake&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;friend&lt;/span&gt; &lt;span class="n"&gt;Snake&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;friend&lt;/span&gt; &lt;span class="n"&gt;Snake&lt;/span&gt; &lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Snake&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Direction&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;Board&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;friend&lt;/span&gt; &lt;span class="n"&gt;Snake&lt;/span&gt; &lt;span class="n"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Snake&lt;/span&gt;&lt;span class="p"&gt;);&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;Point&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;head&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;Snake&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;s&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;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;Point&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;tail&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;Snake&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;alive&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;Snake&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;s&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;In addition, the module provides the state-evolving functions &lt;code&gt;initial&lt;/code&gt;, &lt;code&gt;move&lt;/code&gt; and &lt;code&gt;kill&lt;/code&gt;. So, whenever a snake needs to be modified, these functions are utilized.&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;namespace&lt;/span&gt; &lt;span class="n"&gt;snake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;snake_model&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="n"&gt;Snake&lt;/span&gt; &lt;span class="n"&gt;initial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Point&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Snake&lt;/span&gt; &lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Snake&lt;/span&gt; &lt;span class="n"&gt;snake&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="n"&gt;direction&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;Board&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;Snake&lt;/span&gt; &lt;span class="n"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Snake&lt;/span&gt; &lt;span class="n"&gt;snake&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;As usual, the shell stores the game state, which includes the snakes.&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;namespace&lt;/span&gt; &lt;span class="n"&gt;shell&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;GameState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;snake_model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Snake&lt;/span&gt; &lt;span class="n"&gt;snake&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Board&lt;/span&gt; &lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GameEngineActor&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;Actor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GameEngineActor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;GameState&lt;/span&gt; &lt;span class="n"&gt;state_&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;Here a piece of logic that is not part of the module but still builds on top of the snake state.&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="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;snakeBitesItself&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;snake_model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Snake&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;snake&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the context of the game loop, the shell triggers evolution of its game state, but the rules for changing the snake are defined by the module. Still other functions like &lt;code&gt;snakeBitesItself&lt;/code&gt; can depend on snakes.&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;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snake_model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snake&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snakeBitesItself&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snake&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snake&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;snake_model&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;kill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snake&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;h2&gt;
  
  
  Deriving the Module Pattern
&lt;/h2&gt;

&lt;p&gt;Generalizing the snake example shows the underlying pattern.&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;namespace&lt;/span&gt; &lt;span class="n"&gt;protecting_state_module&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="nl"&gt;private:&lt;/span&gt;
  &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;value_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;friend&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;value&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;State&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;friend&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;evolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// query functions&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;value&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;State&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// evolving functions&lt;/span&gt;
&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="nf"&gt;evolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Input&lt;/span&gt; &lt;span class="n"&gt;input&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 module defines a state type together with two different types of functions:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Query Functions&lt;/em&gt; simply provide access to state details. As &lt;em&gt;Protecting State Module&lt;/em&gt; share the meaning of its state with the outer world, query functions enable any other function to build on top of its state in a read-only way. This keeps up the flexibility we initially gained with making the state explicit.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Evolving Functions&lt;/em&gt; allow modification of the state, but only as encoded by these functions. Anyone that holds the state may invoke them. So the flexibility of who may evolve state remains, but the how is encapsulated in the module. This way state evolution is protected. &lt;/p&gt;

&lt;p&gt;The protection is enforced through language-level access control using private data members and a set of friend functions. This matters because meaningful state tends to attract direct modification: once other code can see the representation, it is tempting to adjust it in place. Private data members prevent callers from bypassing the module’s rules.&lt;/p&gt;

&lt;p&gt;The tradeoff is that tests can no longer freely construct or modify arbitrary internal states. Instead, they interact with the module through its public state-evolving operations and state queries. Tests apply state changes and verify the resulting behavior through the module’s observable outputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Protection Changes
&lt;/h2&gt;

&lt;p&gt;A &lt;em&gt;Protecting State Module&lt;/em&gt; separates code that may evolve state from code that may not. &lt;/p&gt;

&lt;p&gt;By forcing all state-evolving operations into the module, it localizes state evolution. This means that the rules for how state may change are implemented in one place. To understand how state can evolve, you no longer have to track down every function that relates to it. The rules are no longer scattered across the codebase. &lt;/p&gt;

&lt;p&gt;Looking at this through the dependency lens clarifies the effect of the module boundary: outside the module, a state-modifying dependency no longer implicitly means a dependency on how that state evolves. Instead, state-modifying dependencies are limited to requesting state changes through the module’s interface. This leads to an important distinction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dependencies on &lt;em&gt;triggering&lt;/em&gt; state changes may still be spread throughout the system. Different parts may decide that a snake moves or gets killed. Those are dependencies on the module’s state-evolution interface. &lt;/li&gt;
&lt;li&gt;In contrast, dependencies on the &lt;em&gt;evolution logic&lt;/em&gt; itself are localized to the module. The rules that define what moving or killing a snake actually means are concentrated in the module. As a result, reasoning about state evolution becomes much simpler. Instead of considering the entire system, you only need to consider a single module.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Protection is a direct consequence of this localization. Because dependencies on the evolution logic are contained within the module, outside code cannot arbitrarily modify the state. The state is protected because no dependency on its evolution logic can exist outside the module.&lt;/p&gt;

&lt;p&gt;Note that there are different aspects to protect. Some modules primarily protect invariants that must always hold. Others primarily define valid transitions between states. Many do both, like the snake example. The &lt;code&gt;snake_model&lt;/code&gt; defines how a snake may change (&lt;code&gt;move&lt;/code&gt; or &lt;code&gt;kill&lt;/code&gt;), while also ensuring that the resulting snake remains valid (keeping its body segments connected).&lt;/p&gt;

&lt;p&gt;Even though state evolution is localized, the state itself remains part of the broader system. State changes are protected, but state meaning is not. Therefore read-only dependencies remain meaningful. &lt;/p&gt;

&lt;p&gt;The key takeaway is this: State remains explicit, but the responsibility for evolving it is localized. The state concept may belong to the broader system, while its valid evolution is owned by the module.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is how to Master State
&lt;/h2&gt;

&lt;p&gt;Mastering state begins with &lt;a href="https://dev.to/mahush/mastering-state-in-modern-c-making-it-explicit-7o"&gt;making state explicit&lt;/a&gt;. State becomes visible in the design and flows through the system as data. Functions no longer implicitly depend on hidden mutable state, and state evolution becomes an explicit part of the program structure.&lt;/p&gt;

&lt;p&gt;Once state is explicit, a new set of design questions appears:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who should understand the meaning of state?&lt;/li&gt;
&lt;li&gt;Who should define how state may evolve?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One answer is an &lt;a href="https://dev.to/mahush/mastering-state-in-modern-c-making-it-encapsulated-27e5"&gt;Encapsulating State Module&lt;/a&gt;. It localizes state meaning. The state remains explicit, but only the module itself interprets it.&lt;/p&gt;

&lt;p&gt;Another answer is a &lt;em&gt;Protecting State Module&lt;/em&gt;. It localizes state evolution. The state remains explicit, but only the module itself defines how it may change.&lt;/p&gt;

&lt;p&gt;These concerns are independent. A module may own the meaning of state, the evolution of state, both, or neither.&lt;/p&gt;

&lt;p&gt;This is possible because both patterns build on the same underlying mechanism: grouping state with pure functions operating on that state into a module. Whether this results in encapsulation, protection, or both depends on how the state is shared and on the role those functions play. Encapsulating and Protecting State Modules are therefore not competing module types. They are complementary ways of assigning ownership within the same module.&lt;/p&gt;

&lt;p&gt;The choice is therefore selective. Such modules become useful only when either the meaning of state or the valid evolution of state needs an owner. Otherwise, state can simply remain explicit.&lt;/p&gt;

&lt;p&gt;Ultimately, making state explicit is the foundation. From there, the design challenge becomes deciding whether localizing state meaning or state evolution is worth the additional boundary.&lt;/p&gt;




&lt;p&gt;This post is created with AI assistance for brainstorming and improving formulation. Original and canonical source: &lt;a href="https://github.com/mahush/funkyposts" rel="noopener noreferrer"&gt;https://github.com/mahush/funkyposts&lt;/a&gt; (v01)&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>functional</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Mastering State in Modern C++: Making It Encapsulated</title>
      <dc:creator>Matti | funkyposts</dc:creator>
      <pubDate>Thu, 28 May 2026 07:52:30 +0000</pubDate>
      <link>https://dev.to/mahush/mastering-state-in-modern-c-making-it-encapsulated-43bi</link>
      <guid>https://dev.to/mahush/mastering-state-in-modern-c-making-it-encapsulated-43bi</guid>
      <description>&lt;p&gt;&lt;strong&gt;Explicit state with encapsulated meaning&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/mahush/mastering-state-in-modern-c-making-it-explicit-7o"&gt;Previously&lt;/a&gt;, we made state explicit by assigning state evolution to the core and state persistence and mutation to the shell. This made state visible at the highest level, and changing it required explicit passing between shell and core. &lt;/p&gt;

&lt;p&gt;So far, we looked at state that had broader meaning in the system. The shell knew about snakes and food, and passed them around intentionally. But some state is more localized: a parser may need to remember where it is in an input stream, or a cache may need to remember previous lookups.&lt;/p&gt;

&lt;p&gt;Handling such details as if they had broader significance would promote module implementation details to first-class concepts outside the module. Worse, unrelated logic could start depending on these details unintentionally. The system would still be explicit, but internal implementation details would start leaking into the surrounding code.&lt;/p&gt;

&lt;p&gt;In object-oriented C++, we would usually solve this by putting the state behind a class interface. But if we want to keep the functional mechanics from the previous posts, the question becomes more specific:&lt;/p&gt;

&lt;p&gt;How do we encapsulate internal implementation details without going back to objects whose behavior depends on hidden mutable state?&lt;/p&gt;

&lt;p&gt;This post shows how pure functions operating on shared module state can form a coherent stateful module — one that keeps state evolution explicit while encapsulating module state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Encapsulating State Modules
&lt;/h2&gt;

&lt;p&gt;An &lt;em&gt;Encapsulating State Module&lt;/em&gt; groups a state definition together with pure functions that interpret and use that state. Its state exists solely for the module’s own purposes. No code other than the module itself has a reason to understand what that state means.&lt;/p&gt;

&lt;p&gt;As before, the shell still persists and updates state by replacing the current value with the value returned from a pure function. The difference is not in how the state evolves, but in how it is treated. The shell still stores and threads the state through the system, but only the module itself interprets what that state means.&lt;/p&gt;

&lt;p&gt;Treating the state this way creates the module’s encapsulation boundary. The shell passes the state to the module because its type identifies the owning module, not because the shell understands what the state represents. Code outside the module may store and pass the state, but should not depend on its internal structure.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkozr1xd0w80hz1puojy9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkozr1xd0w80hz1puojy9.png" alt=" " width="800" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  An Example: An Encapsulated Filter
&lt;/h2&gt;

&lt;p&gt;Let’s dive into some code from my &lt;a href="https://github.com/mahush/funkysnakes/tree/v0.1.0" rel="noopener noreferrer"&gt;funkysnakes&lt;/a&gt; project and see the idea in action.&lt;/p&gt;

&lt;p&gt;Snakes are controlled via the arrow keys. But the game loop and key events are asynchronous. At the beginning of each game loop tick, each snake's movement direction is updated based on new key events received since the previous tick. This direction-update logic is implemented in the &lt;code&gt;direction_command_filter&lt;/code&gt; module. As the name suggests the logic is implemented by filtering key-press events.&lt;/p&gt;

&lt;p&gt;Unsurprisingly, this module maintains its own state: a queue of direction commands per player. This state exists only to implement the filtering logic. Outside the filter module, the queue itself has no meaningful role.&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;namespace&lt;/span&gt; &lt;span class="n"&gt;direction_command_filter&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;State&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="n"&gt;PerPlayerDirectionQueue&lt;/span&gt; &lt;span class="o"&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;map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PlayerId&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;deque&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;PerPlayerDirectionQueue&lt;/span&gt; &lt;span class="n"&gt;queues&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 &lt;code&gt;direction_command_filter&lt;/code&gt; module's interface provides two functions. &lt;code&gt;tryAdd&lt;/code&gt;, which feeds direction commands into the filter, and &lt;code&gt;tryConsumeNext&lt;/code&gt;, which retrieves the next direction that passed the filter. Both functions may evolve the module state:&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;namespace&lt;/span&gt; &lt;span class="n"&gt;direction_command_filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;tryAdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;state&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;PerPlayerSnakes&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;snakes&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;DirectionCommand&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;cmd&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;tuple&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PerPlayerDirection&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tryConsumeNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;state&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 regular domain state, &lt;code&gt;PerPlayerSnakes&lt;/code&gt;, is stored alongside the module-local state, &lt;code&gt;direction_command_filter::State&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;namespace&lt;/span&gt; &lt;span class="n"&gt;shell&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;GameState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;PerPlayerSnakes&lt;/span&gt; &lt;span class="n"&gt;snakes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;direction_command_filter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;direction_command_filter_state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GameEngineActor&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;Actor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GameEngineActor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;
    &lt;span class="n"&gt;GameState&lt;/span&gt; &lt;span class="n"&gt;state_&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 &lt;code&gt;GameEngineActor&lt;/code&gt; then threads the module state through &lt;code&gt;tryAdd&lt;/code&gt; and &lt;code&gt;tryConsumeNext&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="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction_command_filter_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;direction_command_filter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;tryAdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction_command_filter_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snakes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_command&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;new_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="n"&gt;direction_command_filter&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;tryConsumeNext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction_command_filter_state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction_command_filter_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deriving the Module Pattern
&lt;/h2&gt;

&lt;p&gt;This example is concrete, but zooming out reveals the underlying pattern:&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;namespace&lt;/span&gt; &lt;span class="k"&gt;module&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;State&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;span class="n"&gt;State&lt;/span&gt; &lt;span class="nf"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Input&lt;/span&gt; &lt;span class="n"&gt;input&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 module defines a state type and a set of pure operations over that state. Each operation receives the current state and returns the updated state.&lt;/p&gt;

&lt;p&gt;The namespace forms the module boundary. It groups the state type together with the operations that interpret and evolve it. In that sense, &lt;code&gt;module&lt;/code&gt; is self-contained: its behavior can be tested by constructing a &lt;code&gt;State&lt;/code&gt;, calling its pure functions, and checking the returned state.&lt;/p&gt;

&lt;p&gt;The namespace also gives the operations a visible scope, much like a class name would for member functions. Client code calls &lt;code&gt;module::operation&lt;/code&gt; and stores &lt;code&gt;module::State&lt;/code&gt;, so the module boundary remains visible at the call site. This also means the state type can simply be called &lt;code&gt;State&lt;/code&gt;: The required meaning comes from the namespace it belongs to.&lt;/p&gt;

&lt;p&gt;Here the shell side at a glance:&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;namespace&lt;/span&gt; &lt;span class="n"&gt;shell&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;State&lt;/span&gt; &lt;span class="n"&gt;module_state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;module_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;module_state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&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 shell persists the current state between calls and threads it through the module operations. It stores the state, passes it to the module, and replaces it with the returned value. The state remains visible, but it is treated differently: surrounding code only carries it through the system but does not interpret it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Encapsulation Changes
&lt;/h2&gt;

&lt;p&gt;Functional design allows broad composition over shared state: pure functions can combine and transform whatever data they need. &lt;em&gt;Encapsulating State Modules&lt;/em&gt; introduce a boundary into that otherwise flexible model. Their state remains explicit and still evolves through pure functions, but it stays localized to the module that owns the corresponding implementation logic instead of becoming generally shared data.&lt;/p&gt;

&lt;p&gt;When looking at this pattern through the dependency lens, we can see that outside code should depend on the module boundary, not on the module implementation. The shell may store &lt;code&gt;module::State&lt;/code&gt; and call &lt;code&gt;module::operation&lt;/code&gt;, and other operations may receive or pass that state as needed. But none of that code should read fields from &lt;code&gt;module::State&lt;/code&gt; or base decisions on its internal structure. Once surrounding code starts depending on those details, the module’s internals leak into the system. The pattern is meant to prevent exactly that: dependencies on module internals.&lt;/p&gt;

&lt;p&gt;In the snake example, &lt;code&gt;direction_command_filter::State&lt;/code&gt; is module-local state, while &lt;code&gt;PerPlayerSnakes&lt;/code&gt; is passed in as an input. The filter module owns its queue state, but can still consume other state when needed. This is an important distinction: an &lt;em&gt;Encapsulating State Module&lt;/em&gt; is not isolated from the rest of the system. It depends on the meaning of other state to perform its work, but it does not expose the meaning of its own state. This keeps the useful flexibility of sharing state across pure functions, while keeping the filter’s queue state localized to the module.&lt;/p&gt;

&lt;p&gt;For this kind of module-internal state, conceptual encapsulation is often sufficient. The state is clearly identified as belonging to a specific module, and has no broader meaning in surrounding code. That makes accidental external use less likely than with generally shared state, where many parts of the system may naturally want to inspect or modify the data. Stronger language-enforced encapsulation is also possible, but it becomes more relevant when state evolution itself needs protection.&lt;/p&gt;

&lt;p&gt;The conceptual encapsulation used here also allows more flexible testing. Tests can still treat the module as a public API and verify behavior through its operations. But when useful, they can also construct relevant states directly, pass them through pure functions, and inspect the returned results without driving an object through long sequences of mutating method calls.&lt;/p&gt;

&lt;p&gt;The key idea is this: state evolution remains explicit, but state interpretation stays localized. From the shell’s perspective, module state is treated like a black box: the shell stores it and threads it through module functions, but never peeks inside. As a result, the state remains local to the module that interprets it, and outside code does not depend on how the state is represented or what it means internally. The state is visible as a value, but encapsulated as a module-owned concept.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;State is not mastered yet&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This post showed how state can remain explicit without becoming a shared concept throughout the system.&lt;/p&gt;

&lt;p&gt;But explicit state and encapsulated meaning are not the full story yet. Sometimes state should be adjusted carefully.&lt;/p&gt;

&lt;p&gt;That is the &lt;a href="https://dev.to/mahush/mastering-state-in-modern-c-making-it-protected-5584"&gt;next step&lt;/a&gt;: keeping state evolution explicit while introducing protection around how that state may evolve.&lt;/p&gt;




&lt;p&gt;This post is created with AI assistance for brainstorming and improving formulation. Original and canonical source: &lt;a href="https://github.com/mahush/funkyposts" rel="noopener noreferrer"&gt;https://github.com/mahush/funkyposts&lt;/a&gt; (v02)&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>functional</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Mastering State in Modern C++: Making It Explicit</title>
      <dc:creator>Matti | funkyposts</dc:creator>
      <pubDate>Wed, 06 May 2026 07:30:00 +0000</pubDate>
      <link>https://dev.to/mahush/mastering-state-in-modern-c-making-it-explicit-7o</link>
      <guid>https://dev.to/mahush/mastering-state-in-modern-c-making-it-explicit-7o</guid>
      <description>&lt;p&gt;&lt;strong&gt;Passing state as data in the functional core–imperative shell&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In object-oriented systems, logic often depends on state hidden inside objects. At a call site, you typically see only a function and its explicit inputs, not the internal state that may also influence its behavior.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://dev.to/mahush/interfacing-pure-functions-with-our-impure-world-5e8f"&gt;functional core – imperative shell&lt;/a&gt; pattern aims to make such dependencies explicit by separating logic from effects. State is one such effect. But what does that separation mean in practice? How can logic still determine what the next state should be?&lt;/p&gt;

&lt;p&gt;Without a clear answer, state easily becomes an implicit dependency again—undermining the very goal of the whole design.&lt;/p&gt;

&lt;h2&gt;
  
  
  How State Flows Through Core and Shell
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;functional core – imperative shell&lt;/em&gt; pattern separates logic from side effects. For state, this means that although the logic in the core drives changes, it does not persist or mutate state.&lt;/p&gt;

&lt;p&gt;This is resolved by letting state live in the shell, while the core receives it as input and returns updates. So, state evolution follows a clear pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;persisted and routed by the shell&lt;/strong&gt;: the shell holds the current state and passes it as data into a core function
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;driven by the core&lt;/strong&gt;: the core function computes and returns an updated state
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;mutated by the shell&lt;/strong&gt;: the shell applies the returned state
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This way, the logic still determines how state evolves—but without persisting or mutating it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fub7qymin326a7fvpgqe7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fub7qymin326a7fvpgqe7.png" alt=" " width="799" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In consequence, there is a clear responsibility split: the shell owns persistence, routing, and mutation, but not meaning. It may store state, pass selected state into core functions, and replace stored state with returned values. However, it does not interpret the internal structure of that state. The core owns that meaning and therefore evolves state through its logic.&lt;/p&gt;

&lt;p&gt;Let’s dive into an example, to see how state flows between shell and core.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeing It in Code
&lt;/h2&gt;

&lt;p&gt;Here is a concrete example from my &lt;a href="https://github.com/mahush/funkysnakes/tree/v0.1.0" rel="noopener noreferrer"&gt;funkysnakes github project&lt;/a&gt; that illustrates the pattern clearly. The project implements the actor-based &lt;em&gt;functional core–imperative shell&lt;/em&gt; architecture introduced in an &lt;a href="https://dev.towhen-one-shell-isnt-enough-scaling-the-functional-core-imperative-shell-pattern-with-actors-in-cpp"&gt;earlier post&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;In this example, the snakes are part of the game state, and moving them means evolving that state.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;GameEngineActor&lt;/code&gt;, as shell, holds the &lt;code&gt;GameState&lt;/code&gt; struct that aggregates the relevant sub-states:&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="c1"&gt;// Shell: where state is persisted&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GameEngineActor&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;Actor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GameEngine&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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;GameState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PerPlayerSnakes&lt;/span&gt; &lt;span class="n"&gt;snakes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;FoodItems&lt;/span&gt; &lt;span class="n"&gt;food_items&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Board&lt;/span&gt; &lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="n"&gt;GameState&lt;/span&gt; &lt;span class="n"&gt;state_&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 core provides the pure function &lt;code&gt;moveSnakes&lt;/code&gt; depending on the sub-states &lt;code&gt;snakes&lt;/code&gt;, &lt;code&gt;board&lt;/code&gt;, and &lt;code&gt;food_items&lt;/code&gt;.&amp;nbsp;It advances the snakes by one step, returning updated &lt;code&gt;snakes&lt;/code&gt; while &lt;code&gt;board&lt;/code&gt; and &lt;code&gt;food_items&lt;/code&gt; are only read. Another pure function &lt;code&gt;replenishFood&lt;/code&gt; follows the same pattern.&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="c1"&gt;// Core: where state evolution is driven&lt;/span&gt;
&lt;span class="n"&gt;PerPlayerSnakes&lt;/span&gt; &lt;span class="nf"&gt;moveSnakes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PerPlayerSnakes&lt;/span&gt; &lt;span class="n"&gt;snakes&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;Board&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;board&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;FoodItems&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;food_items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;FoodItems&lt;/span&gt; &lt;span class="nf"&gt;replenishFood&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FoodItems&lt;/span&gt; &lt;span class="n"&gt;food_items&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;Board&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;board&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;PerPlayerSnakes&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;snakes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, &lt;code&gt;moveSnakes&lt;/code&gt; and &lt;code&gt;replenishFood&lt;/code&gt; are called by the shell within the game loop of the &lt;code&gt;GameEngineActor&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="c1"&gt;// Shell: where state is mutated&lt;/span&gt;
&lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snakes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;moveSnakes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snakes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                           &lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                           &lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;food_items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;food_items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;replenishFood&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;food_items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                  &lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                                  &lt;span class="n"&gt;state_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;snakes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example maps to the &lt;em&gt;functional core–imperative shell&lt;/em&gt; design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the core is the pure functions &lt;code&gt;moveSnakes&lt;/code&gt;, &lt;code&gt;replenishFood&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the shell is the &lt;code&gt;GameEngineActor&lt;/code&gt;, which holds and mutates the state
&lt;/li&gt;
&lt;li&gt;both connect at the function call, where the core’s result is applied to the state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One key detail is how state is perceived differently. In the shell, &lt;code&gt;GameState&lt;/code&gt; persists across calls and is mutated over time—this is what makes it state. In the core, however, there is no notion of state—only data passed in and returned. This is exactly what allows pure functions to drive state changes without mutating state themselves.&lt;/p&gt;

&lt;p&gt;The example also reveals a subtle boundary: selecting sub-states is not interpreting them; it is routing. The shell may pick &lt;code&gt;snakes&lt;/code&gt;, &lt;code&gt;board&lt;/code&gt;, and &lt;code&gt;food_items&lt;/code&gt; from &lt;code&gt;GameState&lt;/code&gt; and pass them to &lt;code&gt;moveSnakes&lt;/code&gt; or &lt;code&gt;replenishFood&lt;/code&gt;, but the interpretation of those values remains in the core.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Design Gives You
&lt;/h2&gt;

&lt;p&gt;Looking at the following benefits through the lens of dependencies reveals why they arise:&lt;/p&gt;

&lt;p&gt;One key aspect is &lt;em&gt;transparency&lt;/em&gt;. All state appears at the top level, making it obvious which state exists. State changes are explicit and easy to follow, rather than scattered deep inside objects as is often the case in nested OOP designs. What makes this possible is that state dependencies are no longer hidden, but explicit.&lt;/p&gt;

&lt;p&gt;There is also high &lt;em&gt;flexibility&lt;/em&gt; in which state can be processed by which function. Here the &lt;code&gt;snakes&lt;/code&gt; sub-state is updated by &lt;code&gt;moveSnakes&lt;/code&gt;, while depending on the &lt;code&gt;board&lt;/code&gt; and the currently existing &lt;code&gt;food_items&lt;/code&gt;. The same state can also be passed into &lt;code&gt;replenishFood&lt;/code&gt;, where &lt;code&gt;food_items&lt;/code&gt; are updated while depending on &lt;code&gt;board&lt;/code&gt; and &lt;code&gt;snakes&lt;/code&gt;. So you are free in two directions: each operation can choose the state it depends on or evolves, and generally logic can be sliced into functions as needed because state can always be passed along. New operations can even be added as pure functions over existing state, without changing existing operations. New dependencies may be introduced, but they remain explicit because they are expressed as function parameters.&lt;/p&gt;

&lt;p&gt;And another great benefit of this design is that it improves &lt;em&gt;testability&lt;/em&gt; significantly. As pointed out in the &lt;a href="https://dev.tobridging-object-oriented-and-functional-thinking-in-modern-cpp"&gt;intro post&lt;/a&gt;, testing stateful code is often complex. To test a specific behavior, you first need to get your software into the right state. This means using the regular API, test-specific APIs, or mocks. This changes when logic no longer depends on hidden internal state, but only on explicit input. With these dependencies exposed, you can simply pass in whatever state you need, making testing straightforward.&lt;/p&gt;

&lt;p&gt;In conclusion, all of these benefits stem from the same shift: state dependencies become explicit instead of hidden.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Reaches Its Limits
&lt;/h2&gt;

&lt;p&gt;So far, we looked at state that can be passed freely through pure functions. Making state explicit gives us transparency, flexibility, and testability. But explicit state alone leaves important ownership questions open.&lt;/p&gt;

&lt;p&gt;Some state is an implementation detail—parser state, cache state, or other internals. Making such state explicit should not mean exposing its meaning throughout the surrounding system.&lt;/p&gt;

&lt;p&gt;And some state should not be modified freely. Making state explicit should not mean that its evolution can be defined anywhere. State often has invariants or valid transitions that should be preserved consistently.&lt;/p&gt;

&lt;p&gt;These concerns lead to additional ways of structuring explicit state. In the next post, we will look at &lt;a href="https://dev.to/mahush/mastering-state-in-modern-c-making-it-encapsulated-27e5"&gt;Encapsulating State Modules&lt;/a&gt;: keeping state explicit while localizing its meaning. After that, we will look at &lt;a href="https://dev.to/mahush/mastering-state-in-modern-c-making-it-protected-5584"&gt;Protecting State Modules&lt;/a&gt;: keeping state explicit while localizing its evolution.&lt;/p&gt;




&lt;p&gt;This post is created with AI assistance for brainstorming and improving formulation. Original and canonical source: &lt;a href="https://github.com/mahush/funkyposts" rel="noopener noreferrer"&gt;https://github.com/mahush/funkyposts&lt;/a&gt; (v03)&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>functional</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Bridging Object-Oriented and Functional Thinking in Modern C++</title>
      <dc:creator>Matti | funkyposts</dc:creator>
      <pubDate>Wed, 29 Apr 2026 07:15:45 +0000</pubDate>
      <link>https://dev.to/mahush/bridging-object-oriented-and-functional-thinking-in-modern-c-4b47</link>
      <guid>https://dev.to/mahush/bridging-object-oriented-and-functional-thinking-in-modern-c-4b47</guid>
      <description>&lt;p&gt;&lt;em&gt;How dependencies make C++ systems hard to test and evolve—and why functional thinking changes it&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Fighting complexity is crucial if you want to succeed in software development. In many real-world C++ projects, however, systems tend to evolve in the opposite direction. Components become tightly coupled, so changes ripple through large parts of the system. State is scattered, reducing transparency about how it evolves over time. Behavior emerges from many interacting parts, making it hard to reason about individual pieces in isolation. &lt;/p&gt;

&lt;p&gt;This also shows up in testing: a significant amount of boilerplate and indirection is needed just to enable it. Establishing specific system states requires substantial setup, and maintaining tests becomes costly, as even small production changes force updates across large parts of the test code.&lt;/p&gt;

&lt;p&gt;The result is a system that is hard to get right—and once it works, it feels rigid and difficult to evolve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where OOP Leads to Complexity
&lt;/h2&gt;

&lt;p&gt;The underlying problems unfold nicely when jumping into the details from the testing point of view. So, let's focus on unit-testable code, that is code structured in smaller parts that can be executed in isolation. To get there we apply the “separation of concerns” principle, which provides the units for testing. And to actually run modules independently, dependency injection comes into play. This means if one module depends on another, it is passed in, normally as a constructor argument. In a unit test where a single module should run in isolation, all dependent modules need to be mocked. So, instead of real dependencies, the test passes its mocks as constructor arguments.&lt;/p&gt;

&lt;p&gt;This reveals the first issue: &lt;strong&gt;No testing without mocking&lt;/strong&gt;. Technically that mocking approach works well—unit testing is enabled. But actually it comes at a high price. The mocks need to be written and maintained, which increases test-specific code and in turn leads to higher test complexity. And we should not neglect this, as only if all the mocks are implemented correctly, the test is helpful.&lt;/p&gt;

&lt;p&gt;Mocks must implement the same interface as the real components they replace, which leads to another problem: &lt;strong&gt;Interfaces create tight coupling between alternative implementations&lt;/strong&gt;. Often these interfaces contain several interdependent function signatures with non-trivial pre- and post-conditions. Any change affects all implementations and thus also all mocks, which increases maintenance effort. With dependency injection and many mocks, this interface-level coupling becomes especially visible and expensive. So, we often trade testability for flexibility here, which can be a poor deal.&lt;/p&gt;

&lt;p&gt;But the challenges don’t stop at interfaces. Once state comes into play, things get even more complicated: &lt;strong&gt;State encapsulated within a class is hard to modify by a test&lt;/strong&gt;. In traditional OOP, private mutable state is fully hidden, so tests cannot set it up directly. A test that needs the object in a specific internal configuration must drive the state there indirectly through the public interface, which often requires multiple method calls, complex sequences, and mocks. This indirect setup adds complexity and makes tests more brittle.&lt;/p&gt;

&lt;p&gt;Actually, state handling in a traditional OOP manner has another problematic dimension: &lt;strong&gt;Generally state is cluttered across the object graph&lt;/strong&gt;. This introduces state dependencies that are hard to see. The more stateful objects exist, the harder it gets to reason about how state evolves in the system. Of course, this compounds the state-related test complexity mentioned earlier.&lt;/p&gt;

&lt;p&gt;Interestingly, testing just makes these problems visible—but they exist in the design itself. E.g. the tight coupling of implementations sharing an interface can be described more generally as: &lt;strong&gt;Inheritance creates tight coupling within the hierarchy&lt;/strong&gt;. So, once a class hierarchy is established, the base class interface becomes almost impossible to change without impacting every derived class. This gets worse the deeper the hierarchy is. Even small adjustments propagate through, making class hierarchies inflexible and expensive to modify.&lt;/p&gt;

&lt;p&gt;Let's step back and draw conclusions. All these problems create a lot of complexity. This way they cause the pain described in the beginning. But there is more to understand here: &lt;strong&gt;The root cause is dependency&lt;/strong&gt;—complexity emerges as different kinds of dependencies accumulate: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;components dependent on each other based on injection can only be executed in isolation by introducing mocks&lt;/li&gt;
&lt;li&gt;the broader an interface, the more complex the dependency between components sharing it&lt;/li&gt;
&lt;li&gt;inheritance causes dependencies between classes within hierarchies&lt;/li&gt;
&lt;li&gt;hidden, distributed, and evolving state creates implicit dependencies across the system&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every issue we looked at comes down to this: dependencies force parts of the system to change together, to be set up together, and to be understood together. As they reinforce each other, complexity grows increasingly fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Functional Thinking Unlocks
&lt;/h2&gt;

&lt;p&gt;So, I started questioning the design ideas I was following for years and searched for different ways of structuring my code to better manage these dependencies. The blog post &lt;a href="https://medium.com/javascript-scene/mocking-is-a-code-smell-944a70c90a6a" rel="noopener noreferrer"&gt;Mocking is Code Smell&lt;/a&gt; helped me see this more clearly. It also pushed me to explore functional ideas that turned out to be particularly useful. Put simply, functional programming offers techniques that can complement a traditional C++ toolbox.  &lt;/p&gt;

&lt;p&gt;These techniques work because they change how we design systems—and with that, which dependencies emerge. To get an idea of what changes, consider pure functions as building blocks for your logic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pure functions receive all dependencies explicitly as input, instead of relying on injected objects. This eliminates implicit dependencies and significantly reduces the need for mocking.&lt;/li&gt;
&lt;li&gt;In a world of pure functions, there is no hidden state, so dependencies on state are always explicit and visible.&lt;/li&gt;
&lt;li&gt;Function interfaces are typically narrower than class interfaces, which keeps dependencies simpler.&lt;/li&gt;
&lt;li&gt;There is no inheritance hierarchy, removing an entire class of dependencies &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In conclusion, when you compose your business logic out of pure functions, dependencies become explicit, fewer, and simpler. This directly mitigates complexity.&lt;br&gt;
The design becomes clearer, and as a result, reasoning, testing, and maintenance improve.&lt;/p&gt;

&lt;p&gt;Just to be explicit, I am not saying functional programming is going to solve all problems, nor that it’s applicable in C++ without limits. I am saying there are aspects that are highly valuable when designing C++ systems. So, I advocate for complementing OOP with FP where appropriate.&lt;/p&gt;

&lt;h2&gt;
  
  
  If This Resonates
&lt;/h2&gt;

&lt;p&gt;Adopting a different way of structuring code takes effort, and how much depends on your learning path. If you’re coming from an OOP-heavy, C++-style background like I did, I might speak your language well enough to further clarify these techniques and design choices. &lt;/p&gt;

&lt;p&gt;My original motivation wasn’t to write about system design or functional programming. I simply wanted to figure out how to apply these ideas effectively in real-world C++ code. After exploring this for a while and finding techniques that work, it feels natural to share and discuss them. &lt;/p&gt;

&lt;p&gt;The funkyposts blog is meant to build a bridge between traditional C++ and functional programming by illustrating practical FP patterns in modern C++. The goal is to provide concrete ways to improve how systems are structured—while staying grounded in real-world constraints. I also welcome feedback and different perspectives to further refine these ideas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dive deeper
&lt;/h2&gt;

&lt;p&gt;The next step is to leverage these insights in practice—improving existing designs without rewriting everything. The subsequent posts in this series show how.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/mahush/interfacing-pure-functions-with-our-impure-world-5e8f"&gt;Handling Side Effects in Modern C++: Interfacing Pure Functions with Our Imperative World&lt;/a&gt; — Using the functional core–imperative shell pattern to mitigate dependencies&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/mahush/when-one-shell-isnt-enough-scaling-the-functional-core-imperative-shell-pattern-with-actors-in-c-43f6"&gt;When One Shell Isn’t Enough: Scaling the Functional Core–Imperative Shell Pattern with Actors in C++&lt;/a&gt; — How to structure growing C++ systems into actor-driven core–shell pairs that isolate dependencies&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/mahush/mastering-state-in-modern-c-making-it-explicit-7o"&gt;Mastering State in Modern C++: Making It Explicit&lt;/a&gt; — Making state flow explicit in the functional core–imperative shell pattern&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/mahush/mastering-state-in-modern-c-making-it-encapsulated-43bi"&gt;Mastering State in Modern C++: Making It Encapsulated&lt;/a&gt; — Localizing module-internal state without hidden mutation or domain model pollution&lt;/li&gt;
&lt;li&gt;More posts in this series coming soon&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Part of the &lt;em&gt;funkyposts&lt;/em&gt; blog — bridging object-oriented and functional thinking in C++.&lt;br&gt;
Created with AI assistance for brainstorming and improving formulation. Original and canonical source: &lt;a href="https://github.com/mahush/funkyposts" rel="noopener noreferrer"&gt;https://github.com/mahush/funkyposts&lt;/a&gt; (v07)&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>functional</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>When One Shell Isn’t Enough: Scaling the Functional Core–Imperative Shell Pattern with Actors in C++</title>
      <dc:creator>Matti | funkyposts</dc:creator>
      <pubDate>Fri, 10 Apr 2026 08:15:26 +0000</pubDate>
      <link>https://dev.to/mahush/when-one-shell-isnt-enough-scaling-the-functional-core-imperative-shell-pattern-with-actors-in-c-43f6</link>
      <guid>https://dev.to/mahush/when-one-shell-isnt-enough-scaling-the-functional-core-imperative-shell-pattern-with-actors-in-c-43f6</guid>
      <description>&lt;p&gt;&lt;strong&gt;How to structure growing C++ systems into actor-driven core–shell pairs that isolate dependencies&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The previous &lt;a href="https://dev.to/mahush/interfacing-pure-functions-with-our-impure-world-5e8f"&gt;post&lt;/a&gt; showed how the &lt;em&gt;functional core–imperative shell&lt;/em&gt; pattern decouples the business logic from side effect handling by centralizing all side effects in the shell.&lt;/p&gt;

&lt;p&gt;But as the application grows, handling different kinds of side effects (IO, state, timers) for the entire system in a single place quickly becomes problematic: more and more unrelated concerns end up sharing the same context. As a consequence, they can accidentally influence each other, or at least make it harder to see which dependencies actually exist. The shell has to be sorted out again and again, making the code harder to reason about and maintain.&lt;/p&gt;

&lt;p&gt;This raises the crucial question: how do we scale the &lt;em&gt;functional core–imperative shell&lt;/em&gt; pattern without tying together parts that shouldn't influence each other?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Design Idea: Actors as Shell
&lt;/h2&gt;

&lt;p&gt;In order to keep things clean, we need to decouple unrelated shell concerns. Thus, a single shell isn’t enough—we need multiple ones, each handling an independent, non-overlapping subset of side effects.&lt;/p&gt;

&lt;p&gt;To fully understand what this means, let’s look at this design from the business logic point of view: effectively, the business logic is split into multiple cores, each with its own shell that handles only the side effects it requires. Thus, the system is structured into multiple core–shell pairs, each forming its own isolated unit around a specific concern. Each unit owns its behavior end-to-end—from handling input effects in its shell, through business logic in the core, to producing output effects via the shell. This is not just splitting the shell, but partitioning the system into smaller, self-contained slices that evolve independently.&lt;/p&gt;

&lt;p&gt;One concrete option to put this idea into practice is using the actor model. Actors are inherently separated from each other, so they naturally provide the needed isolation. The approach is simple: combine the actor model with the &lt;em&gt;functional core–imperative shell&lt;/em&gt; pattern and implement shells as actors. This allows multiple shells to coexist cleanly while still being able to communicate through messages. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1h54fta1ab8cyi4l2l4n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1h54fta1ab8cyi4l2l4n.png" alt=" " width="800" height="818"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  A Real-World Example
&lt;/h2&gt;

&lt;p&gt;Let’s see this design in action with an example from my &lt;a href="https://github.com/mahush/funkysnakes/tree/v0.1.0" rel="noopener noreferrer"&gt;funkysnakes&lt;/a&gt; project. I prototyped &lt;a href="https://github.com/mahush/funkyactors/tree/v0.1.0" rel="noopener noreferrer"&gt;the actor implementation there&lt;/a&gt; on top of the &lt;a href="https://github.com/chriskohlhoff/asio" rel="noopener noreferrer"&gt;Asio framework&lt;/a&gt;. It's quite lean, although it has similar semantics to ROS2 in terms of topic-based message passing. &lt;/p&gt;

&lt;p&gt;Building on this, the &lt;em&gt;funkysnakes&lt;/em&gt; game implementation is distributed across multiple actors, each following the core–shell pattern as described above.&lt;/p&gt;

&lt;p&gt;Let's focus on two actors that are connected by a topic that communicates the &lt;code&gt;DirectionMsg&lt;/code&gt;, representing a requested direction for the player's snakes.&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;enum&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Direction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;UP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DOWN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LEFT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RIGHT&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;DirectionMsg&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;PlayerId&lt;/span&gt; &lt;span class="n"&gt;player_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="n"&gt;direction&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;This message is published by the &lt;code&gt;InputActor&lt;/code&gt; that is responsible for handling user input once a player presses a direction key.&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;class&lt;/span&gt; &lt;span class="nc"&gt;InputActor&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;Actor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;InputActor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;public:&lt;/span&gt;
        &lt;span class="n"&gt;InputActor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActorContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                   &lt;span class="n"&gt;TopicPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DirectionMsg&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;direction_topic&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Actor&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="n"&gt;direction_pub_&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;create_pub&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;move&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;direction_topic&lt;/span&gt;&lt;span class="p"&gt;))}&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;processInputs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;stdin_reader_&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;tryTakeChar&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;new_state&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tryParseKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;ch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key_parser_state_&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;key_parser_state_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;new_state&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;direction_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tryConvertKeyToDirectionMsg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;direction_msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                      &lt;span class="n"&gt;direction_pub_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;publish&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;direction_msg&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;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;PublisherPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DirectionMsg&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;direction_pub_&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 &lt;code&gt;GameEngineActor&lt;/code&gt; receives the &lt;code&gt;DirectionMsg&lt;/code&gt; message and applies it to its game state &lt;code&gt;game_state_&lt;/code&gt;. Once the &lt;code&gt;game_loop_timer_&lt;/code&gt; triggers, the snakes are moved forward based on the latest direction. The processing of actor inputs like messages or timer events is coordinated in the &lt;code&gt;processInputs&lt;/code&gt; method, which is invoked whenever an input arrives.&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;class&lt;/span&gt; &lt;span class="nc"&gt;GameEngineActor&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;Actor&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GameEngineActor&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;public:&lt;/span&gt;
        &lt;span class="n"&gt;GameEngineActor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ActorContext&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                        &lt;span class="n"&gt;TopicPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DirectionMsg&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;direction_topic&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                        &lt;span class="n"&gt;TimerFactoryPtr&lt;/span&gt; &lt;span class="n"&gt;timer_factory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="n"&gt;direction_sub_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;direction_topic&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
          &lt;span class="n"&gt;game_loop_timer_&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;create_timer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;GameTimer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timer_factory&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;processInputs&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;direction_msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;direction_sub_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tryTakeMessage&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// applies direction to game_state_ by calling the related pure function of the core here&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;elapsed_event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;game_loop_timer_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tryTakeElapsedEvent&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// moves snakes forward by calling the related pure function of the core here&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;SubscriptionPtr&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DirectionMsg&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;direction_sub_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;GameLoopTimerPtr&lt;/span&gt; &lt;span class="n"&gt;game_loop_timer_&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;GameState&lt;/span&gt; &lt;span class="n"&gt;game_state_&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;In summary, the &lt;code&gt;InputActor&lt;/code&gt; asynchronously publishes player direction updates, while the &lt;code&gt;GameEngineActor&lt;/code&gt; consumes them and evolves the game state each time the game loop timer fires.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart TD

subgraph InputActor["Input Actor (Shell)"]
    InputCore["Functional Core"]
end

subgraph GameEngineActor["Game Engine Actor (Shell)"]
    GameCore["Functional Core"]
end

InputActor --&amp;gt;|DirectionMsg| GameEngineActor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why this scales
&lt;/h2&gt;

&lt;p&gt;Of course, this modular design comes with some benefits, let's take a closer look:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Separation of Concerns: Clear Responsibilities and Boundaries&lt;/strong&gt;&lt;br&gt;
The input handling and game logic are completely decoupled. Each actor focuses on a narrow, well-defined concern. The &lt;code&gt;DirectionMsg&lt;/code&gt; forms their connection point which is independent of any specific actor, so the modules don’t depend on each other directly—they only agree on a shared data contract. This keeps the boundaries clean and prevents any form of tight coupling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simplified Reasoning: Isolation&lt;/strong&gt;&lt;br&gt;
Complexity is distributed across multiple isolated shells. Each shell only handles its own side effects, so unrelated parts no longer share the same context and cannot accidentally interfere with each other. The &lt;code&gt;GameEngineActor&lt;/code&gt; cannot publish direction messages, and the &lt;code&gt;InputActor&lt;/code&gt; cannot access or modify the game state or the game loop timer. Each part is isolated and can be understood without needing to consider the behavior of the others.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fewer Bugs: Concurrency and Isolation&lt;/strong&gt;&lt;br&gt;
Processing key events and running the game loop happen asynchronously. Key events occur sporadically, while the game loop runs periodically via a timer. Each actor has its own single-threaded execution environment and processes events such as incoming messages or timer expirations sequentially. This eliminates low-level multithreading issues such as data races, because actors do not share mutable state and each actor mutates its state sequentially. Anyone who has spent days debugging low-level multithreading issues knows how valuable such a design is.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Better Maintainability: Independent Scaling&lt;/strong&gt;&lt;br&gt;
You can add another input source, such as a Bluetooth controller, without touching the &lt;code&gt;GameEngineActor&lt;/code&gt;. You can even add it without touching the existing &lt;code&gt;InputActor&lt;/code&gt;, just adding another actor that publishes a &lt;code&gt;DirectionMsg&lt;/code&gt; message. Each module evolves independently.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Applying the dependency lens from the &lt;a href="https://dev.to/mahush/bridging-object-oriented-and-functional-thinking-in-modern-c-4b47"&gt;intro post&lt;/a&gt; gives another perspective on why this design scales: it’s not just the separation itself, but how it constrains dependencies. By isolating actors from each other, all cross-actor dependencies are ruled out upfront except those explicitly expressed through message passing. Without shared state or a shared execution context, message passing is the only way for parts of the system to influence each other. As a result, the system becomes easier to reason about, test, and evolve because fewer parts are able to influence each other.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Advantage: Bridging by Design
&lt;/h2&gt;

&lt;p&gt;There is another very important benefit that goes beyond modularity: The actor-based &lt;em&gt;functional core–imperative shell&lt;/em&gt; design is able to bridge naturally object-oriented and functional code via inter-actor communication.&lt;/p&gt;

&lt;p&gt;This is powerful in practice because the actor boundary lets both styles coexist without forcing the whole system into one paradigm. It's not a compromise—it's straightforward design: you choose which actor should follow which design based on your needs and the architecture just supports that choice. &lt;/p&gt;

&lt;h2&gt;
  
  
  Where we arrived at
&lt;/h2&gt;

&lt;p&gt;The &lt;em&gt;functional core – imperative shell&lt;/em&gt; pattern scales by partitioning the system into multiple core–shell pairs. This isolates unrelated concerns like input handling and game state evolution, effectively preventing unintended dependencies between them.&lt;/p&gt;

&lt;p&gt;Sure, introducing this structure is not for free. The extra abstraction and message passing bring additional complexity that needs to be balanced against the advantages.&lt;/p&gt;

&lt;p&gt;However, when that additional structure is justified, you gain valuable isolation that simplifies reasoning and maintenance.&lt;/p&gt;

&lt;p&gt;On top of that, it enables clean bridging between traditional object-oriented design and a functional approach. In practice, I’ve found the flexibility to incrementally apply functional design where it makes sense extremely valuable—you might agree.&lt;/p&gt;

&lt;h2&gt;
  
  
  Outlook
&lt;/h2&gt;

&lt;p&gt;This and the &lt;a href="https://dev.to/mahush/interfacing-pure-functions-with-our-impure-world-5e8f"&gt;previous post&lt;/a&gt; touched on state handling, but only at a high level as part of the shell. At the same time, the core needs to read and evolve that state.&lt;/p&gt;

&lt;p&gt;Designing clean interfaces for core functions requires a deeper understanding of how state is represented and passed. In the &lt;a href="https://dev.to/mahush/mastering-state-in-modern-c-making-it-explicit-7o"&gt;next post&lt;/a&gt;, I will take a closer look at state management and how to handle it effectively.&lt;/p&gt;




&lt;p&gt;Part of the &lt;em&gt;funkyposts&lt;/em&gt; blog — bridging object-oriented and functional thinking in C++.&lt;br&gt;
Created with AI assistance for brainstorming and improving formulation. Original and canonical source: &lt;a href="https://github.com/mahush/funkyposts" rel="noopener noreferrer"&gt;https://github.com/mahush/funkyposts&lt;/a&gt; (v03)&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>functional</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Handling Side Effects in Modern C++: Designing Systems Around Pure Functions</title>
      <dc:creator>Matti | funkyposts</dc:creator>
      <pubDate>Thu, 26 Mar 2026 21:17:57 +0000</pubDate>
      <link>https://dev.to/mahush/interfacing-pure-functions-with-our-impure-world-5e8f</link>
      <guid>https://dev.to/mahush/interfacing-pure-functions-with-our-impure-world-5e8f</guid>
      <description>&lt;p&gt;&lt;em&gt;Using the functional core–imperative shell pattern to mitigate dependencies&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the previous &lt;a href="https://dev.to/mahush/bridging-object-oriented-and-functional-thinking-in-modern-c-4b47"&gt;post&lt;/a&gt;, we saw why structuring business logic without side effects—using pure functions—effectively mitigates dependencies and makes code easier to reason about and test.&lt;/p&gt;

&lt;p&gt;But applying this idea in a real system isn't straightforward: Although you want your functions without side effects, the application you are building still must perform IO and maintain state to be useful.&lt;/p&gt;

&lt;p&gt;This leads to a practical design question: how do we structure systems so that side effects are decoupled from the business logic?&lt;/p&gt;

&lt;h2&gt;
  
  
  Decoupling Logic and Side Effects
&lt;/h2&gt;

&lt;p&gt;The key idea is clear separation at the system level. So, instead of mixing business logic and side effects in the same place, one part of the system focuses on logic, while another is dedicated to side effect handling.&lt;/p&gt;

&lt;p&gt;In this setup the logic still determines which effects should happen, but without performing them directly. Instead, it describes what needs to happen, and the surrounding code interprets and performs the effects.&lt;/p&gt;

&lt;p&gt;If this reminds you of the command pattern, that’s not a coincidence—but here the idea is used to organize the entire system. And this is exactly the separation that structuring logic as pure functions naturally leads to.&lt;/p&gt;

&lt;p&gt;For example, a pure function decides to create a log message, so it returns a string and the caller interacts with the outside world by printing it to stderr.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Functional Core–Imperative Shell Pattern
&lt;/h2&gt;

&lt;p&gt;This simple structure is called &lt;em&gt;functional core–imperative shell&lt;/em&gt;: it splits an application into a functional core and an imperative shell.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3uo581w1r5xh3sn860k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff3uo581w1r5xh3sn860k.png" alt=" " width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;functional core&lt;/strong&gt;, is where the business logic is encoded in pure functions. You can wire these functions freely. This lets you build various layers of abstraction and organize the business logic cleanly.&lt;/p&gt;

&lt;p&gt;The remaining part forms the &lt;strong&gt;imperative shell&lt;/strong&gt; that calls the core and performs effects. For a C++ developer this is familiar terrain: using the standard library to write to stderr, mutating private members, using protocol stacks to communicate with other systems, or managing timers. In addition, the shell is responsible for managing the application’s execution environment, such as setting up concurrency. There is only one constraint: don’t implement business logic here.&lt;/p&gt;

&lt;p&gt;One important design aspect is the direction of dependencies: the shell depends on the core, but the core is independent of the shell. This enforces the clean separation as it prevents effects like state, IO, or other external concerns from leaking into the business logic and keeps the core self-contained.&lt;/p&gt;

&lt;h2&gt;
  
  
  Applying the Pattern in C++
&lt;/h2&gt;

&lt;p&gt;Let's look at a simplified C++ example from a snake game to see core and shell clearly. Consider direction change validation — the rule that prevents instantly reversing direction.&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="c1"&gt;// functional core&lt;/span&gt;
&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Direction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Up&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Down&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Right&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SoundEffect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;InvalidInput&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;EvaluationResult&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;direction_changed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;SoundEffect&lt;/span&gt; &lt;span class="n"&gt;sound_effect&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="nf"&gt;opposite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Up&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;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Down&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Down&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;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Up&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Left&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;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Right&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;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="p"&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;d&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;constexpr&lt;/span&gt; &lt;span class="n"&gt;EvaluationResult&lt;/span&gt; &lt;span class="nf"&gt;evaluateDirectionChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="n"&gt;requested&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requested&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;opposite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&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;SoundEffect&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;InvalidInput&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SoundEffect&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;None&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;These functions and their input and output types form the &lt;em&gt;functional core&lt;/em&gt;. Here the business logic about the game's behavior on direction changes is encoded.&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="c1"&gt;// imperative shell&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="n"&gt;snake_direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Direction&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Direction&lt;/span&gt; &lt;span class="n"&gt;requested_direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;readUserInput&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;  &lt;span class="c1"&gt;// effectful&lt;/span&gt;

        &lt;span class="k"&gt;auto&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;evaluateDirectionChange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;snake_direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requested_direction&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// calling core&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sound_effect&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;SoundEffect&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;playSound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sound_effect&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// effectful&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;direction_changed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;snake_direction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requested_direction&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// effectful&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 &lt;code&gt;main&lt;/code&gt; function is the &lt;em&gt;imperative shell&lt;/em&gt;—it calls the core, interprets the result, and performs side effects like reading user input, playing sound, or mutating the game state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recap - What has changed
&lt;/h2&gt;

&lt;p&gt;The separation into core and shell shifts how business logic is structured and how it interacts with the rest of the system. But let's state it more concretely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the business logic no longer performs effects directly but only decides what should happen&lt;/li&gt;
&lt;li&gt;side effect handling (IO, state mutation) is no longer distributed across the business logic but centralized outside of it&lt;/li&gt;
&lt;li&gt;state is no more hidden and implicitly mutated but visible at the top level and explicitly passed into the core and returned&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, these changes introduce an explicit interface between the pure business logic and the effectful parts of the system at the cost of introducing additional types to describe those effects.&lt;/p&gt;

&lt;p&gt;Looking at it through the lens of dependencies from the previous post, the situation improves as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the business logic has no dependencies on external systems&lt;/li&gt;
&lt;li&gt;fewer places in the system depend on IO or state mutation, and these dependencies are localized instead of spread across the system&lt;/li&gt;
&lt;li&gt;state dependencies are explicit: the business logic depends only on input data passed in and returned, not on internal mutable state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a consequence, reasoning about the business logic becomes significantly easier, and testing follows naturally: you call the function with input values and verify the result—without heavy mocking, dependency injection, or complex setup. This is exactly the improvement we were aiming for in the previous post.&lt;/p&gt;

&lt;p&gt;In addition, effect handling becomes easier to understand and control, as it is no longer spread across the logic but centralized in one place.&lt;/p&gt;

&lt;p&gt;So, the main achievement of &lt;em&gt;functional core–imperative shell&lt;/em&gt; is removing hidden dependencies from the business logic and localizing the remaining ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting Small Works Well
&lt;/h2&gt;

&lt;p&gt;Although this is a fundamental shift in how to deal with side effects, it doesn’t require an all-or-nothing redesign. The pattern works for a subset of your business logic, so you can apply it locally, one part of the system at a time: start by moving effectful behavior out of a single piece of business logic while leaving the surrounding structure in place. From there, you can incrementally restructure more of the system step by step.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Leads Next
&lt;/h2&gt;

&lt;p&gt;All the side effect handling is now centralized in the shell. As the application grows this accumulates complexity there. Even in a simple snake game, this escalates quickly: handling user input, playing sound, managing the game state, and performing screen IO.&lt;/p&gt;

&lt;p&gt;In my &lt;a href="https://dev.to/mahush/when-one-shell-isnt-enough-scaling-the-functional-core-imperative-shell-pattern-with-actors-in-c-43f6"&gt;next post&lt;/a&gt;, I will show how to refine this design further to better organize the application to address scaling.&lt;/p&gt;




&lt;p&gt;Part of the &lt;em&gt;funkyposts&lt;/em&gt; blog — bridging object-oriented and functional thinking in C++.&lt;br&gt;
Created with AI assistance for brainstorming and improving formulation. Original and canonical source: &lt;a href="https://github.com/mahush/funkyposts" rel="noopener noreferrer"&gt;https://github.com/mahush/funkyposts&lt;/a&gt; (v04)&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>functional</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Bridging Object-Oriented and Functional Thinking in Modern C++</title>
      <dc:creator>Matti | funkyposts</dc:creator>
      <pubDate>Tue, 24 Mar 2026 22:15:00 +0000</pubDate>
      <link>https://dev.to/mahush/why-functional-programming-got-me-4h2p</link>
      <guid>https://dev.to/mahush/why-functional-programming-got-me-4h2p</guid>
      <description>&lt;p&gt;&lt;strong&gt;Re-published with major changes&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This post has been significantly revised. Read the updated version &lt;a href="https://dev.to/mahush/bridging-object-oriented-and-functional-thinking-in-modern-c-4b47"&gt;here&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;This post previously explored combining object-oriented and functional programming in C++. However, the perspective has shifted—especially regarding how dependencies impact system design and testability.&lt;/p&gt;

&lt;p&gt;To avoid confusion, please refer to the updated version above, which reflects the current direction of this series.&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>functional</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
