The topic of Mastering State in Modern C++: Making It Explicit is currently the subject of lively debate — readers and analysts are keeping a close eye on developments.
This is taking place in a dynamic environment: companies’ decisions and competitors’ reactions can quickly change the picture.
In an earlier post, we saw that the functional core – imperative shell pattern reduces complexity by centralizing state mutation in the shell, turning hidden state dependencies into explicit ones.
The underlying idea is simple: keep state out of the business logic. But putting this into practice is not: how can the business logic still evolve state that lives elsewhere—and why does this make dependencies explicit?
Without a clear model at the code level, state easily becomes an implicit dependency again—undermining the whole design.
The functional core – imperative shell pattern separates business logic from side effects. State handling is just another side effect. For state, this means that although the business logic in the core drives changes, it does not persist or mutate state.
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:
This way, the business logic still determines how state evolves—but without persisting or mutating it.
Here is a concrete example from my funkysnakes github project that illustrates the pattern clearly. The project implements the actor-based functional core–imperative shell architecture introduced in an earlier post.
In this example, the snakes are part of the game state, and moving them means evolving that state.
The GameEngineActor, as shell, holds the GameState struct that aggregates the relevant sub-states:

The core provides the pure function moveSnakes depending on the sub-states snakes, board, and food_items. It advances the snakes by one step, returning updated snakes while board and food_items are only read:
Finally, moveSnakes is called by the shell within the game loop of the GameEngineActor:
A key detail is how state is perceived differently. In the shell, GameState 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.
Looking at the following benefits through the lens of dependencies reveals why they arise:
One key aspect is transparency. 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.
There’s also high flexibility in which state can be processed by which function. Here the snakes sub-state is mutated, but depends on the board and the currently existing food_items. Whatever data a pure function needs can simply be passed in by the shell. This flexibility comes from decoupling logic from stored state and connecting both only through data passed to the function, keeping dependencies simple and explicit.
And another great benefit of this design is that it improves testability significantly. As pointed out in the intro post, 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 the business 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.
In conclusion, all of these benefits stem from the same shift: state dependencies become explicit instead of hidden.
So far, we looked at state that has meaning at the domain level: snakes, food items, and the board. This kind of state belongs to the game model, so it makes sense that the game engine shell holds it explicitly and passes it into the core.
But not all state should be understood at that level. Some state only exists to support a specific module—parser state, cache state, or other implementation details. Exposing all of that at the domain level would clutter the shell with details it should not need to understand—blurring the domain model and making the system harder to reason about.
So the next question is how to reintroduce encapsulation without giving up the functional mechanics we established here.
In the upcoming post, we will look at how to handle such module-internal state while keeping state evolution explicit and dependencies under control.
This post is created with AI assistance for brainstorming and improving formulation. Original and canonical source: https://github.com/mahush/funkyposts (v01)
Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment’s permalink.
For further actions, you may consider blocking this person and/or reporting abuse
DEV Community — A space to discuss and keep up software development and manage your software career
Built on Forem — the open source software that powers DEV and other inclusive communities.
Why it matters
News like this often changes audience expectations and competitors’ plans.
When one player makes a move, others usually react — it is worth reading the event in context.
What to look out for next
The full picture will become clear in time, but the headline already shows the dynamics of the industry.
Further statements and user reactions will add to the story.
