👉 Right now, I’m writing The Go Optimization Guide, helping developers build faster, more efficient Go applications.

🗂️ Looking for older content? My previous blog (from 2010 to 2022, in Russian) is still available here.

Multilingual benchmarking project => Bazel for advanced engineering

caption

In her one year, Molly saw many more exciting places than I did until I was about 28. She does pretty well :-D

When working on performance experiments across C++ and Go, you obviously need a multilingual project structure. There were two paths forward: create separate build systems under a shared repository, or consolidate everything under a single, coherent framework. Bazel made that decision easy.

Using Bazel to unify builds isn’t just convenient—it should be the default choice for any serious engineering effort that involves multiple languages. It eliminates the friction of managing isolated tools, brings deterministic builds, and handles dependencies, benchmarking, and cross-language coordination with minimal ceremony.

Here’s why Bazel makes sense for performance-critical, multilingual projects like this one—no fragile tooling, no redundant setups, just clean integration that scales.

Functional Programming style in C++

caption

Teskey-Torpok Pass is a lovely pass leading you to Song-Kol Lake, Naryn region.

I’ve been on a bit of a Leetcode streak lately, poking at problems from companies I secretly admire. To keep things interesting (and to avoid nodding off in front of the screen), I challenged myself to solve the same task three ways: good old C++, its shiny modern C++20 cousin, and Elixir. It turns out that staring at a problem through a functional programming lens is like putting on X-ray specs—you see the same lines of code, but suddenly, there’s a weird beauty in that filter chain.<

My first pass was as traditional as it gets—a simple C++ class with an array and a loop. No magic here, just pushing timestamps and scanning them one by one. The implementation is pretty naive, but considering the constraints provided by the Design a hit counter challenge, even an unscalable approach is acceptable. So, we will push all new timestamps into the std::queue std::vector and then simply count.

Fixing C++ lacks switches on strings

caption

Birch Grove is just 40 minutes from Bishkek. Although I was concerned about the strong, foggy weather, it was an amazing opportunity for photography!

C++ is a powerful language, and I genuinely love it, but sometimes, even in modern versions, it lacks some surprisingly simple features. One such missing feature is switch on std::string. You’d think that by now, we could use a switch statement on strings just like we do with integers or enums—after all, Go has it! But no, C++ keeps us on our toes.

Why Doesn’t C++ Support switch on strings? Because "you only pay for what you use," which is the standard C++ mantra. The switch statement in C++ relies on integral types. Under the hood, it works by converting the case values into jump table indices for efficient execution. But std::string (or even std::string_view) is not an integral type—it’s a more complex data structure. That’s why you can’t simply do:

switch (msg->get_value<std::string>()) { // Nope, not possible :-(
    case "topology":
        // Handle network topology
        break;
    case "broadcast":
        // Handle network broadcast
        break;
}

AI, Software Engineering, and the Evolution of Code Generation

caption

From a weekend trip to a nearby gorge. Why is it here? Because I love the mountains of Kyrgyzstan!

Mark Zuckerberg recently made a bold statement: AI will soon take over the work of mid-level engineers (Forbes). While this may sound like another tech CEO hyping AI, my latest experience with OpenAI’s o3-mini-high model suggests he might not be too far off.

Thanks to DeepSeek, OpenAI was compelled to make o3-mini-high available in a regular ChatGPT subscription instead of locking it behind a steep $200 paywall. I would never pay original $200 for a model, but since I already have the regular ChatGPT subscription, it was an obvious choice to try it out. With this in mind, I decided to experiment: Could o3-mini-high generate a functional Go codebase for my GFSM library?

The experiment

For context, GFSM is my Go Finite State Machine library, and I needed a new generator to extract and save state machines in formats like PlantUML and Mermaid. Writing such a generator requires a solid understanding of Go’s Abstract Syntax Tree (AST) package, something I hadn’t used in years.

How to compile C++ in 2025. Bazel or CMake?

caption

Today, we’re examining two modern build systems for C++: CMake, the industry favorite, and Bazel, a powerful alternative. While CMake is often the default choice, I believe that approach warrants a bit more scrutiny—after all, we’re focusing on modern tools here (yep, not counting Make, right?). To explore this, I’ve created a practical demo project showcasing how both systems manage a real-world scenario.

Using the maelstrom-challenges project as a starting point, I’ve extracted a C++ library called maelstrom-node. This library has been set up to work seamlessly with both Bazel and CMake, giving us a hands-on comparison of their approaches, strengths, and quirks.

The Project Structure

Here’s what the final directory layout for maelstrom-node looks like:

Managing Multi-Language Projects with Bazel

caption

In today’s software development landscape, it’s rare to encounter a project built with just one programming language or platform. Modern applications often require integrating multiple technologies to meet diverse requirements. This complexity is both a challenge and an opportunity, demanding robust tools to manage dependencies, builds, and integrations seamlessly. Bazel, a powerful build system, is one such tool that has proven invaluable for multi-language projects.

Recently, I decided to extend my Maelstrom challenges with a C++-based test to explore how Bazel can simplify managing multi-language dependencies and streamline development workflows.

Why Bazel for Multi-Language Projects?

Bazel’s design philosophy emphasizes performance and scalability, making it an excellent choice for projects that involve multiple languages. With its support for Bazel modules, adding dependencies is as simple as declaring them in a MODULE.bazel file. For example, integrating the popular logging library spdlog is straightforward:

Bazel and Rust: A Perfect Match for Scalable Development

caption

Bazel never fails to impress, and its support for Rust demonstrates its versatility and commitment to modern development. Two distinct dependency management modes—Cargo—based and pure Bazel—allow developers to tailor workflows to their projects' needs. This adaptability is particularly valuable for integrating Rust applications into monorepos or scaling complex systems. I decided to explore how Bazel supports Rust, including managing dependencies, migrating from Cargo.toml to BUILD.bazel, and streamlining integration testing.

Harnessing Cargo-Based Dependency Management

Bazel’s ability to integrate with Cargo, Rust’s native package manager, is a standout feature. This approach preserves compatibility with the Rust ecosystem while allowing projects to benefit from Bazel’s powerful build features. By using rules_rust, a Bazel module can seamlessly import dependencies defined in Cargo.toml and Cargo.lock into its build graph.

Returning to Rust: A Journey Through Tooling, Performance

When I started tackling the Maelstrom challenges, my initial thought was to use C++. It’s a language I know inside out, and its performance is hard to beat. However, as I contemplated setting up the project, I realized I couldn’t justify fighting with the C++ pipeline for free. Crafting a proper CMake or Bazel configuration might be worthwhile for large-scale projects or when compensated, but for personal experiments? It’s an unnecessary headache.

Why Go is My Default Choice

For most non-performance critical scenarios, Go is my default, no-brainer choice. It has a clean build system, excellent tooling, and a developer experience that doesn’t make me dread the setup process. Go’s simplicity allows me (and any level team) to focus on solving the problem rather than wrestling with the environment. Yet, this time, I decided to take a different path.

Minimal CI for Go library with GitHub actions

caption

Continuous Integration (CI) has become an essential part of modern software development, and for good reason. It ensures code quality, speeds up development, and catches potential issues early. However, you can get started without an elaborate CI setup. Even a minimal CI setup can significantly improve your workflow. Here's why every project should have at least minimal CI and how to implement it effectively using GitHub Actions.

What Constitutes Minimal CI?

For a project to benefit from CI without excessive complexity, it should include the following essential components:

  1. Project Compilation: Verify that the codebase is always in a buildable state.
  2. Unit Test Execution: Ensure the core functionality works as expected.
  3. Static Code Analysis: Catch bugs and enforce coding standards before they become an issue.

GFSM: A Simple and Fast Finite State Machine for Go

caption

Design patterns are not widely used in Go, as they can often lead to unnecessary complexity in the codebase. However, the Finite State Machine (FSM) is an exception that proves to be incredibly useful. When I set out to design GFSM, I aimed to create a fast and straightforward FSM implementation for Go.

I initially sought a quick and straightforward FSM solution for Go, but I couldn't find anything that met my needs. Drawing inspiration from the speed-focused and minimalistic principles of C++ while remaining true to Go's idioms, I developed GFSM to fill this gap. The outcome is GFSM—a library that distinguishes itself from alternatives like looplab/fsm by prioritizing speed and simplicity.

Whether orchestrating microservices, handling distributed systems, or designing embedded systems, GFSM brings the reliability and efficiency needed to keep things running smoothly.