<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged rust</title>
<link>https://coderlegion.com/tag/rust</link>
<description>Powered by Question2Answer</description>
<item>
<title>Let AI Help Diagnose Incidents - Without Giving It the Keys</title>
<link>https://coderlegion.com/32802/let-ai-help-diagnose-incidents-without-giving-it-the-keys</link>
<description>&lt;p&gt;&lt;em&gt;Practical lessons from building Esapay, a prototype exploring safer recovery for payment infrastructure.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Payment systems can fail in ways that look deceptively similar. Slow checkouts might point to an overloaded service, a growing backlog, or trouble somewhere further downstream. Responding to every spike in the same way can make an incident worse: a change that helps one bottleneck may increase pressure on another.&lt;/p&gt;
&lt;p&gt;I started building &lt;strong&gt;Esapay&lt;/strong&gt; to explore a question that matters beyond payments: how can AI help engineers make sense of an incident while keeping important system changes controlled, reviewable, and reversible?&lt;/p&gt;
&lt;p&gt;Esapay is an experimental project, not a production payment processor. Its recovery demonstrations use simulated workloads, and its results should not be read as guarantees about live payment traffic.&lt;/p&gt;
&lt;h3 id=&quot;the-principle-recommendations-and-authority-are-different-things&quot;&gt;The Principle: Recommendations and Authority Are Different Things&lt;/h3&gt;
&lt;p&gt;AI can be useful when an incident has several possible explanations, when signals are noisy, or when an engineer needs a concise summary of what may be happening. But a model's recommendation should not automatically become an infrastructure change.&lt;/p&gt;
&lt;p&gt;A safer design keeps three responsibilities conceptually separate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Interpretation:&lt;/strong&gt; Use telemetry and contextual reasoning to form a hypothesis.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decision control:&lt;/strong&gt; Check a proposed response against current conditions, operational limits, and applicable policy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execution and verification:&lt;/strong&gt; Make only an authorized change, then inspect whether the outcome matched expectations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This separation makes it possible to benefit from AI without requiring the system to trust every output it generates. It also makes the overall system easier to reason about when a model is unavailable or returns an answer that cannot be used safely.&lt;/p&gt;
&lt;h3 id=&quot;1-keep-automated-actions-deliberately-narrow&quot;&gt;1. Keep Automated Actions Deliberately Narrow&lt;/h3&gt;
&lt;p&gt;A free-form instruction is difficult to validate. A bounded operation is easier to inspect before it is allowed to run.&lt;/p&gt;
&lt;p&gt;One lesson from Esapay is to define the kinds of changes a system is permitted to consider, rather than allowing a reasoning component to invent arbitrary operational commands. The enforcement layer should independently check a proposed change and remain responsible for whether it is accepted.&lt;/p&gt;
&lt;p&gt;This does not make automation safe by itself. The permitted operations still need clear limits, appropriate authorization, and tests for invalid inputs and unexpected conditions.&lt;/p&gt;
&lt;p&gt;A useful design question is: &lt;em&gt;Could the system explain why this change was allowed, and could a separate part of the application reject it?&lt;/em&gt; If the answer is no, the authority boundary may be too vague.&lt;/p&gt;
&lt;h3 id=&quot;2-plan-for-missing-slow-or-malformed-ai-responses&quot;&gt;2. Plan for Missing, Slow, or Malformed AI Responses&lt;/h3&gt;
&lt;p&gt;A system that depends on an AI service should define what happens when that service is unavailable. Network problems, timeouts, model errors, and unexpected output are ordinary failure modes—not exceptional situations that can be ignored until deployment.&lt;/p&gt;
&lt;p&gt;For critical workflows, the fallback should be intentionally limited. Depending on the system, that might mean using established deterministic checks, asking for human review, or declining to take an automated action. The correct choice depends on the risk; continuing automatically is not always the safest option.&lt;/p&gt;
&lt;p&gt;The broader lesson is to design the failure path at the same time as the success path. If a workflow only behaves safely when the model responds perfectly, its reliability depends on an assumption that will eventually be tested.&lt;/p&gt;
&lt;h3 id=&quot;3-treat-decisions-as-snapshots-of-a-changing-world&quot;&gt;3. Treat Decisions as Snapshots of a Changing World&lt;/h3&gt;
&lt;p&gt;A recommendation can be reasonable when it is created and unsafe a moment later. Other requests may have changed the resource, its limits, or the surrounding conditions while the system was preparing a response.&lt;/p&gt;
&lt;p&gt;Before applying a change, a robust system should establish that the information behind the decision is still current. Where concurrent changes are possible, that check should be coordinated with the mutation itself; checking a version and changing state as two unrelated operations can leave a race between them.&lt;/p&gt;
&lt;p&gt;This is a general backend engineering problem, not something unique to AI. AI can make it more visible because reasoning may take long enough for the world to change before the recommendation is ready.&lt;/p&gt;
&lt;p&gt;Tests should deliberately create competing updates and verify that outdated decisions are rejected or reconsidered. Happy-path tests alone will not reveal these timing problems.&lt;/p&gt;
&lt;h3 id=&quot;4-verify-effects-instead-of-assuming-success&quot;&gt;4. Verify Effects Instead of Assuming Success&lt;/h3&gt;
&lt;p&gt;A successful function call or accepted command does not necessarily mean an incident has been resolved. A change may be applied but fail to improve the symptom, improve one metric while worsening another, or fail to reach the external system it was intended to affect.&lt;/p&gt;
&lt;p&gt;That is why recovery should include an outcome check. Before acting, define what improvement is expected. Afterward, compare the observed state with that expectation and retain enough context to understand what happened.&lt;/p&gt;
&lt;p&gt;Reversibility also deserves care. A rollback that restores an application's local view does not automatically restore an external service, cluster, or payment provider. Systems that cross those boundaries need a way to verify the external result and handle partial failure.&lt;/p&gt;
&lt;p&gt;The aim is not to claim that every action can always be undone. It is to make uncertainty explicit and avoid declaring success before there is evidence for it.&lt;/p&gt;
&lt;h3 id=&quot;5-make-decisions-explainable-after-the-incident&quot;&gt;5. Make Decisions Explainable After the Incident&lt;/h3&gt;
&lt;p&gt;When an automated response succeeds, engineers need to know what changed and why. When it fails, they need to reconstruct the path to that outcome.&lt;/p&gt;
&lt;p&gt;Useful operational records can include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The observed symptoms.&lt;/li&gt;
&lt;li&gt;The rationale for a recommendation.&lt;/li&gt;
&lt;li&gt;The decision outcome.&lt;/li&gt;
&lt;li&gt;The change that was attempted.&lt;/li&gt;
&lt;li&gt;The result that followed.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Such records should avoid storing credentials or unnecessary personal and payment data. Access controls and retention rules matter just as much as the record format.&lt;/p&gt;
&lt;p&gt;Integrity checks can help reveal whether recorded events have been altered, but they are not a substitute for durable storage, restricted access, backups, or a clear operational process. An audit trail is only as useful as its coverage and the trustworthiness of the environment that stores it.&lt;/p&gt;
&lt;p&gt;These details are easy to underestimate when building a prototype. They become central when someone must explain an incident to a teammate, an operator, or an auditor later.&lt;/p&gt;
&lt;h3 id=&quot;6-evaluate-the-trade-offs-honestly&quot;&gt;6. Evaluate the Trade-offs Honestly&lt;/h3&gt;
&lt;p&gt;AI-assisted recovery introduces costs as well as potential benefits. Additional reasoning can improve a recommendation in some cases, but it can also add latency, complexity, operational dependencies, and new failure modes. A simpler baseline may be preferable for predictable incidents.&lt;/p&gt;
&lt;p&gt;For Esapay, I use a local simulation and benchmark harness to explore these trade-offs. Such experiments are valuable for comparing approaches under controlled conditions, reproducing scenarios, and identifying regressions. They do not establish how a system will behave under every real-world workload.&lt;/p&gt;
&lt;p&gt;A useful evaluation should state what was simulated, what was measured, what baseline was used, and what remains untested. It should also consider more than one headline number:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Recovery quality.&lt;/li&gt;
&lt;li&gt;Time spent in a degraded condition.&lt;/li&gt;
&lt;li&gt;Unintended changes.&lt;/li&gt;
&lt;li&gt;Resource overhead.&lt;/li&gt;
&lt;li&gt;Behavior when the reasoning service fails.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal of a benchmark is not to make a project look fast. It is to help answer a specific engineering question and show the limits of the evidence.&lt;/p&gt;
&lt;h3 id=&quot;questions-i-ask-when-designing-ai-assisted-automation&quot;&gt;Questions I Ask When Designing AI-Assisted Automation&lt;/h3&gt;
&lt;p&gt;Before trusting an automated recovery workflow, I want clear answers to questions like these:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What is the smallest set of actions the system genuinely needs?&lt;/li&gt;
&lt;li&gt;Which independent checks can veto a recommendation?&lt;/li&gt;
&lt;li&gt;What happens if the model is unavailable, uncertain, or returns unusable output?&lt;/li&gt;
&lt;li&gt;How do we know the state has not changed since the recommendation was prepared?&lt;/li&gt;
&lt;li&gt;What evidence tells us the action helped—and what happens if it did not?&lt;/li&gt;
&lt;li&gt;Can we reconstruct the decision without exposing secrets or unnecessary user data?&lt;/li&gt;
&lt;li&gt;Which results come from controlled simulation, and which have been validated in a real environment?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These questions are useful even when the final design does not use multiple agents or a language model. They force the system's authority, failure behavior, and evidence to be discussed explicitly.&lt;/p&gt;
&lt;h3 id=&quot;closing-thought&quot;&gt;Closing Thought&lt;/h3&gt;
&lt;p&gt;The most interesting part of applying AI to infrastructure is not giving a model more power. It is deciding where reasoning adds value, where deterministic controls are necessary, and how to tell whether the final outcome was actually good.&lt;/p&gt;
&lt;p&gt;Esapay remains a prototype, and there is more work to do before its ideas could support production-critical workloads. Building it has reinforced one principle for me:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AI can help inform an operational decision, but the system should retain clear control over authorization, execution, and verification.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;For engineers exploring similar ideas, I would start with narrow permissions, explicit failure paths, deliberate concurrency tests, careful outcome measurement, and honest reporting of limitations. Those foundations matter more than making an architecture sound autonomous.&lt;/p&gt;
&lt;h3 id=&quot;explore-esapay&quot;&gt;Explore Esapay&lt;/h3&gt;
&lt;p&gt;Interested in exploring the project or discussing potential contributions? Visit the repository:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GitHub repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/sujithputta02/Esapay&quot;&gt;https://github.com/sujithputta02/Esapay&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you have ideas for improving reliable AI-assisted systems, I'd be interested in hearing your thoughts and learning from the developer community.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/32802/let-ai-help-diagnose-incidents-without-giving-it-the-keys</guid>
<pubDate>Wed, 28 Oct 2026 10:22:53 +0000</pubDate>
</item>
<item>
<title>The Future of Offline Payments: The DeFi Approach</title>
<link>https://coderlegion.com/31646/the-future-of-offline-payments-the-defi-approach</link>
<description>&lt;p&gt;Over the last two decades, digital payments have become an integral part of our everyday lives. Today, we can send money across borders, purchase goods using mobile wallets, and transfer digital assets without necessarily relying on traditional financial intermediaries. Despite these advances, one fundamental challenge remains: accessibility.&lt;/p&gt;
&lt;p&gt;For people living in rural communities, traders operating in areas with unreliable mobile networks, and businesses experiencing network congestion, completing a digital payment can become unexpectedly difficult. In these situations, the problem is not necessarily access to a bank account or a digital wallet. Rather, it is the inability to connect to the infrastructure required to authorize, verify, or complete a transaction.&lt;/p&gt;
&lt;p&gt;This challenge has not gone unnoticed. It has attracted interest across both traditional finance (TradFi) and decentralized finance (DeFi). Research into offline payment protocols and blockchain-based payment systems is exploring new ways to make digital payments more resilient when connectivity is unavailable.&lt;/p&gt;
&lt;p&gt;Within the Web3 ecosystem, developers and researchers are exploring how digital transactions can be initiated, authorized, and exchanged without requiring a constant internet connection.&lt;/p&gt;
&lt;p&gt;In this article, we'll explore emerging approaches to offline payments in DeFi. I'll also introduce PAT (Primitive Airborne Transaction), my contribution to this evolving field, which I had intended to submit to Colosseum's World Fair hackathon.&lt;/p&gt;
&lt;h3 id=&quot;innovations-and-approaches-to-offline-payments-in-web3&quot;&gt;Innovations and Approaches to Offline Payments in Web3&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Solana: Fast Payments, QR Codes, and Offline Signing&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Solana is particularly interesting in this discussion because it combines a high-performance blockchain with a payment ecosystem designed for consumer applications.&lt;/p&gt;
&lt;p&gt;From exploring its documentation, I learned that Solana supports offline and multi-party transaction signing, including the preparation of transaction messages, independent signatures, transaction validity, and eventual submission. For instance, a transaction can be prepared and signed independently of the device that eventually broadcasts it. This was one of the reasons I chose to build PAT on Solana.&lt;/p&gt;
&lt;p&gt;However, Solana Pay's standard QR-based payment flow is not itself a complete offline payment solution. QR codes solve a communication and user-interface problem. They do not eliminate the need to validate transaction state or prevent a payer from attempting to spend the same funds more than once.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Bitcoin: Offline Transaction Authorization&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In June 2026, researchers presented &lt;a rel=&quot;nofollow&quot; href=&quot;https://ieeexplore.ieee.org/document/11575450&quot;&gt;Offline Bitcoin: An Air-Gapped Protocol for Trustless Bitcoin Transactions&lt;/a&gt; at the IEEE International Conference on Blockchain and Cryptocurrency.&lt;/p&gt;
&lt;p&gt;The research describes a protocol and application for making Bitcoin payments using devices without internet access. It leverages Bitcoin's Taproot capabilities and QR codes to exchange payment information between offline and online devices without requiring the offline device to connect to the internet. The intended receiver can claim the funds using a secret, while the sender can recover them after a configurable timeout if the receiver does not act.&lt;/p&gt;
&lt;p&gt;The fundamental difficulty with offline blockchain payments is that disconnected devices cannot query the latest blockchain state. This creates challenges involving transaction validity, forgery, and double-spending.&lt;/p&gt;
&lt;p&gt;This brings us to an important question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How can a system preserve the accessibility of offline payments without compromising the integrity of digital money?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It is within this context that I began building PAT.&lt;/p&gt;
&lt;h3 id=&quot;pat-primitive-airborne-transaction-&quot;&gt;PAT (Primitive Airborne Transaction)&lt;/h3&gt;
&lt;p&gt;Although I did not get to submit PAT to Colosseum's World Fair hackathon, I built Primitive Airborne Transaction, a Solana-based project exploring how digital payment workflows can operate during periods of intermittent connectivity.&lt;/p&gt;
&lt;p&gt;The underlying idea is simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Reserve while connected. Spend while disconnected. Settle when reconnected.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/kq64vhzbhiilujfsoucg.jpg&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 1: Connect your wallet.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The sender connects a compatible Solana wallet to PAT while online.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 2: Reserve funds for offline spending.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;While connected, the sender reserves funds for a future payment. This establishes the transaction's context before the sender goes offline.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 3: Authorize the payment offline.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When the sender wants to pay, PAT generates a payment authorization tied to the intended receiver's wallet address. This authorization is represented by a cryptographic signature, which can be shared through a QR code or copied for transfer.&lt;/p&gt;
&lt;p&gt;The authorization is valid for a limited period. This helps define how long the payment request can be used.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Step 4: Submit the payment for settlement.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The merchant receives the authorization and, when connected to the internet, scans the QR code or enters the provided signature and initiates settlement on Solana.&lt;/p&gt;
&lt;p&gt;The blockchain then checks the relevant transaction conditions before the payment can be completed.&lt;/p&gt;
&lt;p&gt;This means the payment authorization can be exchanged without requiring the sender to have an active internet connection at that moment, while the merchant can submit the transaction when connectivity is available.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/vbut1wgsksnmjfrecgav.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
&lt;p&gt;PAT is an amalgamation of multiple ideas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Solana-based reservation state&lt;/li&gt;
&lt;li&gt;Cryptographic authorization&lt;/li&gt;
&lt;li&gt;Local persistence&lt;/li&gt;
&lt;li&gt;QR-based exchange&lt;/li&gt;
&lt;li&gt;Deferred settlement&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The goal is to address the gap that emerges when the normal online payment workflow is interrupted. However, the current prototype still has shortcomings, particularly in user experience and operational reliability, that need to be addressed.&lt;/p&gt;
&lt;p&gt;You can explore the application here:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;pat-sol.vercel.app&quot;&gt;PAT Web App&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Hilda-Enyioko/pat&quot;&gt;Hilda-Enyioko/pat&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;While we share ideas and contribute to financial accessibility, a key question is ask is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can the concept behind PAT be replicated in TradFi systems?&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31646/the-future-of-offline-payments-the-defi-approach</guid>
<pubDate>Sun, 18 Oct 2026 22:34:57 +0000</pubDate>
</item>
<item>
<title>Pyintel Mem</title>
<link>https://coderlegion.com/31554/pyintel-mem</link>
<description>&lt;p&gt;The Problem: The Agent Memory Bloatware Crisis&lt;/p&gt;
&lt;p&gt;If you have built an autonomous AI agent recently (using frameworks like LangChain, Mem0, or Letta), you have probably noticed a glaring absurdity:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To give an agent long-term memory across sessions, you are often forced to run three separate database engines:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;vector database&lt;/strong&gt; (Qdrant, Pinecone, Chroma) for dense semantic search.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;keyword/full-text engine&lt;/strong&gt; (Elasticsearch, BM25) to catch exact tokens, port numbers, or UUIDs.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;graph database&lt;/strong&gt; (Neo4j, FalkorDB) to track relationships and dependencies between entities.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The result? Docker container fatigue, runaway cloud bills, massive Python virtual environments, and query latencies exceeding 200 ms simply to recall what the user said 10 minutes ago.&lt;/p&gt;
&lt;p&gt;We asked a straightforward question: &lt;strong&gt;Why can't agent memory live in a single, compact binary file that boots in 1 ms, runs completely offline, and queries in under 100 microseconds?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;What SQLite did for relational data and Git did for version control, we set out to build for agent memory.&lt;/p&gt;
&lt;p&gt;Meet &lt;strong&gt;Pyintel Mem (&lt;code&gt;.pmem&lt;/code&gt;)&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-architecture-single-file-binary-wire-layout-0x50-0x4d-&quot;&gt;The Architecture: Single-File Binary Wire Layout (&lt;code&gt;0x50 0x4D&lt;/code&gt;)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;pyintel-mem&lt;/code&gt; compiles to a &lt;strong&gt;536.5 KB standalone binary with 0 external runtime crate dependencies&lt;/strong&gt; (no &lt;code&gt;tokio&lt;/code&gt;, no &lt;code&gt;serde&lt;/code&gt;, no &lt;code&gt;rocksdb&lt;/code&gt;, no &lt;code&gt;sqlite&lt;/code&gt;, no &lt;code&gt;ndarray&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;All data structures—vectors, HNSW graph layers, BM25 hash postings, and CSR temporal edges—live inside a single binary &lt;code&gt;.pmem&lt;/code&gt; file with a 64-byte wire header:&lt;/p&gt;
&lt;h4 id=&quot;zero-copy-os-memory-mapping-mmap-&quot;&gt;Zero-Copy OS Memory Mapping (&lt;code&gt;mmap&lt;/code&gt;)&lt;/h4&gt;
&lt;p&gt;When a process executes &lt;code&gt;pmem recall &quot;query&quot;&lt;/code&gt;, the file is not parsed or copied into heap memory. Instead, it utilizes native operating system memory mapping (&lt;code&gt;MapViewOfFile&lt;/code&gt; on Windows, &lt;code&gt;mmap&lt;/code&gt; on POSIX). Pages are faulted in directly by CPU cache lines on-demand.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-4-signal-hybrid-retrieval-engine&quot;&gt;The 4-Signal Hybrid Retrieval Engine&lt;/h3&gt;
&lt;p&gt;Instead of running separate queries across three distinct databases and reranking them with a slow LLM pass, &lt;code&gt;pyintel-mem&lt;/code&gt; evaluates a unified &lt;strong&gt;4-Signal Hybrid Search in a single memory-mapped pass&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;\[\text{Score} = \Big(\alpha \cdot S_{\text{vector}} + \beta \cdot S_{\text{bm25}} + \gamma \cdot S_{\text{graph}}\Big) \cdot e^{-\lambda \cdot \Delta t} \cdot \text{Importance}\]&lt;/p&gt;
&lt;h4 id=&quot;1-vector-quantization-sq8-bq1-hardware-popcnt&quot;&gt;1. Vector Quantization: SQ8 + BQ1 Hardware POPCNT&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Symmetric int8 (SQ8):&lt;/strong&gt; Compresses 32-bit floats 4x with 16-way loop-unrolled SIMD dot-product accumulation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1-bit Binary Signatures (BQ1):&lt;/strong&gt; Compresses vectors 32x into &lt;code&gt;u64&lt;/code&gt; bitmasks. A single-cycle hardware &lt;code&gt;POPCNT&lt;/code&gt; instruction calculates Hamming distance to instantly discard 90% of non-matching candidates before running dot products.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;2-zero-allocation-hash-keyed-okapi-bm25-&quot;&gt;2. Zero-Allocation Hash-Keyed Okapi BM25+&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Tokenizes text into &lt;code&gt;u64&lt;/code&gt; FNV-1a hash postings with CamelCase and digit boundary splitting (&lt;code&gt;AutoVLA&lt;/code&gt; -&amp;gt; &lt;code&gt;auto, vla&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Built-in &lt;strong&gt;Damerau-Levenshtein &amp;lt;= 1 typo recovery&lt;/strong&gt;: queries like &lt;code&gt;&quot;what is the autoVLA ADE scre?&quot;&lt;/code&gt; automatically recover &lt;code&gt;&quot;score&quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;3-csr-encoded-2-hop-temporal-knowledge-graph&quot;&gt;3. CSR-Encoded 2-Hop Temporal Knowledge Graph&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Nodes are linked by directed weighted edges: &lt;code&gt;RELATES_TO&lt;/code&gt;, &lt;code&gt;CAUSES&lt;/code&gt;, &lt;code&gt;SUPERSEDES&lt;/code&gt;, &lt;code&gt;PART_OF&lt;/code&gt;, &lt;code&gt;DEPENDS_ON&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Traversals use Compressed Sparse Row (CSR) layout with spreading activation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Superseding Logic:&lt;/strong&gt; When fact B replaces fact A, linking them with &lt;code&gt;SUPERSEDES&lt;/code&gt; suppresses the outdated fact during recall while preserving historical lineage.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;4-ebbinghaus-spaced-repetition-amp-o-1-tombstoning&quot;&gt;4. Ebbinghaus Spaced Repetition &amp;amp; O(1) Tombstoning&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Unaccessed memories decay exponentially based on elapsed time ($\Delta t$).&lt;/li&gt;
&lt;li&gt;Recalling a memory automatically reinforces its &lt;code&gt;access_count&lt;/code&gt; and refreshes its timestamp.&lt;/li&gt;
&lt;li&gt;Critical rules can be &lt;code&gt;--pin&lt;/code&gt;ned to never decay.&lt;/li&gt;
&lt;li&gt;Outdated facts can be soft-deleted in O(1) time via the &lt;code&gt;TOMBSTONE&lt;/code&gt; flag.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;solving-filtered-recall-collapse-with-the-adaptive-router&quot;&gt;Solving &quot;Filtered Recall Collapse&quot; with the Adaptive Router&lt;/h3&gt;
&lt;p&gt;A known limitation of standard HNSW vector search is that when you filter by metadata (e.g., namespace or tag where only 5% of nodes match), greedy graph traversals hit dead ends, causing recall to collapse to near 0%.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pyintel-mem&lt;/code&gt; includes an &lt;strong&gt;Adaptive Selectivity Router&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;High Selectivity (&amp;gt;15% match):&lt;/strong&gt; Routes through the logarithmic HNSW graph.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Low Selectivity (&amp;lt;15% match):&lt;/strong&gt; Dynamically bypasses the graph and runs a pre-filtered SIMD SQ8 scan over candidate bitsets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Result:&lt;/strong&gt; 100% recall retention across all filter densities in under 350 microseconds.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;empirical-benchmark&quot;&gt;Empirical Benchmark&lt;/h3&gt;
&lt;p&gt;Measured on an AMD Ryzen / NVMe setup (&lt;code&gt;x86_64-pc-windows-gnu&lt;/code&gt;, release build with LTO + strip):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Metric &lt;/th&gt;&lt;th&gt;Result&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Standalone Binary Size&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;536.5 KB&lt;/strong&gt; (0 external crates)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Indexed Nodes &amp;amp; Edges&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;2,500 nodes (128-D) + 833 edges&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;.pmem File Size&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;1,088 KB&lt;/strong&gt; (~445 bytes / node)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Single-Store Query&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;31.9 µs&lt;/strong&gt; (sub-millisecond)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Federated Global Query (3 stores)&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;84.0 µs&lt;/strong&gt; (1.00 ms total mmap)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Selective 5% Filtered Query&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;332.2 µs p50&lt;/strong&gt; (100% recall)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;quickstart&quot;&gt;Quickstart&lt;/h3&gt;
&lt;h4 id=&quot;1-install-via-cargo&quot;&gt;1. Install via Cargo&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cargo install --git https://github.com/Pyintel/pyintel-mem.git
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-shorthand-cli&quot;&gt;2. Shorthand CLI&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Store facts with namespace and tags
pmem store &quot;Apex Studio UDP listener migrated to port 4141&quot; --ns apex --tags network,udp --importance 2.0

# Sub-millisecond recall (typo tolerant)
pmem recall &quot;what is the apex udp port?&quot;

# Link causal / superseding relationships
pmem link --src 2 --dst 1 --kind SUPERSEDES --weight 1.0

# Delete / forget outdated memories by ID or query
pmem forget &quot;port 3000&quot;

# Federated cross-store search across EVERY .pmem file on disk in &amp;lt;100 µs
pmem global &quot;what is the apex udp port?&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;3-connect-as-an-mcp-server-cursor-claude-code-antigravity-&quot;&gt;3. Connect as an MCP Server (Cursor / Claude Code / Antigravity)&lt;/h4&gt;
&lt;p&gt;Add this to your MCP settings (&lt;code&gt;mcp_config.json&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;mcpServers&quot;: {
    &quot;pyintel-mem&quot;: {
      &quot;command&quot;: &quot;pmem&quot;,
      &quot;args&quot;: [&quot;mcp&quot;, &quot;workspace.pmem&quot;]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Exposes &lt;code&gt;mem_store&lt;/code&gt;, &lt;code&gt;mem_recall&lt;/code&gt;, &lt;code&gt;mem_forget&lt;/code&gt;, &lt;code&gt;mem_link&lt;/code&gt;, and &lt;code&gt;mem_stats&lt;/code&gt; over stdio JSON-RPC 2.0.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;one-click-chat-memory-harvester&quot;&gt;One-Click Chat Memory Harvester&lt;/h3&gt;
&lt;p&gt;Have an ongoing Cursor, Claude, or ChatGPT conversation that you want to preserve into &lt;code&gt;.pmem&lt;/code&gt; without dumping a giant wall of text?&lt;/p&gt;
&lt;p&gt;Grab our autonomous Memory Harvester prompt:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PowerShell:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-powershell&quot;&gt;irm https://pyintel.cc/pmem-harvest.prompt | claude -p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Bash / macOS / Linux:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;curl -fsSL https://pyintel.cc/pmem-harvest.prompt | claude -p
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It instructs the agent to deconstruct the session into &lt;strong&gt;atomic 1–2 sentence facts (20–50 words)&lt;/strong&gt;, categorize them into namespaces, pin core rules, and link causal relationships automatically.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;summary-amp-open-source&quot;&gt;Summary &amp;amp; Open Source&lt;/h3&gt;
&lt;p&gt;Pyintel Mem is completely open-source under the MIT license. We provide Authenticode-signed pre-built binaries for Windows (x86_64 &amp;amp; ARM64), macOS Universal (Apple Silicon &amp;amp; Intel), and Linux (x86_64 &amp;amp; ARM64).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Pyintel/pyintel-mem&quot;&gt;https://github.com/Pyintel/pyintel-mem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Website &amp;amp; Documentation:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://pyintel.cc/products/pyintel-mem&quot;&gt;https://pyintel.cc/products/pyintel-mem&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory Harvester Prompt:&lt;/strong&gt; [&lt;a rel=&quot;nofollow&quot; href=&quot;https://pyintel.cc/pmem-harvest.prompt&quot;&gt;https://pyintel.cc/pmem-harvest.prompt&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Launches</category>
<guid isPermaLink="true">https://coderlegion.com/31554/pyintel-mem</guid>
<pubDate>Sat, 17 Oct 2026 21:25:54 +0000</pubDate>
</item>
<item>
<title>How to Catch Rogue LLM Agents on a Local Machine? Meet TopGent</title>
<link>https://coderlegion.com/31247/how-to-catch-rogue-llm-agents-on-a-local-machine-meet-topgent</link>
<description>&lt;p&gt;As a security guy, I kept asking myself what an AI agent does on my machine apart from answering my questions.&lt;/p&gt;
&lt;p&gt;Can it read my secrets? Which IPs and countries does it talk to? What files can it reach? What processes does it start? Can I stop it if it does something I don't like?&lt;/p&gt;
&lt;p&gt;I could not find one tool that shows all of that in one place. So I decided to make my own, so meet Topgent.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/q3hpbpmquk78i1ngim35.png&quot; alt=&quot;Topgent - main view&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-is-topgent&quot;&gt;What is Topgent&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/xbcx1arm89p4hpro6kch.png&quot; alt=&quot;List of the detected agents&quot;&gt;&lt;br&gt;
The name is &lt;code&gt;top&lt;/code&gt; plus &lt;code&gt;agent&lt;/code&gt;. top shows the running processes on Linux and macOS. Topgent monitors real behaviour of the AI agents on your local machine.&lt;/p&gt;
&lt;p&gt;For each agent it shows the &lt;code&gt;files&lt;/code&gt; and &lt;code&gt;credentials&lt;/code&gt; it can reach, the &lt;code&gt;networks&lt;/code&gt; and &lt;code&gt;countries&lt;/code&gt; it connects to, the processes it starts, and the model it uses. Then it gives the agent a &lt;code&gt;risk grade&lt;/code&gt;, and you can &lt;code&gt;stop&lt;/code&gt; it.&lt;/p&gt;
&lt;p&gt;It is old school on purpose. Detection and scoring are &lt;strong&gt;deterministic&lt;/strong&gt; rules, and I chose not to have any LLMs involved. It runs locally and collects metadata only.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/s2phssr0nj2ek4fe35bm.png&quot; alt=&quot;Example of the Blast Radius&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;how-does-an-os-tell-a-process-from-an-agent-&quot;&gt;How does an OS tell a process from an agent?&lt;/h3&gt;
&lt;p&gt;It doesn't. To the OS, Claude Code is a process like any other, with a PID (Process Identifier number), an owner, a path and some network sockets. There is no &quot;agent&quot; flag for OS.&lt;/p&gt;
&lt;p&gt;So topgent looks at the executable's identity and where it was installed.&lt;/p&gt;
&lt;h3 id=&quot;three-questions&quot;&gt;Three questions&lt;/h3&gt;
&lt;p&gt;Most tools mix these up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Declared: what the agent says it can do&lt;/li&gt;
&lt;li&gt;Observed: what it has actually done&lt;/li&gt;
&lt;li&gt;Reachable: what it could do right now&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Example: your SSH key. If an agent never touched it, a tool that only watches events sees nothing and the agent looks clean. But the agent could still read that key any time it wants. Topgent shows that as reachable. It never opens the key to test it. It asks the OS whether the account is allowed to read that file.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.us-east-2.amazonaws.com/uploads/articles/xtlhjr45gtef2ynxnv5j.png&quot; alt=&quot;An agent is declared critical&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;why-rust&quot;&gt;Why Rust&lt;/h3&gt;
&lt;p&gt;Topgent reads OS data like socket tables, audit logs, config files. Memory safety matters when you use that data and parse it. The parsers have 11 fuzz targets(&lt;em&gt;OSS-Fuzz&lt;/em&gt;), and &lt;em&gt;trufflehog&lt;/em&gt;, &lt;em&gt;gitleaks&lt;/em&gt;, &lt;em&gt;osv-scanner&lt;/em&gt; and &lt;em&gt;semgrep&lt;/em&gt; run on every push to ensure we catch some obvious security issues.&lt;/p&gt;
&lt;p&gt;Topgent itself runs unprivileged, and there is an optional packet capture helper that needs a privilege, and it stays off until you turn it on.&lt;/p&gt;
&lt;h3 id=&quot;pitfalls-so-far&quot;&gt;Pitfalls so far&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. I didn't want another antivirus.&lt;/strong&gt; Topgent reports first and acts only when you ask. When you stop a process, it rechecks the PID and start time right before signalling, so it never kills a reused PID by accident.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Three operating systems have three different ways to get security events.&lt;/strong&gt; Linux has audit, macOS and Windows have their own, and none of them share access easily. Linux has been the least painful. On Windows I can't check file readability yet, so there are no reachability findings there, and a Windows score is not comparable with a Linux one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. How much detail to show.&lt;/strong&gt; The operating system reports a huge amount of detail, and most of it is noise. Nobody wants to read thousands of lines to find one problem. So Topgent shows a simple risk grade first. If you want to know why an agent got that grade, one click shows the evidence behind it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4.Short-lived processes.&lt;/strong&gt; Topgent looks at the machine in snapshots, like taking a photo. In my tests it found every process that stays running, but it could not find the quick ones. If a process starts and exits between two snapshots, it never appears in either photo. An agent can run a quick command and be gone before Topgent looks. That is why I want eBPF on Linux, which watches events as they happen instead of taking photos. I am planning to release this soon.&lt;/p&gt;
&lt;h3 id=&quot;ebpf-in-simple-words&quot;&gt;eBPF, in simple words&lt;/h3&gt;
&lt;p&gt;eBPF lets you run small programs inside the Linux kernel after a safety check. You can watch process starts and connections as they happen. I am planning to add it in the next release to catch short-lived processes and look inside containers.&lt;/p&gt;
&lt;h3 id=&quot;no-proxy&quot;&gt;No proxy&lt;/h3&gt;
&lt;p&gt;Many tools inject themself between the agent and the user. I decide to go the other way and work with the OS. Topgent looks into the interaction between the agent and the OS. As mentioned Topgent can also capture network packets (headers only).&lt;/p&gt;
&lt;h3 id=&quot;where-it-is-now&quot;&gt;Where it is now&lt;/h3&gt;
&lt;p&gt;v0.5.1 works on macOS, Linux and Windows. Nineteen agent families are defined, including Claude Code, Codex CLI, Gemini CLI, Aider, OpenHands, Goose, OpenCode, Cursor, Ollama and Cline. Some are verified per platform and the README table shows which.&lt;/p&gt;
&lt;p&gt;Releases are unsigned for now. I also need to get a macOS developer ID and a paid Apple account.&lt;/p&gt;
&lt;p&gt;The tool is Apache-2.0. I also plan an enterprise version for teams that need one view of all agents.&lt;/p&gt;
&lt;h3 id=&quot;try-it&quot;&gt;Try it&lt;/h3&gt;
&lt;p&gt;I would appreciate your honest feedback. If you want to contribute or work on this together, I am happy to collaborate. Windows and macOS event visibility is where I need the most help.&lt;/p&gt;
&lt;p&gt;How do you spot and stop a rogue AI agent on your machine?&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/farikonsec/topgent&quot;&gt;https://github.com/farikonsec/topgent&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31247/how-to-catch-rogue-llm-agents-on-a-local-machine-meet-topgent</guid>
<pubDate>Thu, 15 Oct 2026 23:26:52 +0000</pubDate>
</item>
<item>
<title>Returning to Salesforce After 8 Years: Building a Cross-Platform Task Ecosystem in Tauri v2 &amp; Rust</title>
<link>https://coderlegion.com/31010/returning-to-salesforce-after-years-building-cross-platform-task-ecosystem-in-tauri-rust</link>
<description>&lt;p&gt;Hey CoderLegion! I recently returned to the Salesforce ecosystem after an 8-year hiatus, and the shift in integration architecture is night and day. It’s no longer about writing legacy point-to-point plugins—it's about orchestrating zero-trust multi-platform workflows across Salesforce, Jira, Asana, ServiceNow, and HubSpot.&lt;/p&gt;
&lt;p&gt;To solve this without hitting our users with memory-hungry backend processes (avoiding heavy Chromium/Electron bundles that consume hundreds of megabytes of RAM), we engineered an ultra-lightweight desktop daemon using Tauri v2 and Rust.&lt;/p&gt;
&lt;p&gt;🛠️ The Architecture Stack&lt;/p&gt;
&lt;p&gt;• Web UI Center: A clean, highly visual 4-column Kanban command stream built with React 18, TypeScript, and Tailwind CSS.&lt;br&gt;
• Native Desktop Client: An operational desktop system tray daemon built using a memory-safe Tauri v2 + Rust core running smoothly with a footprint of sub-15MB RAM and &amp;lt;0.05% CPU.&lt;br&gt;
• Multi-OS compilation: Compiling native runtime binaries for Windows (MSI), macOS (DMG), and Linux (DEB) via unified DevOps pipelines.&lt;/p&gt;
&lt;p&gt;🛡️ DevSecOps &amp;amp; Privacy Isolation&lt;/p&gt;
&lt;p&gt;When deploying fleet tools inside corporate networks, strict compliance handles are non-negotiable. &lt;/p&gt;
&lt;p&gt;We integrated an absolute &quot;Privacy by Design&quot; model targeting exclusive task state lifecycles (In Progress ➔ Blocker ➔ Complete). There is zero keystroke logging and zero screen capturing, creating a pipeline that is 100% compliant with EU GDPR and German Works Council (Betriebsrat) frameworks.&lt;/p&gt;
&lt;p&gt;I wrote an architectural breakdown covering how this ties technical delivery frameworks (like Model Context Protocol / AI Agents) into front-end business databases, mapping true task completion speeds to accurate customer acquisition costs.&lt;/p&gt;
&lt;p&gt;I’d love to hear how the community is managing cross-cloud event loops and native desktop triggers in Tauri v2 or Rust right now! Let me know your thoughts on the approach.&lt;br&gt;
Read the full breakdown:&lt;br&gt;
👉 &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.seosiri.com/2026/10/multi-platform-task-automation-ecosystem-tauri-rust.html&quot;&gt;https://www.seosiri.com/2026/10/multi-platform-task-automation-ecosystem-tauri-rust.html&lt;/a&gt; &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31010/returning-to-salesforce-after-years-building-cross-platform-task-ecosystem-in-tauri-rust</guid>
<pubDate>Wed, 14 Oct 2026 04:25:10 +0000</pubDate>
</item>
<item>
<title>Building aegis-filter: Why Standard Kalman Filters Fail in Adversarial Environments</title>
<link>https://coderlegion.com/30401/building-aegis-filter-why-standard-kalman-filters-fail-in-adversarial-environments</link>
<description>&lt;p&gt;When building telemetry filters for autonomous drones or robotics, the textbook recommendation is always the same: &lt;em&gt;“Just slap a Kalman Filter on it.”&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In theory, the linear Kalman filter is mathematically optimal under Gaussian white noise. In the real world, however, physical sensors do not live in textbook conditions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Adversarial GPS/Baro Spoofing:&lt;/strong&gt; A rogue transmitter or RF jammer injects sudden, massive delta jumps (e.g., +150 meters in a single millisecond).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Numerical Covariance Degeneracy:&lt;/strong&gt; Floating-point round-off errors over millions of iterations cause the error covariance matrix $P$ to lose its positive-definiteness ($P \not\succ 0$), producing negative variances and exploding state estimates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Severe Non-Linearities:&lt;/strong&gt; Radar range calculations ($r = \sqrt{x^2 + y^2}$) and orientation quaternions break linear assumptions; Extended Kalman Filters (EKF) require calculating brittle analytical Jacobian matrices that drift rapidly.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To solve these mission-critical vulnerabilities, we built and open-sourced &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://crates.io/crates/aegis-filter&quot;&gt;&lt;code&gt;aegis-filter&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; under &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Exploidus&quot;&gt;Exploidus&lt;/a&gt;&lt;/strong&gt;—a cyber-resilient, &lt;code&gt;#![no_std]&lt;/code&gt;, hard real-time estimation engine written from scratch in pure Rust.&lt;/p&gt;
&lt;p&gt;Here is the engineering breakdown of how it works and what the simulation numbers prove.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-the-core-architecture&quot;&gt;1. The Core Architecture&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;aegis-filter&lt;/code&gt; is engineered with three core defenses:&lt;/p&gt;
&lt;h4 id=&quot;a-joseph-stabilized-covariance-update&quot;&gt;A. Joseph-Stabilized Covariance Update&lt;/h4&gt;
&lt;p&gt;Standard Kalman filters update covariance using $P_k = (I - K_k H&lt;em&gt;k) P&lt;/em&gt;{k|k-1}$. In finite-precision IEEE 754 floating-point arithmetic, subtraction can yield asymmetric, non-positive-definite matrices. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;aegis-filter&lt;/code&gt; enforces the &lt;strong&gt;Joseph Stabilized Form&lt;/strong&gt;:&lt;br&gt;
\[P_k = (I - K_k H_k) P_{k|k-1} (I - K_k H_k)^T + K_k R_k K_k^T\]&lt;/p&gt;
&lt;p&gt;Because both terms are quadratic forms ($A P A^T + B R B^T$), positive-definiteness is mathematically guaranteed across infinite operational lifetimes.&lt;/p&gt;
&lt;h4 id=&quot;b-chi-square-3-sigma-outlier-rejection-gating&quot;&gt;B. Chi-Square / 3-Sigma Outlier Rejection Gating&lt;/h4&gt;
&lt;p&gt;Before any raw measurement is allowed to alter the state vector, the innovation $\tilde{y}_k = z_k - H&lt;em&gt;k \hat{x}&lt;/em&gt;{k|k-1}$ is evaluated against the innovation covariance $S_k = H&lt;em&gt;k P&lt;/em&gt;{k|k-1} H_k^T + R_k$:&lt;br&gt;
\[\text{Reject if } |\tilde{y}_k| &amp;gt; \gamma \sqrt{S_k}\]&lt;/p&gt;
&lt;p&gt;If an adversary transmits a spoofed altitude or an IMU glitches, the reading is gated at line-rate. The filter falls back safely to its physical state propagation without corrupting the state.&lt;/p&gt;
&lt;h4 id=&quot;c-unscented-kalman-filtering-ukf-amp-h-infty-robust-minimax&quot;&gt;C. Unscented Kalman Filtering (UKF) &amp;amp; $H_\infty$ Robust Minimax&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;UKF (Merwe Scaled Sigma Points):&lt;/strong&gt; Replaces brittle Jacobian derivations by propagating $2n + 1$ deterministic sigma points through non-linear observation closures.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;$H_\infty$ Minimax:&lt;/strong&gt; Optimizes state estimation against the &lt;em&gt;worst-case&lt;/em&gt; disturbance rather than assuming well-behaved Gaussian noise.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-flight-telemetry-amp-spoofing-resilience-simulation&quot;&gt;2. Flight Telemetry &amp;amp; Spoofing Resilience Simulation&lt;/h3&gt;
&lt;p&gt;To prove real-world resilience, we simulated a quadcopter ascending at $1.5\text{ m/s}$ subjected to atmospheric turbulence, normal sensor noise, and deliberate adversarial spoofing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Step 12:&lt;/strong&gt; Instantaneous $+150\text{m}$ GPS spoofing attack.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Step 22:&lt;/strong&gt; Severe $-80\text{m}$ sensor hardware dropout glitch.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is the telemetry log produced by running &lt;code&gt;cargo run --example flight_telemetry&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;===============================================================================
  AEGIS-FILTER :: FLIGHT TELEMETRY &amp;amp; ADVERSARIAL RESILIENCE SIMULATION
  Developed under Exploidus (https://github.com/Exploidus)
===============================================================================

Step   | True Alt (m) | Sensor Raw (m) | Aegis Est (m)  | Abs Error    | Status    
-------+--------------+----------------+----------------+--------------+-----------
1      | 1.50         | 1.79           | 1.75           | 0.254        | VALID     
2      | 3.00         | 3.22           | 3.24           | 0.238        | VALID     
3      | 4.50         | 3.46           | 4.31           | 0.187        | VALID     
4      | 6.00         | 6.74           | 6.06           | 0.057        | VALID     
5      | 7.50         | 7.67           | 7.58           | 0.081        | VALID     
6      | 9.00         | 8.90           | 9.04           | 0.045        | VALID     
7      | 10.50        | 9.68           | 10.39          | 0.113        | VALID     
8      | 12.00        | 12.98          | 12.08          | 0.075        | VALID     
9      | 13.50        | 13.41          | 13.55          | 0.047        | VALID     
10     | 15.00        | 14.69          | 14.99          | 0.009        | VALID     
11     | 16.50        | 16.04          | 16.42          | 0.080        | VALID     
12     | 18.00        | 168.00         | 17.92          | 0.080        | SPOOF REJECT
13     | 19.50        | 19.08          | 19.36          | 0.138        | VALID     
14     | 21.00        | 20.65          | 20.83          | 0.172        | VALID     
15     | 22.50        | 22.43          | 22.34          | 0.156        | VALID     
16     | 24.00        | 24.93          | 24.01          | 0.012        | VALID     
17     | 25.50        | 24.79          | 25.40          | 0.099        | VALID     
18     | 27.00        | 26.78          | 26.88          | 0.116        | VALID     
19     | 28.50        | 28.76          | 28.44          | 0.060        | VALID     
20     | 30.00        | 30.64          | 30.04          | 0.044        | VALID     
21     | 31.50        | 30.61          | 31.41          | 0.095        | VALID     
22     | 33.00        | -47.00         | 32.91          | 0.095        | SPOOF REJECT
23     | 34.50        | 34.96          | 34.50          | 0.003        | VALID     
24     | 36.00        | 36.25          | 36.04          | 0.036        | VALID     
25     | 37.50        | 36.60          | 37.39          | 0.110        | VALID     
26     | 39.00        | 39.34          | 38.96          | 0.041        | VALID     
27     | 40.50        | 40.99          | 40.54          | 0.039        | VALID     
28     | 42.00        | 41.84          | 42.01          | 0.009        | VALID     
29     | 43.50        | 42.78          | 43.40          | 0.100        | VALID     
30     | 45.00        | 45.60          | 45.00          | 0.003        | VALID     

-------------------------------------------------------------------------------
  SIMULATION SUMMARY:
  - Total Steps Processed      : 30
  - Valid Readings Fused       : 28
  - Adversarial Spikes Blocked : 2 (100% Gated)
  - Final Variance Confidence  : 0.1185
===============================================================================
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;key-takeaways-from-the-data-&quot;&gt;Key Takeaways from the Data:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Millimeter Tracking Precision:&lt;/strong&gt; Under normal noisy conditions (Step 10, Step 30), estimation error dropped to &lt;strong&gt;0.003–0.009 meters (3–9 mm)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flawless Spoof Rejection:&lt;/strong&gt; When the raw altitude leaped to $168\text{m}$ at Step 12, the filter isolated the anomaly, kept the aircraft altitude at $17.92\text{m}$ (error: $0.080\text{m}$), and prevented a fatal motor cut.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-quick-start-rust-&quot;&gt;3. Quick Start (Rust)&lt;/h3&gt;
&lt;p&gt;Add &lt;code&gt;aegis-filter&lt;/code&gt; to your &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;[dependencies]
aegis-filter = &quot;0.1.0&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For bare-metal Cortex-M/RISC-V embedded targets:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;[dependencies]
aegis-filter = { version = &quot;0.1.0&quot;, default-features = false }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;1d-outlier-gated-fusion-example-&quot;&gt;1D Outlier-Gated Fusion Example:&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use aegis_filter::{ScalarKalmanFilter, FilterError};

fn main() -&amp;gt; Result&amp;lt;(), FilterError&amp;gt; {
    // Initial state: 0.0m, Initial variance: 5.0, Process noise: 0.02, Measurement noise: 0.8
    let mut filter = ScalarKalmanFilter::new(0.0, 5.0, 0.02, 0.8)?;

    let sensor_stream = [1.5, 3.1, 4.4, 168.0 /* Spoofed spike */, 6.0];

    for &amp;amp;raw_reading in &amp;amp;sensor_stream {
        filter.predict(1.5)?; // Predict forward by 1.5m climb

        match filter.update(raw_reading, Some(3.0)) { // 3-sigma gate
            Ok(estimated_alt) =&amp;gt; println!(&quot;Estimated Altitude: {:.2}m&quot;, estimated_alt),
            Err(FilterError::MeasurementGated) =&amp;gt; {
                println!(&quot;Spoofed sensor reading ({:.1}m) rejected! Holding state.&quot;, raw_reading);
            }
            Err(e) =&amp;gt; return Err(e),
        }
    }

    Ok(())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-open-source-links&quot;&gt;4. Open Source Links&lt;/h3&gt;
&lt;p&gt;The engine is open-source under dual MIT/Apache-2.0 licenses:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Crates.io:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://crates.io/crates/aegis-filter&quot;&gt;crates.io/crates/aegis-filter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Exploidus/aegis-filter&quot;&gt;Exploidus/aegis-filter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documentation:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.rs/aegis-filter&quot;&gt;docs.rs/aegis-filter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Feedback, issues, and PRs from control engineers and systems developers are warmly welcome!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/30401/building-aegis-filter-why-standard-kalman-filters-fail-in-adversarial-environments</guid>
<pubDate>Fri, 09 Oct 2026 05:45:50 +0000</pubDate>
</item>
<item>
<title>Data Types Across Programming Languages: What You Actually Need to Know</title>
<link>https://coderlegion.com/29897/data-types-across-programming-languages-what-you-actually-need-to-know</link>
<description>&lt;p&gt;Every introduction to programming eventually reaches data types.&lt;/p&gt;
&lt;p&gt;An integer is a whole number. A string contains text. A Boolean is true or false. A floating-point number represents numbers with fractional components.&lt;/p&gt;
&lt;p&gt;Then you learn another programming language and discover that apparently none of this was quite as simple as advertised.&lt;/p&gt;
&lt;p&gt;An integer in Python is not the same thing as an integer in C. JavaScript's &lt;code&gt;number&lt;/code&gt; covers values other languages separate. Rust would rather make absence explicit than let &lt;code&gt;null&lt;/code&gt; wander through a program. R has several different ways for something to be missing.&lt;/p&gt;
&lt;p&gt;The definitions are not wrong. They are incomplete. A data type does more than tell a computer how to store a value. It expresses what a language assumes you should be able to represent, which distinctions matter, and which mistakes should be caught before the program runs.&lt;/p&gt;
&lt;p&gt;So instead of memorizing another list of types, let's give several languages the same small collection of facts and see what happens.&lt;/p&gt;
&lt;p&gt;Meet Alex.&lt;/p&gt;
&lt;p&gt;Alex is about to have a complicated afternoon, mostly because of one missing middle name.&lt;/p&gt;
&lt;h3 id=&quot;the-deceptively-simple-data-model&quot;&gt;The deceptively simple data model&lt;/h3&gt;
&lt;p&gt;Suppose our program needs to represent a customer:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;name = &quot;Alex&quot;
age = 42
discount = 0.15
middle_name = no value
favorite_numbers = 3, 7, 11
status = ACTIVE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing exotic. Text, an integer, a decimal value, an absent value, a collection, and a status chosen from a known set.&lt;/p&gt;
&lt;p&gt;If data types were merely different spellings for the same concepts, moving this model between languages would be syntax conversion.&lt;/p&gt;
&lt;p&gt;It isn't. And watch which field causes the most trouble.&lt;/p&gt;
&lt;h3 id=&quot;python-let-the-values-tell-us-what-they-are&quot;&gt;Python: let the values tell us what they are&lt;/h3&gt;
&lt;p&gt;Python makes our first version pleasantly straightforward.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;name = &quot;Alex&quot;
age = 42
discount = 0.15
middle_name = None
favorite_numbers = [3, 7, 11]
status = &quot;ACTIVE&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At runtime those values have types such as &lt;code&gt;str&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt;, &lt;code&gt;NoneType&lt;/code&gt;, and &lt;code&gt;list&lt;/code&gt;. We did not declare them before assigning because Python is dynamically typed.&lt;/p&gt;
&lt;p&gt;That makes Alex easy to get into the system. It also means constraints exist only if we deliberately add them.&lt;/p&gt;
&lt;p&gt;Nothing about &lt;code&gt;status = &quot;ACTIVE&quot;&lt;/code&gt; prevents a later &lt;code&gt;status = &quot;ACTVE&quot;&lt;/code&gt;. Python does not know our domain recognizes &lt;code&gt;ACTIVE&lt;/code&gt;, &lt;code&gt;SUSPENDED&lt;/code&gt;, and &lt;code&gt;DELETED&lt;/code&gt; and has never heard of &lt;code&gt;ACTVE&lt;/code&gt;. We can model that with an &lt;code&gt;Enum&lt;/code&gt;, type hints, validation, or classes, but the bare string does not carry the rule.&lt;/p&gt;
&lt;p&gt;Python's integers hold another surprise for anyone arriving from C or Java. A normal Python &lt;code&gt;int&lt;/code&gt; is not limited to a fixed 32-bit range.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;age = 999999999999999999999999999999999999
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An absurd customer age, and a perfectly valid Python integer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What nothing looks like in Python&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Spelling &lt;/th&gt;&lt;th&gt;What it means &lt;/th&gt;&lt;th&gt;Who makes you deal with it&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;None&lt;/code&gt; &lt;/td&gt;&lt;td&gt;The only nothing available &lt;/td&gt;&lt;td&gt;Nobody&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Already, &quot;integer&quot; has stopped meaning one universal thing. And &lt;code&gt;None&lt;/code&gt; is a single, undifferentiated way of saying nothing is here, which will turn out to be a choice rather than an inevitability.&lt;/p&gt;
&lt;h3 id=&quot;javascript-one-number-and-two-different-nothings&quot;&gt;JavaScript: one &lt;code&gt;number&lt;/code&gt;, and two different nothings&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;const name = &quot;Alex&quot;;
const age = 42;
const discount = 0.15;
const middleName = null;
const favoriteNumbers = [3, 7, 11];
const status = &quot;ACTIVE&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similar enough, until we ask JavaScript about the values.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;typeof age;         // &quot;number&quot;
typeof discount;    // &quot;number&quot;
typeof middleName;  // &quot;object&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yes, &lt;code&gt;typeof null&lt;/code&gt; returns &lt;code&gt;&quot;object&quot;&lt;/code&gt;, a famous historical artifact that cannot be fixed without breaking existing code.&lt;/p&gt;
&lt;p&gt;More important for our comparison, &lt;code&gt;42&lt;/code&gt; and &lt;code&gt;0.15&lt;/code&gt; share the ordinary type &lt;code&gt;number&lt;/code&gt;, based on IEEE 754 double-precision floating point. JavaScript also has &lt;code&gt;BigInt&lt;/code&gt; for integers beyond the safe range of &lt;code&gt;number&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Absence is where it gets interesting. An uninitialized variable is &lt;code&gt;undefined&lt;/code&gt;. A variable deliberately set to nothing is &lt;code&gt;null&lt;/code&gt;. The language provides two spellings for emptiness and then declines to tell you which one means what. In practice one tends to mean &quot;nobody has assigned this yet&quot; and the other &quot;somebody decided this is empty,&quot; except when a codebase uses them the other way around, or interchangeably.&lt;/p&gt;
&lt;p&gt;Loose equality shows what the language really thinks. Zero, the empty string, &lt;code&gt;false&lt;/code&gt;, and an empty array all agree they are the same thing. The two values that actually mean nothing is here do not join in:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-javascript&quot;&gt;0 == &quot;&quot;;             // true
&quot;&quot; == false;         // true

null == undefined;   // true
null === undefined;  // false
null == 0;           // false
null == &quot;&quot;;          // false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;null&lt;/code&gt; and &lt;code&gt;undefined&lt;/code&gt; are loosely equal to each other and to nothing else in the language. They form their own small island.&lt;/p&gt;
&lt;p&gt;So JavaScript does distinguish absence from emptiness, and the rule is arguably the right one: a missing thing is not a zero thing. The problem is where that rule lives. It sits inside an equality operator famous for being unpredictable, next to a &lt;code&gt;typeof&lt;/code&gt; that reports &lt;code&gt;null&lt;/code&gt; as &lt;code&gt;&quot;object&quot;&lt;/code&gt;. Alex Dorey's &lt;a rel=&quot;nofollow&quot; href=&quot;https://dorey.github.io/JavaScript-Equality-Table/&quot;&gt;JavaScript Equality Table&lt;/a&gt; renders every comparison as a grid, if you want the full picture and a certain amount of despair.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What nothing looks like in JavaScript&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Spelling &lt;/th&gt;&lt;th&gt;What it means &lt;/th&gt;&lt;th&gt;Who makes you deal with it&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;undefined&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Never assigned &lt;/td&gt;&lt;td&gt;Nobody&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;null&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Deliberately empty, by convention &lt;/td&gt;&lt;td&gt;Nobody&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A type system is already more than a vocabulary list.&lt;/p&gt;
&lt;h3 id=&quot;c-representation-becomes-difficult-to-ignore&quot;&gt;C: representation becomes difficult to ignore&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;stdint.h&amp;gt;

typedef enum {
    ACTIVE,
    SUSPENDED,
    DELETED
} Status;

typedef struct {
    char *name;
    int32_t age;
    double discount;
    char *middle_name;
    int favorite_numbers[3];
    Status status;
} Customer;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Representation is visible almost everywhere. We chose an explicitly 32-bit signed integer for age, &lt;code&gt;double&lt;/code&gt; for the discount, an array of exactly three integers, pointers to characters for names, and an enumeration for status.&lt;/p&gt;
&lt;p&gt;A null pointer might represent an absent middle name:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;customer.middle_name = NULL;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But C does not attach our intended meaning to that pointer. &lt;code&gt;NULL&lt;/code&gt; here could mean Alex has no middle name, that we have not loaded it yet, that the allocation failed, or that somebody forgot to initialize the struct. The program establishes the convention and the program is responsible for respecting it. The compiler will not help.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What nothing looks like in C&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Spelling &lt;/th&gt;&lt;th&gt;What it means &lt;/th&gt;&lt;th&gt;Who makes you deal with it&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;NULL&lt;/code&gt; pointer &lt;/td&gt;&lt;td&gt;Whatever this program decided it means &lt;/td&gt;&lt;td&gt;Convention, and only convention&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;C also forces us to care directly about ranges and representation. A fixed-width signed integer cannot grow forever. This is not C being gratuitously difficult. C was designed around a different relationship between programmer and machine, and its type system reflects that.&lt;/p&gt;
&lt;h3 id=&quot;java-primitives-references-and-the-null-question&quot;&gt;Java: primitives, references, and the null question&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;enum Status {
    ACTIVE,
    SUSPENDED,
    DELETED
}

class Customer {
    String name;
    int age;
    double discount;
    String middleName;
    int[] favoriteNumbers;
    Status status;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Java distinguishes primitive types such as &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;double&lt;/code&gt;, and &lt;code&gt;boolean&lt;/code&gt; from reference types such as &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Customer&lt;/code&gt;, and arrays.&lt;/p&gt;
&lt;p&gt;An &lt;code&gt;int&lt;/code&gt; cannot be &lt;code&gt;null&lt;/code&gt;. A &lt;code&gt;String&lt;/code&gt; can.&lt;/p&gt;
&lt;p&gt;That single asymmetry does a lot of work. It means absence is available for some of Alex's fields and structurally impossible for others, and the choice was made by whoever picked the field types rather than by anyone thinking about the domain. Unless we add conventions or tooling, &lt;code&gt;String middleName&lt;/code&gt; does not tell us whether null is an expected state or a bug waiting for the right execution path.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What nothing looks like in Java&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Spelling &lt;/th&gt;&lt;th&gt;What it means &lt;/th&gt;&lt;th&gt;Who makes you deal with it&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;null&lt;/code&gt; &lt;/td&gt;&lt;td&gt;An empty reference &lt;/td&gt;&lt;td&gt;Nobody&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;(not available) &lt;/td&gt;&lt;td&gt;Primitives cannot be absent at all &lt;/td&gt;&lt;td&gt;The compiler, by refusing&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Java also has wrapper classes such as &lt;code&gt;Integer&lt;/code&gt;, which means two things that sound like &quot;an integer&quot; behave differently:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;int age;
Integer optionalAge;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One of those can be absent. The other cannot. Learning the keyword is only the beginning; the useful question is what guarantees come with it.&lt;/p&gt;
&lt;h3 id=&quot;rust-make-some-invalid-states-harder-to-represent&quot;&gt;Rust: make some invalid states harder to represent&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;enum Status {
    Active,
    Suspended,
    Deleted,
}

struct Customer {
    name: String,
    age: u32,
    discount: f64,
    middle_name: Option&amp;lt;String&amp;gt;,
    favorite_numbers: Vec&amp;lt;i32&amp;gt;,
    status: Status,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look at &lt;code&gt;middle_name: Option&amp;lt;String&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Instead of allowing a &lt;code&gt;String&lt;/code&gt; to secretly contain null, Rust represents the possibility of absence in the type itself. The value is either &lt;code&gt;Some(...)&lt;/code&gt; or &lt;code&gt;None&lt;/code&gt;, and code consuming it has to deal with that possibility before it can get at the string.&lt;/p&gt;
&lt;p&gt;This is the same information every other language has been carrying around implicitly, finally written down somewhere the compiler can read it. The other languages let you forget. Rust makes forgetting a compile error.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Status&lt;/code&gt; enum similarly makes the valid alternatives explicit. We cannot accidentally assign &lt;code&gt;ACTVE&lt;/code&gt;, because no such variant exists.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What nothing looks like in Rust&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Spelling &lt;/th&gt;&lt;th&gt;What it means &lt;/th&gt;&lt;th&gt;Who makes you deal with it&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;None&lt;/code&gt;, inside &lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;This value may legitimately be absent &lt;/td&gt;&lt;td&gt;The compiler&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A type system can encode valid states, not merely storage categories. That does not make incorrect programs impossible. It moves some classes of error from runtime behaviour into representations the compiler can reject.&lt;/p&gt;
&lt;p&gt;The type system is participating in the design.&lt;/p&gt;
&lt;h3 id=&quot;go-useful-zero-values-and-deliberate-simplicity&quot;&gt;Go: useful zero values and deliberate simplicity&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;type Status string

const (
    Active    Status = &quot;ACTIVE&quot;
    Suspended Status = &quot;SUSPENDED&quot;
    Deleted   Status = &quot;DELETED&quot;
)

type Customer struct {
    Name            string
    Age             int
    Discount        float64
    MiddleName      *string
    FavoriteNumbers []int
    Status          Status
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One important Go idea appears before we populate Alex at all: types have zero values. The zero value of an &lt;code&gt;int&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt;, of a &lt;code&gt;bool&lt;/code&gt; is &lt;code&gt;false&lt;/code&gt;, of a &lt;code&gt;string&lt;/code&gt; is &lt;code&gt;&quot;&quot;&lt;/code&gt;. Pointers, slices, maps, functions, channels, and interfaces can be &lt;code&gt;nil&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;That creates a question. If &lt;code&gt;Age&lt;/code&gt; contains &lt;code&gt;0&lt;/code&gt;, is Alex zero years old, or did nobody supply an age?&lt;/p&gt;
&lt;p&gt;The type cannot answer. If the application uses a zero value to stand for “not supplied,” that state becomes indistinguishable from a legitimately supplied zero. If the distinction matters, the model has to represent it deliberately, which is why MiddleName above is a *string rather than a string.&lt;br&gt;
&lt;strong&gt;What nothing looks like in Go&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Spelling     &lt;/th&gt;&lt;th&gt;What it means &lt;/th&gt;&lt;th&gt;Who makes you deal with it&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;0&lt;/code&gt;, &lt;code&gt;&quot;&quot;&lt;/code&gt;, &lt;code&gt;false&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Zero/default value; cannot by itself tell us whether a domain value was supplied &lt;/td&gt;&lt;td&gt;The model must distinguish this if it matters&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;nil&lt;/code&gt;        &lt;/td&gt;&lt;td&gt;Empty pointer, slice, map, channel, or interface &lt;/td&gt;&lt;td&gt;Nobody&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Convenient defaults acquire meaning only when they enter a domain.&lt;/p&gt;
&lt;h3 id=&quot;r-missing-is-not-one-simple-state&quot;&gt;R: &quot;missing&quot; is not one simple state&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-r&quot;&gt;name &amp;lt;- &quot;Alex&quot;
age &amp;lt;- 42L
discount &amp;lt;- 0.15
middle_name &amp;lt;- NA_character_
favorite_numbers &amp;lt;- c(3L, 7L, 11L)
status &amp;lt;- &quot;ACTIVE&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;R was designed around statistical computing, and its types and missing-value semantics reflect that.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;NA&lt;/code&gt; represents a missing value, with typed forms such as &lt;code&gt;NA_integer_&lt;/code&gt; and &lt;code&gt;NA_character_&lt;/code&gt;. R also has &lt;code&gt;NULL&lt;/code&gt;, which is not another spelling of &lt;code&gt;NA&lt;/code&gt;, and numeric computation can produce &lt;code&gt;NaN&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;These answer different questions. A missing observation in a dataset is not the same thing as the absence of an object. Neither is the same thing as a mathematically undefined result. R treats those as three distinct facts, because in statistics they are three distinct facts, and conflating them produces wrong answers rather than crashes.&lt;/p&gt;
&lt;p&gt;Consider:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-r&quot;&gt;mean(c(10, 20, NA))
mean(c(10, 20, NA), na.rm = TRUE)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;What nothing looks like in R&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Spelling &lt;/th&gt;&lt;th&gt;What it means &lt;/th&gt;&lt;th&gt;Who makes you deal with it&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;NA&lt;/code&gt; &lt;/td&gt;&lt;td&gt;A missing observation &lt;/td&gt;&lt;td&gt;Functions ask, via &lt;code&gt;na.rm&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;NULL&lt;/code&gt; &lt;/td&gt;&lt;td&gt;No object at all &lt;/td&gt;&lt;td&gt;Nobody&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;NaN&lt;/code&gt; &lt;/td&gt;&lt;td&gt;An undefined numeric result &lt;/td&gt;&lt;td&gt;Nobody&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The second call makes an explicit methodological choice to exclude missing observations. The language made you say so.&lt;/p&gt;
&lt;h3 id=&quot;the-field-that-caused-all-the-trouble&quot;&gt;The field that caused all the trouble&lt;/h3&gt;
&lt;p&gt;Alex's missing middle name has now been through seven general-purpose languages. Stack those seven tables on top of each other and the third column tells the whole story.&lt;/p&gt;
&lt;p&gt;Python gave it &lt;code&gt;None&lt;/code&gt;, one undifferentiated nothing. JavaScript offered two and left the meaning to you. C gave a null pointer whose meaning lives entirely in a convention the compiler cannot see. Java made absence available for reference types and impossible for primitives, as a side effect of a decision about memory. Rust put it in the type signature and refused to let you ignore it. Go made it indistinguishable from a valid value unless you reach for a pointer. R split it into three concepts because its users needed three.&lt;/p&gt;
&lt;p&gt;Not one of those languages is wrong. They disagree about a prior question: how much does the programmer have to admit they do not know?&lt;/p&gt;
&lt;p&gt;Representations of absence carry semantics whether or not the programmer acknowledges them. The difference between these languages is who is required to do the acknowledging, and when.&lt;/p&gt;
&lt;p&gt;Which is why the missing middle name turned out to be more interesting than the integer.&lt;/p&gt;
&lt;h3 id=&quot;the-table-gets-weird-quickly&quot;&gt;The table gets weird quickly&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Language &lt;/th&gt;&lt;th&gt;Typing &lt;/th&gt;&lt;th&gt;Ordinary integers &lt;/th&gt;&lt;th&gt;&lt;code&gt;42&lt;/code&gt; and &lt;code&gt;0.15&lt;/code&gt; same type? &lt;/th&gt;&lt;th&gt;How absence is spelled&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Python &lt;/td&gt;&lt;td&gt;Dynamic &lt;/td&gt;&lt;td&gt;Arbitrary size &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;code&gt;None&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;JavaScript &lt;/td&gt;&lt;td&gt;Dynamic &lt;/td&gt;&lt;td&gt;Fixed (&lt;code&gt;BigInt&lt;/code&gt; for more) &lt;/td&gt;&lt;td&gt;Yes, both &lt;code&gt;number&lt;/code&gt; &lt;/td&gt;&lt;td&gt;&lt;code&gt;null&lt;/code&gt; and &lt;code&gt;undefined&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;C &lt;/td&gt;&lt;td&gt;Static &lt;/td&gt;&lt;td&gt;Fixed width, chosen &lt;/td&gt;&lt;td&gt;Usually no &lt;/td&gt;&lt;td&gt;Null pointer, by convention&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Java &lt;/td&gt;&lt;td&gt;Static &lt;/td&gt;&lt;td&gt;Fixed width &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;code&gt;null&lt;/code&gt;, references only&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Rust &lt;/td&gt;&lt;td&gt;Static &lt;/td&gt;&lt;td&gt;Fixed width &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;&lt;code&gt;Option&amp;lt;T&amp;gt;&lt;/code&gt;, in the type&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Go &lt;/td&gt;&lt;td&gt;Static &lt;/td&gt;&lt;td&gt;Fixed width &lt;/td&gt;&lt;td&gt;No &lt;/td&gt;&lt;td&gt;Zero values, &lt;code&gt;nil&lt;/code&gt; for some types&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;R &lt;/td&gt;&lt;td&gt;Dynamic &lt;/td&gt;&lt;td&gt;Fixed width &lt;/td&gt;&lt;td&gt;Usually distinct &lt;/td&gt;&lt;td&gt;&lt;code&gt;NA&lt;/code&gt;, &lt;code&gt;NULL&lt;/code&gt;, &lt;code&gt;NaN&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Closed sets of alternatives vary just as much. Rust, Java, and C have real enums, Python offers &lt;code&gt;Enum&lt;/code&gt; if you reach for it, Go builds them from a named type plus constants, JavaScript models them by hand, and R uses factors. Collections differ too: C, Java, Rust, and Go enforce an element type in the declaration, Python and JavaScript do not, and R's atomic vectors quietly coerce everything toward a common type.&lt;/p&gt;
&lt;p&gt;There is no universal translation saying:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Python int = JavaScript number = C int = Java int = Rust i32 = Go int = R integer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those types overlap. They do not carry identical guarantees, ranges, operations, failure modes, or assumptions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The same name does not imply the same contract.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;if-you-were-designing-a-language-what-would-you-choose-&quot;&gt;If you were designing a language, what would you choose?&lt;/h3&gt;
&lt;p&gt;This gets much more interesting when you reverse the problem.&lt;/p&gt;
&lt;p&gt;Suppose you are designing a language.&lt;/p&gt;
&lt;p&gt;You need an integer.&lt;/p&gt;
&lt;p&gt;Easy.&lt;/p&gt;
&lt;p&gt;Except now you have questions. Fixed widths, or one integer that grows until memory runs out? What happens on overflow? Should &lt;code&gt;3&lt;/code&gt; become &lt;code&gt;3.0&lt;/code&gt; on its own? Should &lt;code&gt;&quot;42&quot; + 1&lt;/code&gt; be legal, and if so, what should it mean?&lt;/p&gt;
&lt;p&gt;Then absence, which by now you can see is the hard one. Do you have &lt;code&gt;null&lt;/code&gt;? Can every reference be null, or do you require an explicit optional type? Do you distinguish a missing observation from a non-existent object? Whichever you choose, every program ever written in your language inherits that decision.&lt;/p&gt;
&lt;p&gt;Then collections, where you decide whether elements must share a type and whether values can be moved or consumed. Then domain state, where you decide whether a programmer can say a value is only ever &lt;code&gt;ACTIVE&lt;/code&gt;, &lt;code&gt;SUSPENDED&lt;/code&gt;, or &lt;code&gt;DELETED&lt;/code&gt;, and whether the compiler can prove every case was handled.&lt;/p&gt;
&lt;p&gt;Finally, timing. When should a bad type relationship surface: while parsing, during compilation, at runtime, or only when the offending path finally executes?&lt;/p&gt;
&lt;p&gt;There is no universally correct set of answers. There are consequences.&lt;/p&gt;
&lt;p&gt;Every choice makes some programs easier to express, some mistakes harder to make, some implementations simpler, some runtimes more complicated, and some programmers wonder why on Earth the language designer did that.&lt;/p&gt;
&lt;p&gt;Sometimes the answer is historical compatibility. Sometimes performance. Sometimes safety. Sometimes the problem domain.&lt;/p&gt;
&lt;p&gt;And sometimes the language designer has a very good explanation and would appreciate it if everyone stopped bringing up &lt;code&gt;typeof null&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;one-language-where-absence-is-not-the-question&quot;&gt;One language where absence is not the question&lt;/h3&gt;
&lt;p&gt;Everything above is a general-purpose language, and every one of them had to decide what nothing means. That is not a universal preoccupation. It is what happens when a language expects you to model a world.&lt;/p&gt;
&lt;p&gt;The e language, commonly associated with Cadence Specman, was designed for hardware verification. Verification code describes legal ranges of values, generates constrained test data, and explores systems whose state spaces are far too large to enumerate by hand. So its declarations carry constraints rather than storage categories:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;struct customer {
    age : uint (bits: 8);
    keep age in [18..120];

    status : [ACTIVE, SUSPENDED, DELETED];
    keep soft status == ACTIVE;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is no absence card for e in this article, and that is the point. In ordinary application programming we ask what type of value this is. A verification environment asks what values are legal here, and how valid combinations should be generated so we can explore what the system does.&lt;/p&gt;
&lt;p&gt;If you look only at Python, JavaScript, Java, and C, it is easy to assume programming languages are all solving approximately the same problem with different punctuation.&lt;/p&gt;
&lt;p&gt;They aren't.&lt;/p&gt;
&lt;h3 id=&quot;types-are-compressed-language-philosophy&quot;&gt;Types are compressed language philosophy&lt;/h3&gt;
&lt;p&gt;At the beginner level, &quot;a type tells us what kind of data a value contains&quot; is perfectly serviceable. Eventually that runs out of road. A type can tell us what values exist, how they are stored, which operations are permitted, which conversions happen silently, what counts as absence, whether absence must be acknowledged, whether alternatives form a closed set, and in some languages how long a value lives and who owns it. &quot;Integer, string, Boolean, float&quot; is not the definition of a type system. It is the introductory vocabulary.&lt;/p&gt;
&lt;p&gt;Which is why learning the types in a new language beats memorizing a conversion table.&lt;/p&gt;
&lt;p&gt;Python's arbitrary-precision integers are a tradeoff. So is C asking you to care about width. Java's primitive and reference split quietly decides which of your fields are allowed to be empty. Go buys simplicity with zero values and pays for it with ambiguity at the domain boundary. R distinguishes forms of missingness because missing observations are a normal feature of statistical work, so its problem domain is visible in its types. A verification language making constrained generation first-class tells you what its designers expected programmers to spend their days doing.&lt;/p&gt;
&lt;p&gt;A language's types are partly a record of what its designers thought mattered, and partly a record of what they were willing to let you avoid thinking about.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;They are compressed language philosophy.&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id=&quot;alex-survived&quot;&gt;Alex survived&lt;/h3&gt;
&lt;p&gt;After all of this, Alex is still 42, still has no recorded middle name, still likes 3, 7, and 11, and remains active. Nothing about the underlying person changed. But every language forced different decisions about how those facts became computation, and the fact that changed most was the one that was not there.&lt;/p&gt;
&lt;p&gt;Knowing that Python has &lt;code&gt;int&lt;/code&gt;, Java has &lt;code&gt;int&lt;/code&gt;, and Rust has &lt;code&gt;i32&lt;/code&gt; is useful when you are trying to get a program running. Understanding why they are not interchangeable is useful when you are trying to understand languages.&lt;/p&gt;
&lt;p&gt;Once you start asking what a language lets you represent, what it makes you acknowledge, and what it refuses to let you say, data types stop being the boring chapter before loops. They become one of the quickest ways to see what a language believes programming should be.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;What is the strangest, most useful, or most frustrating type decision you've encountered in a programming language? More importantly, what problem do you think the language designer was trying to solve with it? Or, if you were designing your own language, which of these decisions would you make differently?&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29897/data-types-across-programming-languages-what-you-actually-need-to-know</guid>
<pubDate>Tue, 06 Oct 2026 14:07:00 +0000</pubDate>
</item>
<item>
<title>Building a Line-Rate Linux Kernel Firewall in Pure Rust with Aya and eBPF/XDP</title>
<link>https://coderlegion.com/29624/building-a-line-rate-linux-kernel-firewall-in-pure-rust-with-aya-and-ebpf-xdp</link>
<description>&lt;p&gt;Volumetric Distributed Denial of Service (DDoS) and transport-layer flooding attacks pose an existential challenge to modern cloud infrastructure, edge gateways, and container workloads. &lt;/p&gt;
&lt;p&gt;Conventional Linux packet filtering utilities such as Netfilter (&lt;code&gt;iptables&lt;/code&gt; and &lt;code&gt;nftables&lt;/code&gt;) evaluate incoming frames only after the kernel has allocated a socket buffer (&lt;code&gt;sk_buff&lt;/code&gt;) descriptor and scheduled softirq processing through the core TCP/IP stack. Under multi-gigabit line-rate flood conditions (millions of packets per second), the operating system spends the vast majority of its CPU cycles simply allocating and freeing &lt;code&gt;sk_buff&lt;/code&gt; memory metadata, triggering kernel starvation and packet drop bottlenecks even before firewall rules are evaluated.&lt;/p&gt;
&lt;p&gt;To address this fundamental OS-level bottleneck, I built &lt;strong&gt;xdp-guard&lt;/strong&gt;: a production-grade, memory-safe in-kernel defense engine and per-IP rate limiter implemented strictly in &lt;strong&gt;Pure Rust&lt;/strong&gt; using the &lt;strong&gt;Aya&lt;/strong&gt; eBPF framework.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-ebpf-xdp-at-the-driver-layer-&quot;&gt;Why eBPF / XDP at the Driver Layer?&lt;/h3&gt;
&lt;p&gt;The &lt;strong&gt;eXpress Data Path (XDP)&lt;/strong&gt; provides a programmable, bare-metal packet processing execution hook directly inside the Network Interface Card (NIC) driver subsystem before any memory descriptor allocation occurs.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Conventional Path:
[NIC RX] -&amp;gt; [IRQ / SoftIRQ] -&amp;gt; [Allocate sk_buff] -&amp;gt; [Netfilter/iptables] -&amp;gt; [Socket / App]
                                      ^
                           Severe CPU Allocation Overhead

xdp-guard Path:
[NIC RX] -&amp;gt; [eBPF / XDP Driver Hook] ---&amp;gt; (Match Blocklist) ---&amp;gt; [ XDP_DROP ] (Near-Zero CPU)
                             |
                      (Match Allowlist)
                             |
                             v
                   [ Pass to OS Stack ]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When hostile or unapproved packets are identified, returning &lt;code&gt;XDP_DROP&lt;/code&gt; causes the network driver to immediately recycle the packet ring buffer without allocating a single byte of kernel socket memory.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;architectural-highlights&quot;&gt;Architectural Highlights&lt;/h3&gt;
&lt;h4 id=&quot;1-zero-c-dependencies-pure-rust-&quot;&gt;1. Zero C Dependencies (Pure Rust)&lt;/h4&gt;
&lt;p&gt;Traditional eBPF tools rely heavily on C toolchains (&lt;code&gt;clang&lt;/code&gt;, &lt;code&gt;llvm&lt;/code&gt;, &lt;code&gt;libbpf&lt;/code&gt;). &lt;code&gt;xdp-guard&lt;/code&gt; is built entirely in Rust from the user-space management CLI down to the &lt;code&gt;no_std&lt;/code&gt; kernel eBPF bytecode using &lt;strong&gt;Aya&lt;/strong&gt;.&lt;/p&gt;
&lt;h4 id=&quot;2-strict-64-bit-memory-boundary-alignments&quot;&gt;2. Strict 64-Bit Memory Boundary Alignments&lt;/h4&gt;
&lt;p&gt;To ensure safe zero-copy communication across the kernel-userspace boundary, shared structures use explicit padding fields to guarantee 8-byte alignment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;#[repr(C)]
#[derive(Clone, Copy, Debug)]
pub struct RuleValue {
    pub action: u32,       // 0 = Pass, 1 = Drop
    pub _pad: u32,         // Explicit 4-byte padding for 8-byte boundary
    pub created_at: u64,   // Monotonic timestamp
    pub ttl_secs: u64,     // Time-to-live expiration
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The presence of &lt;code&gt;_pad: u32&lt;/code&gt; is critical: it prevents architecture-specific compiler padding discrepancies and satisfies the Linux Kernel BPF Verifier.&lt;/p&gt;
&lt;h4 id=&quot;3-dynamic-bpf-virtual-filesystem-bpffs-synchronization&quot;&gt;3. Dynamic BPF Virtual Filesystem (bpffs) Synchronization&lt;/h4&gt;
&lt;p&gt;Firewall rules (blocklist and allowlist) are dynamically synchronized using pinned BPF hash maps located in &lt;code&gt;/sys/fs/bpf/xdp_guard&lt;/code&gt;. Rules can be inserted or deleted instantaneously from the CLI without tearing down network interfaces or interrupting traffic flows.&lt;/p&gt;
&lt;h4 id=&quot;4-in-kernel-token-bucket-rate-limiter&quot;&gt;4. In-Kernel Token-Bucket Rate Limiter&lt;/h4&gt;
&lt;p&gt;In addition to static blocklists, &lt;code&gt;xdp-guard&lt;/code&gt; embeds a stateful token-bucket rate limiter directly in kernel space. Monotonic arrival intervals ($\Delta t = t&lt;em&gt;{now} - t&lt;/em&gt;{last}$) are computed using nanosecond &lt;code&gt;bpf_ktime_get_ns()&lt;/code&gt; clocks, discarding over-limit burst floods at line-rate.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;empirical-verification-real-linux-kernel-telemetry&quot;&gt;Empirical Verification: Real Linux Kernel Telemetry&lt;/h3&gt;
&lt;p&gt;To validate &lt;code&gt;xdp-guard&lt;/code&gt; under realistic operating conditions, we set up an automated Linux network namespace testbed over virtual ethernet (&lt;code&gt;veth&lt;/code&gt;) pairs (&lt;code&gt;xdp_client&lt;/code&gt; at &lt;code&gt;10.10.0.2&lt;/code&gt; and &lt;code&gt;xdp_server&lt;/code&gt; at &lt;code&gt;10.10.0.1&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Telemetry streamed directly from the kernel &lt;code&gt;STATS_MAP&lt;/code&gt; array demonstrated instantaneous 100.00% packet mitigation upon inserting an attacker IP into the kernel blocklist, followed by zero-loss recovery:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;TIMESTAMP            PASSED (PKTS)   DROPPED (PKTS)  DROP RATIO     
-----------------------------------------------------------------
09:56:56 | Pass: 1        (30.2 KB) | Drop: 0        (0 B)   | Ratio: 0.00%
[!] Inserting IP 10.10.0.2 into BLOCKLIST_MAP... [SUCCESS]
09:56:57 | Pass: 0        (30.2 KB) | Drop: 1        (98 B)  | Ratio: 100.00%
09:56:58 | Pass: 0        (30.2 KB) | Drop: 1        (196 B) | Ratio: 100.00%
09:56:59 | Pass: 0        (30.2 KB) | Drop: 1        (294 B) | Ratio: 100.00%
...
09:57:55 | Pass: 0        (30.2 KB) | Drop: 1        (5.7 KB)| Ratio: 100.00%
[!] Removing IP 10.10.0.2 from BLOCKLIST_MAP... [SUCCESS]
09:57:56 | Pass: 1        (30.3 KB) | Drop: 0        (5.7 KB)| Ratio: 0.00%
09:57:57 | Pass: 1        (30.4 KB) | Drop: 0        (5.7 KB)| Ratio: 0.00%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every single drop occurred directly within the NIC driver hook, requiring zero kernel TCP/IP stack overhead.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;source-code-amp-research-paper&quot;&gt;Source Code &amp;amp; Research Paper&lt;/h3&gt;
&lt;p&gt;The complete project, documentation, and formal research paper are fully open-source:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/rahadbhuiya/xdp-guard&quot;&gt;https://github.com/rahadbhuiya/xdp-guard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Citable Zenodo DOI:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://doi.org/10.5281/zenodo.23120995&quot;&gt;10.5281/zenodo.23120995&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automated CI:&lt;/strong&gt; Continuous testing across Rust Stable and Nightly LLVM targets via GitHub Actions.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are working on Linux kernel systems, eBPF, or Rust networking, I would love to hear your thoughts, feedback, or suggestions!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29624/building-a-line-rate-linux-kernel-firewall-in-pure-rust-with-aya-and-ebpf-xdp</guid>
<pubDate>Sat, 03 Oct 2026 13:29:33 +0000</pubDate>
</item>
<item>
<title>Mike Dabydeen on Rust, AI &amp; Engineering Leadership</title>
<link>https://coderlegion.com/29467/mike-dabydeen-on-rust-ai-engineering-leadership</link>
<description>&lt;h2 id=&quot;developer-stories-mike-dabydeen-on-rust-ai-amp-engineering-leadership&quot;&gt;Developer Stories: Mike Dabydeen on Rust, AI &amp;amp; Engineering Leadership&lt;/h2&gt;
&lt;p&gt;Welcome to another episode of &lt;strong&gt;CoderLegion Developer Stories&lt;/strong&gt;!&lt;/p&gt;
&lt;p&gt;In this episode, &lt;strong&gt;Tom Smith sits down with Mike Dabydeen&lt;/strong&gt;, an engineering leader and technology educator from Toronto, Canada.&lt;/p&gt;
&lt;p&gt;Mike shares his experience across &lt;strong&gt;engineering leadership, enterprise APIs, Rust, systems programming, AI, DevOps, and SRE&lt;/strong&gt;, as well as his perspective on building reliable software in a rapidly changing technology landscape.&lt;/p&gt;
&lt;h3 id=&quot;-watch-the-interview&quot;&gt;🎥 Watch the Interview&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://youtu.be/9mR5Ph4MBUI&quot;&gt;▶️ Watch Mike Dabydeen's Developer Story on YouTube&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;-what-we-discuss&quot;&gt;💡 What We Discuss&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;🦀 &lt;strong&gt;Rust &amp;amp; Memory Safety&lt;/strong&gt; — Exploring Rust, ownership, borrowing, and safe systems programming&lt;/li&gt;
&lt;li&gt;🤖 &lt;strong&gt;AI &amp;amp; Reliable AI&lt;/strong&gt; — Building and working with AI systems in real-world environments&lt;/li&gt;
&lt;li&gt;⚙️ &lt;strong&gt;Enterprise APIs&lt;/strong&gt; — Designing and working with APIs at scale&lt;/li&gt;
&lt;li&gt;👨💻 &lt;strong&gt;Engineering Leadership&lt;/strong&gt; — Lessons from leading engineering teams and projects&lt;/li&gt;
&lt;li&gt;☁️ &lt;strong&gt;DevOps &amp;amp; SRE&lt;/strong&gt; — Reliability, automation, cloud, and modern engineering practices&lt;/li&gt;
&lt;li&gt;🎓 &lt;strong&gt;Teaching &amp;amp; Mentoring&lt;/strong&gt; — Mike's experience sharing technology knowledge with the next generation of developers&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;-about-mike-dabydeen&quot;&gt;👨💻 About Mike Dabydeen&lt;/h3&gt;
&lt;p&gt;Mike Dabydeen is an engineering leader who works across &lt;strong&gt;enterprise APIs, decentralized systems, systems programming, and AI&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;He also teaches at &lt;strong&gt;Sheridan College and Conestoga College&lt;/strong&gt; and is active in the developer community.&lt;/p&gt;
&lt;p&gt;His technical interests and experience include &lt;strong&gt;Rust, AI, DevOps, SRE, Java, C, Python, C#/.NET, Go, Kubernetes, AWS, Azure, Docker, React, TypeScript, and more&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;🌐 &lt;a rel=&quot;nofollow&quot; href=&quot;https://michaeldabydeen.com&quot;&gt;Visit Mike's Website&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;-developer-stories&quot;&gt;🎙️ Developer Stories&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Developer Stories&lt;/strong&gt; is a CoderLegion series featuring developers, engineers, technical leaders, and technology professionals from around the world.&lt;/p&gt;
&lt;p&gt;We talk about their journeys, experiences, challenges, technologies, and lessons learned along the way.&lt;/p&gt;
&lt;p&gt;👉 &lt;strong&gt;Join the CoderLegion community:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://coderlegion.com&quot;&gt;https://coderlegion.com&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Guest:&lt;/strong&gt; Mike Dabydeen&lt;br&gt;
&lt;strong&gt;Interviewer:&lt;/strong&gt; Tom Smith&lt;br&gt;
&lt;strong&gt;Series:&lt;/strong&gt; CoderLegion Developer Stories&lt;/p&gt;
&lt;h2 id=&quot;developer-stories-rust-ai-artificial-intelligence-software-engineering-engineering-leadership-systems-programming-devops-sre&quot;&gt;developer-stories #rust #ai #artificial-intelligence #software-engineering #engineering-leadership #systems-programming #devops #sre&lt;/h2&gt;
</description>
<category>Dev Stories</category>
<guid isPermaLink="true">https://coderlegion.com/29467/mike-dabydeen-on-rust-ai-engineering-leadership</guid>
<pubDate>Fri, 02 Oct 2026 03:10:07 +0000</pubDate>
</item>
<item>
<title>Semantic Search in C++ without Python, libtorch or ONNX Runtime</title>
<link>https://coderlegion.com/29317/semantic-search-in-c-without-python-libtorch-or-onnx-runtime</link>
<description>&lt;hr&gt;
&lt;p&gt;title: Semantic Search in C++ without Python, libtorch or ONNX Runtime&lt;br&gt;
published: true&lt;br&gt;
description: Run a transformer model from C++ in four commands. No libtorch, no ONNX Runtime, no Python, no model conversion. Works from C++11 upward.&lt;br&gt;
tags: cpp, machinelearning, rust, opensource&lt;/p&gt;
&lt;h3 id=&quot;canonical-url-https-kjarni-ai-blog-cppinference-&quot;&gt;canonical_url: &lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/cppinference/&quot;&gt;https://kjarni.ai/blog/cppinference/&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Ask how to run a transformer model from C++ and you get two answers: link libtorch, or convert the model and link ONNX Runtime. Both work. Both are large, both want a toolchain of their own, and both put a second inference engine inside your process.&lt;/p&gt;
&lt;p&gt;There is a third answer, and it takes four commands.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;mkdir kjarni-quickstart &amp;amp;&amp;amp; cd kjarni-quickstart
curl -sL https://github.com/olafurjohannsson/kjarni/releases/latest/download/kjarni-x86_64-linux.tar.gz | tar xz
curl -sO https://raw.githubusercontent.com/olafurjohannsson/kjarni/main/crates/kjarni-ffi/examples/cpp/hello.cpp
g++ -std=c++23 hello.cpp -I. -L. -lkjarni_ffi -Wl,-rpath,'$ORIGIN' -o hello &amp;amp;&amp;amp; ./hello
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;related:   0.5510
unrelated: -0.0630
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is a transformer model, downloaded, loaded and run, from an empty directory. No package manager, no Python, no model conversion step. The archive holds the shared library, &lt;code&gt;kjarni.h&lt;/code&gt; (the C ABI) and &lt;code&gt;kjarni.hpp&lt;/code&gt; (a header-only C++23 wrapper). macOS and Windows&lt;br&gt;
builds are on the same [releases page(&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/olafurjohannsson/kjarni/releases&quot;&gt;https://github.com/olafurjohannsson/kjarni/releases&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Here is what &lt;code&gt;hello.cpp&lt;/code&gt; contains:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &quot;kjarni.hpp&quot;
#include &amp;lt;print&amp;gt;

int main() {
    // Downloaded once and cached under ~/.cache/kjarni, then loaded from disk.
    auto embedder = kjarni::Embedder::create({.model = &quot;minilm-l6-v2&quot;});
    if (!embedder) {
        std::println(&quot;{}&quot;, embedder.error().message());
        return 1;
    }

    auto question = embedder-&amp;gt;encode(&quot;How do I get my money back?&quot;);
    auto related = embedder-&amp;gt;encode(&quot;What is your refund policy?&quot;);
    auto unrelated = embedder-&amp;gt;encode(&quot;The weather in Reykjavik is unpredictable.&quot;);

    // No shared words with the question, but the same meaning.
    std::println(&quot;related:   {:.4f}&quot;, kjarni::cosine(*question, *related));
    std::println(&quot;unrelated: {:.4f}&quot;, kjarni::cosine(*question, *unrelated));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&quot;How do I get my money back?&quot; and &quot;What is your refund policy?&quot; share no words at all, and score 0.55. The sentence about the weather scores below zero. That gap is the entire idea behind semantic search.&lt;/p&gt;
&lt;h3 id=&quot;how-semantic-search-works&quot;&gt;How semantic search works&lt;/h3&gt;
&lt;p&gt;An embedding model reads text and returns a vector, an array of floats, 384 numbers for the model above. Text with similar meaning produces vectors that point in similar directions.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;refund policy&quot;  -&amp;gt; [0.12, -0.34, 0.56, ...]  (384 numbers)
&quot;get money back&quot; -&amp;gt; [0.11, -0.33, 0.55, ...]  (384 numbers)  &amp;lt;- close
&quot;weather today&quot;  -&amp;gt; [-0.45, 0.23, -0.12, ...] (384 numbers)  &amp;lt;- far
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You compare two vectors with cosine similarity, which measures the angle between them and ignores their length. It runs from 1 for identical direction to -1 for opposite.&lt;/p&gt;
&lt;h3 id=&quot;what-you-actually-link-against&quot;&gt;What you actually link against&lt;/h3&gt;
&lt;p&gt;This is the part that decides whether the approach is worth anything, so it is worth checking rather than believing. Ask the linker:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-console&quot;&gt;$ ldd hello
    linux-vdso.so.1
    libkjarni_ffi.so =&amp;gt; /home/you/kjarni-quickstart/libkjarni_ffi.so
    libstdc++.so.6 =&amp;gt; /lib/x86_64-linux-gnu/libstdc++.so.6
    libm.so.6 =&amp;gt; /lib/x86_64-linux-gnu/libm.so.6
    libgcc_s.so.1 =&amp;gt; /lib/x86_64-linux-gnu/libgcc_s.so.1
    libc.so.6 =&amp;gt; /lib/x86_64-linux-gnu/libc.so.6
    /lib64/ld-linux-x86-64.so.2
    libpthread.so.0 =&amp;gt; /lib/x86_64-linux-gnu/libpthread.so.0
    libdl.so.2 =&amp;gt; /lib/x86_64-linux-gnu/libdl.so.2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Kjarni, the C++ runtime, and the parts of glibc every program already uses: libc, libm, libgcc, libpthread and libdl. That is the whole list. No libtorch, no &lt;code&gt;onnxruntime&lt;/code&gt;, no Python, no CUDA runtime. The binary is 283 KB and the library is 19.4 MB, which includes the tokenizer, the model loaders and every kernel.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;-Wl,-rpath,'$ORIGIN'&lt;/code&gt; in the build line is what makes that first entry resolve to the library sitting next to your binary rather than something in &lt;code&gt;/usr/local/lib&lt;/code&gt;. Keep it, and&lt;br&gt;
the directory you built in is a directory you can copy somewhere else and run.&lt;/p&gt;
&lt;p&gt;A dependency you cannot see in &lt;code&gt;ldd&lt;/code&gt; is a dependency that cannot break you on a machine that is not yours.&lt;/p&gt;
&lt;h3 id=&quot;errors-are-values&quot;&gt;Errors are values&lt;/h3&gt;
&lt;p&gt;Every fallible call returns &lt;code&gt;std::expected&amp;lt;T, kjarni::Error&amp;gt;&lt;/code&gt;. Nothing in the header throws except &lt;code&gt;std::bad_alloc&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;auto embedder = kjarni::Embedder::create({.model = &quot;minilm-l6-v2&quot;});
if (!embedder) {
    std::println(stderr, &quot;could not load model: {}&quot;, embedder.error().message());
    return 1;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Whether a missing model file is exceptional depends on the program. A batch job should die; a desktop application should show a message and carry on. Returning the failure lets the caller decide, and puts it in the signature where it cannot be missed.&lt;/p&gt;
&lt;p&gt;The options are a designated-initialiser aggregate, so a call names only what it changes:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Embedder::create({.model = &quot;mpnet-base-v2&quot;, .gpu = true})&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;searching-a-corpus&quot;&gt;Searching a corpus&lt;/h3&gt;
&lt;p&gt;Encode the documents once, encode the query at search time, sort by similarity.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &quot;kjarni.hpp&quot;

#include &amp;lt;algorithm&amp;gt;
#include &amp;lt;print&amp;gt;
#include &amp;lt;ranges&amp;gt;
#include &amp;lt;string_view&amp;gt;
#include &amp;lt;vector&amp;gt;

int main() {
    auto embedder = kjarni::Embedder::create({.model = &quot;minilm-l6-v2&quot;});
    if (!embedder) {
        std::println(stderr, &quot;could not load model: {}&quot;, embedder.error().message());
        return 1;
    }

    constexpr std::array docs = {
        std::string_view{&quot;How do I reset my password?&quot;},
        std::string_view{&quot;What is your refund policy?&quot;},
        std::string_view{&quot;Do you ship internationally?&quot;},
        std::string_view{&quot;How do I update my billing address?&quot;},
        std::string_view{&quot;Where can I track my order?&quot;},
    };

    std::vector&amp;lt;std::vector&amp;lt;float&amp;gt;&amp;gt; corpus;
    corpus.reserve(docs.size());
    for (std::string_view d : docs) {
        auto v = embedder-&amp;gt;encode(d);
        if (!v) {
            std::println(stderr, &quot;encode failed: {}&quot;, v.error().message());
            return 1;
        }
        corpus.push_back(v-&amp;gt;to_vector());
    }

    constexpr std::string_view query = &quot;I need to change my login credentials&quot;;
    auto q = embedder-&amp;gt;encode(query);
    if (!q) {
        std::println(stderr, &quot;encode failed: {}&quot;, q.error().message());
        return 1;
    }

    std::vector&amp;lt;std::pair&amp;lt;float, std::string_view&amp;gt;&amp;gt; scored;
    for (auto [i, doc] : std::views::enumerate(docs))
        scored.emplace_back(kjarni::cosine(q-&amp;gt;values(), corpus[i]), doc);

    std::ranges::sort(scored, std::ranges::greater{},
                      &amp;amp;std::pair&amp;lt;float, std::string_view&amp;gt;::first);

    std::println(&quot;query: \&quot;{}\&quot;&quot;, query);
    for (auto [score, doc] : scored)
        std::println(&quot;  {:.4f}  {}&quot;, score, doc);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;query: &quot;I need to change my login credentials&quot;
  0.5981  How do I reset my password?
  0.4067  How do I update my billing address?
  0.0767  Where can I track my order?
  -0.0027  What is your refund policy?
  -0.0451  Do you ship internationally?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&quot;Change my login credentials&quot; matches &quot;reset my password&quot; at 0.60 while sharing no words with it, and &quot;update my billing address&quot; comes second because changing account details is a&lt;br&gt;
related idea. That is what you cannot get from keyword matching.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;encode&lt;/code&gt; returns a &lt;code&gt;kjarni::Embedding&lt;/code&gt;, which owns the array the C API returned and frees it in its destructor. It hands out a &lt;code&gt;std::span&amp;lt;const float&amp;gt;&lt;/code&gt; through &lt;code&gt;values()&lt;/code&gt;, so it drops&lt;br&gt;
straight into ranges, and &lt;code&gt;to_vector()&lt;/code&gt; copies when the data has to outlive the object. There is no raw pointer to forget.&lt;/p&gt;
&lt;h3 id=&quot;classification-and-reranking&quot;&gt;Classification and reranking&lt;/h3&gt;
&lt;p&gt;The other models follow the same shape.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;auto clf = kjarni::Classifier::create({.model = &quot;roberta-sentiment&quot;});
if (!clf) { std::println(stderr, &quot;{}&quot;, clf.error().message()); return 1; }

for (std::string_view t : {&quot;I love this product!&quot;,
                           &quot;Terrible quality, broke after one day.&quot;}) {
    auto top = clf-&amp;gt;top(t);
    if (!top) { std::println(stderr, &quot;{}&quot;, top.error().message()); return 1; }
    if (*top) std::println(&quot;  {:&amp;lt;9} {:5.1f}%  \&quot;{}\&quot;&quot;, (*top)-&amp;gt;name, (*top)-&amp;gt;score * 100, t);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;  positive   98.5%  &quot;I love this product!&quot;
  negative   94.1%  &quot;Terrible quality, broke after one day.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;top()&lt;/code&gt; returns &lt;code&gt;Result&amp;lt;std::optional&amp;lt;Label&amp;gt;&amp;gt;&lt;/code&gt;, which puts two separate questions in the type: did the call work, and did it produce a label. A model with nothing above threshold is&lt;br&gt;
not a failure.&lt;/p&gt;
&lt;p&gt;Reranking uses a cross-encoder, which reads the query and each document together instead of comparing two independently produced vectors. It is slower and much more precise, so it runs&lt;br&gt;
as a second pass over whatever the embeddings retrieved:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;auto rr = kjarni::Reranker::create();
if (!rr) { std::println(stderr, &quot;{}&quot;, rr.error().message()); return 1; }

const std::vector&amp;lt;std::string&amp;gt; docs = {
    &quot;Machine learning is a subset of artificial intelligence.&quot;,
    &quot;Deep learning uses neural networks with many layers.&quot;,
    &quot;The weather today is sunny.&quot;,
};

auto ranked = rr-&amp;gt;rerank(&quot;What is machine learning?&quot;, docs);
if (!ranked) { std::println(stderr, &quot;{}&quot;, ranked.error().message()); return 1; }

for (const auto&amp;amp; r : *ranked)
    std::println(&quot;  {:&amp;gt;9.4f}  {}&quot;, r.score, docs[r.index]);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;    10.5139  Machine learning is a subset of artificial intelligence.
    -5.5301  Deep learning uses neural networks with many layers.
   -11.1001  The weather today is sunny.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The scores are logits, not probabilities. What matters is the ordering and the size of the gap between one document and the next. &lt;code&gt;Ranked&lt;/code&gt; gives back an index into your input rather than a copy of the text, so whatever IDs, URLs and permissions came with your documents stay attached to them.&lt;/p&gt;
&lt;p&gt;One signature detail: &lt;code&gt;rerank&lt;/code&gt; takes &lt;code&gt;std::span&amp;lt;const std::string&amp;gt;&lt;/code&gt; rather than &lt;code&gt;string_view&lt;/code&gt;, because the C call underneath needs an array of null terminated pointers and a &lt;code&gt;string_view&lt;/code&gt; does not promise one.&lt;/p&gt;
&lt;h3 id=&quot;the-same-numbers-in-every-language&quot;&gt;The same numbers in every language&lt;/h3&gt;
&lt;p&gt;Those figures are not specific to C++. The reranker scores 10.5139, -5.5301 and -11.1001 are&lt;br&gt;
the same values the &lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/documentsearchengine/&quot;&gt;C# document search post&lt;/a&gt; prints, and the&lt;br&gt;
similarity scores are the ones in &lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/semanticsearch/&quot;&gt;Semantic Search in C#&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That is the reason a C ABI is the right shape here. There is one engine and one set of&lt;br&gt;
kernels, and the C++ header is a wrapper over the same entry points the C#, Go and Python&lt;br&gt;
packages call. There is no second implementation to drift from the first.&lt;/p&gt;
&lt;h3 id=&quot;choosing-a-model&quot;&gt;Choosing a model&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Model &lt;/th&gt;&lt;th&gt;Dimensions &lt;/th&gt;&lt;th&gt;Input limit &lt;/th&gt;&lt;th&gt;Notes&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;minilm-l6-v2&lt;/code&gt; &lt;/td&gt;&lt;td&gt;384 &lt;/td&gt;&lt;td&gt;256 tokens &lt;/td&gt;&lt;td&gt;Default. Fast, good quality per byte&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;mpnet-base-v2&lt;/code&gt; &lt;/td&gt;&lt;td&gt;768 &lt;/td&gt;&lt;td&gt;384 tokens &lt;/td&gt;&lt;td&gt;Higher quality, slower&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;nomic-embed-text&lt;/code&gt; &lt;/td&gt;&lt;td&gt;768 &lt;/td&gt;&lt;td&gt;8192 tokens &lt;/td&gt;&lt;td&gt;Long documents, though trained at 2048&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;bge-m3&lt;/code&gt; &lt;/td&gt;&lt;td&gt;1024 &lt;/td&gt;&lt;td&gt;8192 tokens &lt;/td&gt;&lt;td&gt;Large, multilingual&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Mind the input limit column. &lt;code&gt;minilm-l6-v2&lt;/code&gt; reads 256 tokens, roughly 900 characters, and silently drops the rest: no error, no warning, just a vector computed from the part it saw.&lt;br&gt;
The cross-encoder has its own limit, reading query and document as one sequence capped at 512 tokens. If your documents are longer than that, chunk them. There is a measurement of&lt;br&gt;
what the truncation costs in &lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/embedding-truncation/&quot;&gt;Your MiniLM Embeddings Are Probably Truncating at 256 Tokens&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;practical-notes&quot;&gt;Practical notes&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Threading.&lt;/strong&gt; The handles are not individually thread safe. Give each thread its own, or serialise calls. The engine already parallelises across cores inside a single call, so one embedder will use the machine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;C++23 is only needed for &lt;code&gt;std::expected&lt;/code&gt;.&lt;/strong&gt; Everything else in &lt;code&gt;kjarni.hpp&lt;/code&gt; is C++20, and GCC 13 or Clang 17 and newer will build it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There is no package manager integration yet.&lt;/strong&gt; No Conan recipe, no vcpkg port. The four commands above are the install story on Linux, and the equivalent archives for macOS and&lt;br&gt;
Windows are on the releases page.&lt;/p&gt;
&lt;h3 id=&quot;if-you-are-on-c-11-or-c-17&quot;&gt;If you are on C++11 or C++17&lt;/h3&gt;
&lt;p&gt;Plenty of codebases are, and a header that demands C++23 is not much use to them. It is worth being clear about what the requirement actually covers: &lt;code&gt;kjarni.hpp&lt;/code&gt; needs C++23 for&lt;br&gt;
&lt;code&gt;std::expected&lt;/code&gt;, and nothing else does. &lt;code&gt;kjarni.h&lt;/code&gt; is plain C, it is the interface every other language binding is built on, and it works from C++11 upward.&lt;/p&gt;
&lt;p&gt;The whole of the C++23 convenience is one RAII wrapper and a copy:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;#include &quot;kjarni.h&quot;

#include &amp;lt;cstdio&amp;gt;
#include &amp;lt;memory&amp;gt;
#include &amp;lt;string&amp;gt;
#include &amp;lt;vector&amp;gt;

namespace {

struct EmbedderDeleter {
    void operator()(KjarniEmbedder* p) const { kjarni_embedder_free(p); }
};
using EmbedderPtr = std::unique_ptr&amp;lt;KjarniEmbedder, EmbedderDeleter&amp;gt;;

std::vector&amp;lt;float&amp;gt; encode(KjarniEmbedder* emb, const char* text) {
    KjarniFloatArray arr{};
    if (kjarni_embedder_encode(emb, text, &amp;amp;arr) != KJARNI_ERROR_CODE_OK) {
        std::fprintf(stderr, &quot;encode failed: %s\n&quot;, kjarni_last_error_message());
        return {};
    }
    std::vector&amp;lt;float&amp;gt; out(arr.data, arr.data + arr.len);
    kjarni_float_array_free(arr);   // copied out, so release the engine's buffer
    return out;
}

} // namespace

int main() {
    KjarniEmbedderConfig cfg = kjarni_embedder_config_default();
    cfg.model_name = &quot;minilm-l6-v2&quot;;
    cfg.quiet = 1;

    KjarniEmbedder* raw = nullptr;
    if (kjarni_embedder_new(&amp;amp;cfg, &amp;amp;raw) != KJARNI_ERROR_CODE_OK) {
        std::fprintf(stderr, &quot;could not load model: %s\n&quot;, kjarni_last_error_message());
        return 1;
    }
    EmbedderPtr embedder(raw);

    const std::vector&amp;lt;float&amp;gt; question  = encode(embedder.get(), &quot;How do I get my money back?&quot;);
    const std::vector&amp;lt;float&amp;gt; related   = encode(embedder.get(), &quot;What is your refund policy?&quot;);
    const std::vector&amp;lt;float&amp;gt; unrelated = encode(embedder.get(), &quot;The weather in Reykjavik is unpredictable.&quot;);
    if (question.empty() || related.empty() || unrelated.empty()) return 1;

    std::printf(&quot;related:   %.4f\n&quot;,
                kjarni_cosine_similarity(question.data(), related.data(), question.size()));
    std::printf(&quot;unrelated: %.4f\n&quot;,
                kjarni_cosine_similarity(question.data(), unrelated.data(), question.size()));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;g++ -std=c++17 hello17.cpp -I. -L. -lkjarni_ffi -Wl,-rpath,'$ORIGIN' -o hello17
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;related:   0.5510
unrelated: -0.0630
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The same numbers as the C++23 version, because it is the same engine underneath. That compiles unchanged under &lt;code&gt;-std=c++11&lt;/code&gt; as well.&lt;/p&gt;
&lt;p&gt;Two rules cover the manual memory. Any &lt;code&gt;KjarniFloatArray&lt;/code&gt; you receive is freed with &lt;code&gt;kjarni_float_array_free&lt;/code&gt; once you have copied what you need out of it, and any handle is freed with its matching &lt;code&gt;_free&lt;/code&gt; function. Wrapping the handle in a &lt;code&gt;unique_ptr&lt;/code&gt; with a&lt;br&gt;
custom deleter, as above, means the second rule takes care of itself on every return path.&lt;/p&gt;
&lt;p&gt;Error text comes from &lt;code&gt;kjarni_last_error_message()&lt;/code&gt;, and it reports the most recent failure process wide, so read it immediately after the call that failed rather than saving it up.&lt;/p&gt;
&lt;h3 id=&quot;compared-to-the-alternatives&quot;&gt;Compared to the alternatives&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;libtorch &lt;/th&gt;&lt;th&gt;ONNX Runtime &lt;/th&gt;&lt;th&gt;Kjarni&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Install &lt;/td&gt;&lt;td&gt;download SDK, match ABI &lt;/td&gt;&lt;td&gt;package plus model conversion &lt;/td&gt;&lt;td&gt;one archive&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Model format &lt;/td&gt;&lt;td&gt;TorchScript &lt;/td&gt;&lt;td&gt;.onnx, converted &lt;/td&gt;&lt;td&gt;HuggingFace safetensors and GGUF directly&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Extra entries in &lt;code&gt;ldd&lt;/code&gt; &lt;/td&gt;&lt;td&gt;many &lt;/td&gt;&lt;td&gt;its own stack &lt;/td&gt;&lt;td&gt;none beyond libc&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Tokenizer &lt;/td&gt;&lt;td&gt;bring your own &lt;/td&gt;&lt;td&gt;bring your own &lt;/td&gt;&lt;td&gt;included&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;GPU &lt;/td&gt;&lt;td&gt;CUDA toolkit &lt;/td&gt;&lt;td&gt;CUDA or DirectML &lt;/td&gt;&lt;td&gt;WebGPU, no toolkit&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The trade is scope. libtorch runs anything expressible in TorchScript. Kjarni runs the model families it implements: BERT-style encoders, cross-encoders, Llama-family decoders, T5, BART and Whisper. For an arbitrary research model, convert it and use ONNX Runtime. For embeddings, classification, reranking or chat inside a C++ program that has to ship somewhere, one archive with no toolchain is a smaller problem than either.&lt;/p&gt;
&lt;h3 id=&quot;getting-it&quot;&gt;Getting it&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai&quot;&gt;kjarni.ai&lt;/a&gt; - documentation and the rest of these posts&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/olafurjohannsson/kjarni/releases&quot;&gt;Releases&lt;/a&gt; - the archive used above, for Linux, macOS and Windows&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/olafurjohannsson/kjarni&quot;&gt;GitHub&lt;/a&gt; - source, including &lt;code&gt;kjarni.hpp&lt;/code&gt; and the C++ examples&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.nuget.org/packages/Kjarni&quot;&gt;NuGet&lt;/a&gt; - the same engine from C#&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/kjarni-wasm&quot;&gt;npm&lt;/a&gt; - the same engine in the browser, via WebAssembly&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://pkg.go.dev/github.com/olafurjohannsson/kjarni-go&quot;&gt;Go module&lt;/a&gt; - the same engine from Go&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://crates.io/crates/kjarni&quot;&gt;crates.io&lt;/a&gt; - the Rust engine itself&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;other-resources&quot;&gt;Other Resources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/semanticsearch/&quot;&gt;Semantic Search in C#&lt;/a&gt; - The same engine and the same vectors, from .NET&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/documentsearchengine/&quot;&gt;Build a Document Search Engine in C#&lt;/a&gt; - Keyword and semantic retrieval combined, with reranking&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/nativeinference/&quot;&gt;Why I Built a Native ML Inference Engine in Rust&lt;/a&gt; - What is underneath all of this&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/cli/&quot;&gt;ML from the Command Line&lt;/a&gt; - The same models as a UNIX tool&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://kjarni.ai/blog/embedding-truncation/&quot;&gt;Your MiniLM Embeddings Are Probably Truncating at 256 Tokens&lt;/a&gt; - Measured, with the cost&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29317/semantic-search-in-c-without-python-libtorch-or-onnx-runtime</guid>
<pubDate>Wed, 30 Sep 2026 21:39:52 +0000</pubDate>
</item>
<item>
<title>Uncle Bob's uml-viewer on Rust: 35 dependency cycles down to 3</title>
<link>https://coderlegion.com/28116/uncle-bobs-uml-viewer-on-rust-35-dependency-cycles-down-to-3</link>
<description>&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;TL;DR.&lt;/strong&gt; Uncle Bob released &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/unclebob/uml-viewer&quot;&gt;uml-viewer&lt;/a&gt;, a clickable architecture diagram that draws Dependency Rule violations in red and is meant to be driven together with a coding agent. It only parses Clojure, so I wrote a 200-line script that turns my Rust crate into its input file. The first picture showed 167 red arrows and 35 dependency cycles. Six commits and 315 files later: 109 red arrows, 3 cycles, and &lt;code&gt;app.rs&lt;/code&gt; down from 1,087 lines to 642. No behavior changed. The tool matters less than the loop it forces on you: look, point at one arrow, let the agent move code, look again. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev/blog/uml-viewer-rust-dependency-cycles?utm_source=coderlegion&amp;amp;utm_medium=community&amp;amp;utm_campaign=uml-viewer&amp;amp;utm_content=inline&quot;&gt;original post&lt;/a&gt; has the same text with a collapsible FAQ and the source list at the end.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Uncle Bob's argument for the tool is short: agents still need supervision, and reading every line they write is the bottleneck. So supervise the structure instead of the text: draw the system, find the shape that is wrong, tell the agent to fix that shape, and check the new drawing.&lt;/p&gt;
&lt;p&gt;I wanted to know if that works on a real codebase, so I pointed it at &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/uptimepage/uptimepage&quot;&gt;Uptimepage&lt;/a&gt;, about 146,000 lines of Rust in one crate. It does, with one condition: the picture is only as honest as the input file you feed it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://uptimepage.dev/static/marketing/blog-uml-viewer-layers.webp&quot; alt=&quot;Two panels of five stacked layers, vocabulary at the bottom and assembly at the top. Before: five red arrows point up from storage and config into api, worker, auth and billing, and two grey loops mark cycles between api and web and between storage and quotas. After: the same modules plus dashed green boxes for request, templates, security and pagination, and every arrow points down or sideways.&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Five of the 167 red arrows and two of the 35 cycles, and where the code went. Every arrow that pointed up now points down.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id=&quot;what-uml-viewer-is&quot;&gt;What uml-viewer is&lt;/h3&gt;
&lt;p&gt;uml-viewer is an open-source desktop tool by Uncle Bob (Robert Martin) that draws a codebase as a UML-like diagram you can click. It is written in Clojure and needs the Clojure CLI and Java 21 or newer.&lt;/p&gt;
&lt;p&gt;Namespaces are components. The modules inside a namespace are the component's elements. Nesting can go as deep as your source tree does. You double-click a component to open the next level, double-click a module to see its class card, and click a function to open the source file at that line.&lt;/p&gt;
&lt;p&gt;Color comes from CRAP and mutation scores, so a red box is code with high complexity and weak tests, or code with no metrics at all, which the README counts as the worst grade. Red arrows come from the Dependency Rule: you tell the tool which namespaces sit at which architectural level, and every dependency that points from an inner level to an outer level is drawn red.&lt;/p&gt;
&lt;p&gt;The tool is built to run next to an agent. By default it opens a tmux window with Grok in the examined project and the two talk through a small mailbox directory. You can also drive it by hand from any agent session: regenerate the input file, press R in the viewer, and the diagram reloads.&lt;/p&gt;
&lt;h3 id=&quot;getting-a-rust-crate-into-it&quot;&gt;Getting a Rust crate into it&lt;/h3&gt;
&lt;p&gt;The only parser it ships reads Clojure. That sounded like the end of the experiment, until I read what the viewer actually consumes. It never sees source code. It reads one EDN file: a list of classes with a namespace and a level, a list of edges with a from, a to and a kind, and a list of levels. That is a format any script can emit.&lt;/p&gt;
&lt;p&gt;So I asked the agent to write a generator for Rust. It came out at about 200 lines of Python and does four things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every &lt;code&gt;.rs&lt;/code&gt; file is one class. &lt;code&gt;mod.rs&lt;/code&gt; stands for its directory and &lt;code&gt;lib.rs&lt;/code&gt; is skipped.&lt;/li&gt;
&lt;li&gt;Every &lt;code&gt;use crate::…&lt;/code&gt;, &lt;code&gt;super::…&lt;/code&gt; and &lt;code&gt;self::…&lt;/code&gt; path, plus every inline &lt;code&gt;crate::a::b&lt;/code&gt; path in a function body, becomes a dependency edge to the nearest enclosing module that exists as a file.&lt;/li&gt;
&lt;li&gt;Comments are stripped first, so a path mentioned in a doc comment does not count as a dependency.&lt;/li&gt;
&lt;li&gt;A hand-written &lt;code&gt;LEVELS&lt;/code&gt; list gives each top-level module a rank.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last item is the only opinion in the script:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;# inner (high level) first, matching the Dependency Rule ranks
LEVELS = [
    [&quot;domain&quot;, &quot;error&quot;, &quot;text&quot;, &quot;metric_names&quot;],
    [&quot;storage&quot;, &quot;security&quot;, &quot;net&quot;, &quot;http_client&quot;, &quot;config&quot;,
     &quot;quotas&quot;, &quot;observability&quot;, &quot;pagination&quot;],
    [&quot;pipeline&quot;, &quot;escalation&quot;, &quot;notifier&quot;, &quot;email&quot;, &quot;telegram&quot;,
     &quot;whatsapp&quot;, &quot;billing&quot;, &quot;auth&quot;, &quot;analytics&quot;,
     &quot;jobs&quot;, &quot;http_outbound&quot;, &quot;targets&quot;],
    [&quot;api&quot;, &quot;web&quot;, &quot;mcp&quot;, &quot;marketing&quot;, &quot;agent&quot;, &quot;worker&quot;, &quot;scheduler&quot;,
     &quot;request&quot;, &quot;public_status&quot;, &quot;templates&quot;, &quot;oauth&quot;,
     &quot;ad_hoc_dispatch&quot;, &quot;channels&quot;],
    [&quot;app&quot;, &quot;router&quot;, &quot;bootstrap&quot;, &quot;main&quot;, &quot;bin&quot;],
]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rank 0 is vocabulary that everyone may use: domain types, error codes, text helpers. Rank 1 is infrastructure: storage, security primitives, config. Rank 2 is the services that do the work: probing, escalation, notification, billing. Rank 3 is every way into the system: HTTP handlers, HTML views, the MCP server, the marketing site. Rank 4 is assembly: the app state, the router, main.&lt;/p&gt;
&lt;p&gt;The rule is then mechanical. An edge is a violation when the module it comes from has a smaller rank than the module it points to. Same rank is allowed. Foreign crates like axum and sqlx are ovals outside the diagram and are never compared.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;The picture is only as honest as the levels list&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The generator does not decide the architecture. I do, in that list. If you put &lt;code&gt;api&lt;/code&gt; in the same group as &lt;code&gt;domain&lt;/code&gt;, the diagram turns green and you have learned nothing. Write the list you want to be true, then let the red arrows show how far the code is from it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;what-the-first-picture-showed&quot;&gt;What the first picture showed&lt;/h3&gt;
&lt;p&gt;The first diagram had 167 red arrows, 35 pairs of modules that imported each other, 17 modules importing &lt;code&gt;api&lt;/code&gt;, and an &lt;code&gt;app.rs&lt;/code&gt; of 1,087 lines.&lt;/p&gt;
&lt;p&gt;Behind the numbers were shapes I half knew about and had never seen drawn:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;storage&lt;/code&gt; reached up into &lt;code&gt;api&lt;/code&gt; for error codes and dashboard read models, and into &lt;code&gt;worker&lt;/code&gt; for heartbeat state.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;api&lt;/code&gt; and &lt;code&gt;web&lt;/code&gt; imported each other. The JSON side took session and token extractors, cookies and client IP from the HTML side, and the HTML side took the heartbeat read model from the JSON handlers.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;quotas&lt;/code&gt; read accounts and organizations from &lt;code&gt;storage&lt;/code&gt;, while &lt;code&gt;storage&lt;/code&gt; embedded quota SQL fragments and read plan types from &lt;code&gt;quotas&lt;/code&gt;. A cycle in both directions.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;marketing&lt;/code&gt; and &lt;code&gt;oauth&lt;/code&gt; imported &lt;code&gt;web&lt;/code&gt; for three template filters.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;config&lt;/code&gt; imported &lt;code&gt;auth&lt;/code&gt; and &lt;code&gt;billing&lt;/code&gt; for two enums.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of this was visible in the review of any single pull request. Every one of those imports was reasonable on the day it was written. A diff shows one import at a time, so the sum of them never appeared in any review.&lt;/p&gt;
&lt;h3 id=&quot;the-loop&quot;&gt;The loop&lt;/h3&gt;
&lt;p&gt;Every round had the same six steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Regenerate the EDN file and reload the viewer.&lt;/li&gt;
&lt;li&gt;Pick one red arrow. Hover it to see which module pairs it bundles.&lt;/li&gt;
&lt;li&gt;Tell the agent one thing: what must not import what, and where the shared piece should live.&lt;/li&gt;
&lt;li&gt;The agent moves the code and runs the compiler and the tests.&lt;/li&gt;
&lt;li&gt;Regenerate. Check that the arrow is gone and count the new ones.&lt;/li&gt;
&lt;li&gt;Run the full suite, review the diff, commit.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The instruction in step 3 is the part that makes this work. It looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;storage must not import api. The error codes and the dashboard read models
it takes from there are crate-wide vocabulary. Move the codes to error::codes
and the read models to domain::metrics, and point every reader at the new
path. No behavior changes.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is one rule, one violation, and one place to put the result. The agent does not have to guess what &quot;cleaner&quot; means. It has a named arrow to remove, and the next diagram says whether it did.&lt;/p&gt;
&lt;p&gt;I ran this with Claude Code, but nothing in the loop depends on it. Codex, Cursor, Grok, or any agent that can edit files and run a test suite gets the same instruction and the same picture afterwards.&lt;/p&gt;
&lt;p&gt;Six rounds took the crate from 167 red arrows to 109:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Round &lt;/th&gt;&lt;th&gt;Red arrows I pointed at &lt;/th&gt;&lt;th&gt;Where the code went&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;1 &lt;/td&gt;&lt;td&gt;&lt;code&gt;storage&lt;/code&gt; into &lt;code&gt;api&lt;/code&gt; and &lt;code&gt;worker&lt;/code&gt;, &lt;code&gt;config&lt;/code&gt; into &lt;code&gt;auth&lt;/code&gt; &lt;/td&gt;&lt;td&gt;error codes to &lt;code&gt;error::codes&lt;/code&gt;, 12 read models to &lt;code&gt;domain::metrics&lt;/code&gt;, provider enums to &lt;code&gt;domain::credential&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2 &lt;/td&gt;&lt;td&gt;&lt;code&gt;storage&lt;/code&gt; into &lt;code&gt;auth&lt;/code&gt; and &lt;code&gt;api&lt;/code&gt; &lt;/td&gt;&lt;td&gt;token hashing, HMAC and SHA-256 helpers to &lt;code&gt;security&lt;/code&gt;, redaction scrubbers to &lt;code&gt;security::redaction&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3 &lt;/td&gt;&lt;td&gt;&lt;code&gt;api&lt;/code&gt; and &lt;code&gt;web&lt;/code&gt; into each other &lt;/td&gt;&lt;td&gt;extractors, cookies, client IP and host resolution to a new &lt;code&gt;request&lt;/code&gt; module that imports neither&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4 &lt;/td&gt;&lt;td&gt;&lt;code&gt;storage&lt;/code&gt; and &lt;code&gt;quotas&lt;/code&gt; into each other &lt;/td&gt;&lt;td&gt;SQL fragments to &lt;code&gt;storage::count_sql&lt;/code&gt;, plan types to &lt;code&gt;domain::quota&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5 &lt;/td&gt;&lt;td&gt;&lt;code&gt;marketing&lt;/code&gt;, &lt;code&gt;oauth&lt;/code&gt; and &lt;code&gt;api&lt;/code&gt; into &lt;code&gt;web&lt;/code&gt; &lt;/td&gt;&lt;td&gt;template filters and formatters to &lt;code&gt;templates&lt;/code&gt;, &lt;code&gt;app.rs&lt;/code&gt; split into &lt;code&gt;config::boot&lt;/code&gt;, &lt;code&gt;observability::readiness&lt;/code&gt;, &lt;code&gt;targets::status&lt;/code&gt; and &lt;code&gt;net&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;6 &lt;/td&gt;&lt;td&gt;&lt;code&gt;public_status&lt;/code&gt; and &lt;code&gt;web&lt;/code&gt; into &lt;code&gt;api&lt;/code&gt;, &lt;code&gt;security&lt;/code&gt; into &lt;code&gt;http_client&lt;/code&gt; &lt;/td&gt;&lt;td&gt;page envelopes to &lt;code&gt;pagination&lt;/code&gt;, &lt;code&gt;Cipher::from_config&lt;/code&gt; into &lt;code&gt;security&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;And the totals:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://uptimepage.dev/static/marketing/blog-uml-viewer-before-after.webp&quot; alt=&quot;Four paired horizontal bars, red for before and green for after: Dependency Rule violations 167 to 109, module pairs importing each other 35 to 3, modules importing api 17 to 4, lines in app.rs 1,087 to 642.&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Six commits, 315 files, same test suite before and after. Violations down 35 percent, cycles down 91 percent.&lt;/em&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;Before &lt;/th&gt;&lt;th&gt;After&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Dependency Rule violations &lt;/td&gt;&lt;td&gt;167 &lt;/td&gt;&lt;td&gt;109&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Module pairs importing each other &lt;/td&gt;&lt;td&gt;35 &lt;/td&gt;&lt;td&gt;3&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Modules importing &lt;code&gt;api&lt;/code&gt; &lt;/td&gt;&lt;td&gt;17 &lt;/td&gt;&lt;td&gt;4&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Lines in &lt;code&gt;app.rs&lt;/code&gt; &lt;/td&gt;&lt;td&gt;1,087 &lt;/td&gt;&lt;td&gt;642&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Files touched &lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;315&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Behavior changes &lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;I regenerated the graph at every one of the six commits to see what each round removed:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://uptimepage.dev/static/marketing/blog-uml-viewer-rounds.webp&quot; alt=&quot;A line chart over seven points from start to round six. Module pairs importing each other fall 35, 28, 23, 19, 16, 8, 3. Modules importing api fall 17, 11, 9, 6, 6, 6, 4. Under each round a short label names what moved.&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Cycles and api importers per round. The first two rounds did most of the work on the red count; rounds three to six were about the cycles.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The shape surprised me. The red arrow count stopped moving after round two. Rounds three to six barely touched it, but they took the cycles from 23 down to 3, because a cycle between two modules at the same level is not a Dependency Rule violation at all. The rule catches arrows that point up and says nothing about two modules on the same level that import each other, so you need both counts.&lt;/p&gt;
&lt;p&gt;Not every move survived. In a later round the agent moved the health-check paths into &lt;code&gt;observability&lt;/code&gt;, and a coupling test on the marketing module said no, because that module is only allowed to reach a short list of leaf modules. Another move put the strict JSON parser under &lt;code&gt;request&lt;/code&gt;, then had to come back because that parser reads the OpenAPI document. Both reverts took minutes, because the diagram and the test said so before the commit did.&lt;/p&gt;
&lt;h3 id=&quot;why-i-stopped-at-91&quot;&gt;Why I stopped at 91&lt;/h3&gt;
&lt;p&gt;After the six commits, 109 red arrows were left. Most pointed into &lt;code&gt;app&lt;/code&gt;, but ten did not: a sampler and a silence job that lived under &lt;code&gt;observability&lt;/code&gt; but reached into the scheduler and the notifier, two background jobs reading &lt;code&gt;public_status&lt;/code&gt;, the HTTP metrics layer, and the rate-limit middleware in &lt;code&gt;quotas&lt;/code&gt; reading the app state. That middleware was also the third of the three cycles. One more round moved each of those into the module that owns it and left 91 red arrows and 2 cycles.&lt;/p&gt;
&lt;p&gt;Every one of the 91 is the same shape. Handlers read the app state, and the app state is built from the modules those handlers live in. &lt;code&gt;app&lt;/code&gt; imports &lt;code&gt;api&lt;/code&gt; and &lt;code&gt;web&lt;/code&gt; to mount them; &lt;code&gt;api&lt;/code&gt; and &lt;code&gt;web&lt;/code&gt; import &lt;code&gt;app&lt;/code&gt; to get the state.&lt;/p&gt;
&lt;p&gt;Fixing it is possible. Split the state into per-handler sub-states and hand each handler only its slice. I counted what that would touch: about 90 files of plumbing that make the code harder to read, not easier. I stopped because I could name every remaining arrow, and at that point the diagram had told me everything it was going to.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Stop when every arrow has a name&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Drive the count down until every arrow that is left has a name and a reason, then stop. The 91 arrows left in my crate are all &quot;this handler reads the app state&quot;, and a diagram that shows them is more honest than one that hides them behind 90 files of indirection.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id=&quot;keeping-it-fixed&quot;&gt;Keeping it fixed&lt;/h3&gt;
&lt;p&gt;Two things stop the graph from drifting back.&lt;/p&gt;
&lt;p&gt;The marketing module has a test that is an allow-list: the exact set of leaf modules it may import. A new reach into the app fails the build. It used to be a deny-list, which only catches the mistakes you already thought of.&lt;/p&gt;
&lt;p&gt;The other is a habit, not a test. Regressions arrive with feature commits, not with refactors. After every push I regenerate the graph and read the list of red arrows that do not point into &lt;code&gt;app&lt;/code&gt;. If the list is empty, the feature stayed in its layer. If it is not, the offending edge is one instruction away from gone, before the next feature builds on it.&lt;/p&gt;
&lt;h3 id=&quot;why-this-beats-refactor-the-codebase-&quot;&gt;Why this beats &quot;refactor the codebase&quot;&lt;/h3&gt;
&lt;p&gt;Two months ago I wrote about &lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev/blog/map-your-codebase-for-ai-agents?utm_source=coderlegion&amp;amp;utm_medium=community&amp;amp;utm_campaign=uml-viewer&amp;amp;utm_content=inline&quot;&gt;mapping this codebase for humans and AI agents&lt;/a&gt;. The lesson then was that a model is good at shape and bad at numbers, so I had to check every count it produced by hand.&lt;/p&gt;
&lt;p&gt;This is the same lesson, applied to refactoring. Here the agent never counts anything; the generator does. The agent gets one rule with one violation and a picture that says pass or fail after every change. It is the same reason a failing test is a better instruction than a paragraph of requirements: the acceptance criterion exists before the work starts, and it is not the agent that judges it.&lt;/p&gt;
&lt;p&gt;What I did not get is the color. CRAP and mutation scores come from Uncle Bob's Clojure tooling, and there is no Rust equivalent wired in yet. The README is clear that a box with no metrics is painted as the worst grade, so the fill on my boxes means nothing until coverage and mutation numbers exist for Rust. The arrows alone were worth the setup.&lt;/p&gt;
&lt;h3 id=&quot;do-it-yourself&quot;&gt;Do it yourself&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Install the Clojure CLI and Java 21 or newer, then clone &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/unclebob/uml-viewer&quot;&gt;uml-viewer&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Write a generator for your language. One class per module, one dependency edge per import, output as EDN with &lt;code&gt;:hierarchical true&lt;/code&gt;. The generated &lt;code&gt;examples/uml-viewer.edn&lt;/code&gt; in the repo is a complete example of the format. In Rust, imports are &lt;code&gt;use&lt;/code&gt; paths; in TypeScript they are &lt;code&gt;import&lt;/code&gt; statements; in Python, &lt;code&gt;import&lt;/code&gt; and &lt;code&gt;from&lt;/code&gt;. A regex over each file is enough to start.&lt;/li&gt;
&lt;li&gt;Write the &lt;code&gt;:levels&lt;/code&gt; list by hand, inner layer first. Describe the architecture you want; the red arrows will show where the code differs.&lt;/li&gt;
&lt;li&gt;Run the viewer against your file. On a fresh start it waits for its companion agent; the &lt;code&gt;--restart&lt;/code&gt; flag restores the last view without spawning one, which is what you want when you drive it from your own agent session.&lt;/li&gt;
&lt;li&gt;Pick one red arrow. Write one instruction that names the two modules and the new home. Regenerate. Repeat.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Key takeaways&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;uml-viewer reads a plain EDN file, not source code. A 200-line script gets any language in.&lt;/li&gt;
&lt;li&gt;The levels list is the only opinion in the input, so write the architecture you want and let the red show the distance.&lt;/li&gt;
&lt;li&gt;Give the agent one rule, one violation and one destination per instruction. The next diagram is the acceptance test.&lt;/li&gt;
&lt;li&gt;Rust will not tell you two modules import each other. Build the graph and count the mutual pairs yourself.&lt;/li&gt;
&lt;li&gt;Stop when every remaining red arrow has a name. Mine are all &quot;handler reads app state&quot;, and that is fine.&lt;/li&gt;
&lt;li&gt;Turn the result into an allow-list test, and regenerate the graph after every feature push, because that is when regressions arrive.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;The six commits are public in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/uptimepage/uptimepage/compare/b791c262...cee12c44&quot;&gt;Uptimepage repository&lt;/a&gt; if you want to read what moved and why.&lt;/p&gt;
&lt;h3 id=&quot;sources&quot;&gt;Sources&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Robert C. Martin, &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/unclebob/uml-viewer&quot;&gt;uml-viewer&lt;/a&gt; on GitHub. The README documents the EDN format, the &lt;code&gt;:levels&lt;/code&gt; rule and the companion agent mailbox.&lt;/li&gt;
&lt;li&gt;Robert C. Martin, &lt;a rel=&quot;nofollow&quot; href=&quot;https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html&quot;&gt;The Clean Architecture&lt;/a&gt;, The Clean Code Blog, August 2012. The Dependency Rule.&lt;/li&gt;
&lt;li&gt;Uptimepage, &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/uptimepage/uptimepage&quot;&gt;source repository&lt;/a&gt;, AGPL. Commits &lt;code&gt;bda59342&lt;/code&gt; through &lt;code&gt;cee12c44&lt;/code&gt; are the six rounds described here.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I build &lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev/?utm_source=coderlegion&amp;amp;utm_medium=community&amp;amp;utm_campaign=uml-viewer&amp;amp;utm_content=cta&quot;&gt;Uptimepage&lt;/a&gt; because I wanted uptime checks that say why something failed instead of &quot;transport error&quot;: HTTP with per-phase timing, TLS and domain expiry, ping and TCP, heartbeats for background jobs, and browser flows, from several regions. It is AGPL-3.0 open source, which is why the six commits above are public and you can check every number in this post against them.&lt;/p&gt;
&lt;p&gt;Now the question for the comments: what draws the module graph for your language, and does it show the cycles or only the layer violations? My rounds three to six only moved the cycle count, and I would have missed that with a tool that colors arrows but never counts pairs.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28116/uncle-bobs-uml-viewer-on-rust-35-dependency-cycles-down-to-3</guid>
<pubDate>Tue, 22 Sep 2026 07:13:36 +0000</pubDate>
</item>
<item>
<title>Real-Time AI Interview Help: Keeping Technical Answers Relevant as Questions Change</title>
<link>https://coderlegion.com/27239/real-time-ai-interview-help-keeping-technical-answers-relevant-as-questions-change</link>
<description>&lt;p&gt;A technical interview rarely follows a straight line. You suggest a solution, the interviewer changes a constraint, and suddenly the most important part of your original answer no longer applies. The challenge is keeping your explanation aligned with the problem as it develops.&lt;/p&gt;
&lt;p&gt;That is the angle I find most interesting about real-time AI interview help. Generating a plausible answer is only one part of the task. An assistant also needs to recognize what changed, preserve the details that still matter, and avoid pulling the conversation back toward an outdated solution.&lt;/p&gt;
&lt;p&gt;I’m Peter, founder of Linkjob AI. In this article, I’ll use a hypothetical backend interview to explain how I think about useful assistance during technical conversations. These are design principles, not performance claims about a particular model.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Start with a concrete problem&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Imagine an interviewer asks you to design a service that accepts uploaded files and processes them in the background. You propose an API, object storage, a job queue, and workers.&lt;/p&gt;
&lt;p&gt;That is a reasonable starting point, but listing components does not explain the decisions behind them. A clearer answer connects each component to a requirement:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Object storage holds the uploaded files.&lt;/li&gt;
&lt;li&gt;The queue separates upload requests from processing work.&lt;/li&gt;
&lt;li&gt;Workers process jobs independently of the request lifecycle.&lt;/li&gt;
&lt;li&gt;A status endpoint lets users check progress.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;An assistant should help the candidate articulate those relationships. Producing a longer list of technologies would add little unless the interviewer has introduced requirements that justify them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Treat a follow-up as a change to the problem&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now the interviewer asks: “What happens if a worker finishes processing a file but crashes before acknowledging the message?”&lt;/p&gt;
&lt;p&gt;The conversation has moved from architecture to failure handling. Repeating the original system diagram would miss the question.&lt;/p&gt;
&lt;p&gt;A useful response should focus on the sequence: processing completed, acknowledgment did not, and the queue may deliver the job again. The candidate can then discuss how to make repeated processing safe.&lt;/p&gt;
&lt;p&gt;A short prompt might be enough:&lt;/p&gt;
&lt;p&gt;“Explain redelivery, identify the operation that must be idempotent, and describe how completion is recorded.”&lt;/p&gt;
&lt;p&gt;That leaves room for the candidate to reason through the actual design. It also avoids claiming that adding a queue automatically solves reliability.&lt;/p&gt;
&lt;p&gt;If the interviewer then asks about two workers receiving the same job concurrently, the assistant should update its guidance again. Checking whether a job is complete is not sufficient if both workers can pass that check before either records completion.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep requirements separate from assumptions&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Technical answers become confusing when assumptions silently turn into facts. In the file-processing example, the interviewer might never have specified file size, processing duration, traffic volume, or whether jobs must finish in order.&lt;/p&gt;
&lt;p&gt;An assistant could fill those gaps with invented numbers and produce an impressive-looking architecture. The candidate would then be defending decisions built on requirements that nobody agreed to.&lt;/p&gt;
&lt;p&gt;A better approach is to identify which unknown changes the design. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Large files may affect upload strategy and storage access.&lt;/li&gt;
&lt;li&gt;Long-running jobs may require attention to visibility timeouts or lease renewal.&lt;/li&gt;
&lt;li&gt;Strict ordering may constrain how work is distributed.&lt;/li&gt;
&lt;li&gt;External side effects may make retries harder to handle safely.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The candidate does not need to ask every possible question. They need to surface the assumptions that materially affect their answer.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Suggest the next reasoning step&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;During a live conversation, a full essay can compete with the interviewer for attention. I would rather see an assistant offer a compact sequence the candidate can develop in their own words.&lt;/p&gt;
&lt;p&gt;For the worker-crash question, that sequence could be:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;State that the job might be delivered again.&lt;/li&gt;
&lt;li&gt;Explain what duplicate execution would do.&lt;/li&gt;
&lt;li&gt;Describe a mechanism for preventing or safely handling duplicate effects.&lt;/li&gt;
&lt;li&gt;Acknowledge the remaining failure case.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The fourth step matters. Suppose the worker calls an external billing service and then crashes before recording success locally. A local “completed” flag alone cannot establish whether the charge happened. The answer needs to consider the external service’s behavior, such as whether it supports idempotency keys.&lt;/p&gt;
&lt;p&gt;This is the kind of distinction that makes technical assistance useful: helping someone identify the next issue they should reason about.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do not manufacture the candidate’s experience&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There is also a difference between explaining a design and claiming to have implemented it. An assistant may be able to describe a distributed job-processing system without any evidence that the candidate has built one.&lt;/p&gt;
&lt;p&gt;If the interviewer asks, “Have you handled this in production?”, the response should reflect the candidate’s actual background. It should not invent a migration, an incident, or a performance improvement.&lt;/p&gt;
&lt;p&gt;A candidate can explain how they would approach an unfamiliar problem while being clear about their experience. Assistance should preserve that distinction, particularly when using a résumé or project notes as context.&lt;/p&gt;
&lt;p&gt;My interest in &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkjob.ai/&quot;&gt;real-time AI interview help&lt;/a&gt; centers on making relevant information easier to use in the moment. That includes recognizing when the available information does not support a claim.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Evaluate how well the assistant follows the conversation&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Testing an interview assistant with isolated questions misses an important part of the experience. A response can be correct for the opening question and irrelevant by the third follow-up.&lt;/p&gt;
&lt;p&gt;For the file-processing example, I would test a sequence that introduces duplicate delivery, concurrent workers, external side effects, and then a simpler traffic requirement that removes the need for some earlier complexity.&lt;/p&gt;
&lt;p&gt;The questions I would ask during evaluation are practical:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does the assistant notice each changed constraint?&lt;/li&gt;
&lt;li&gt;Does it abandon advice that no longer applies?&lt;/li&gt;
&lt;li&gt;Does it distinguish a proposed design from a proven guarantee?&lt;/li&gt;
&lt;li&gt;Can the candidate understand the suggestion while continuing to listen?&lt;/li&gt;
&lt;li&gt;Does the output encourage clarification when necessary?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These checks examine whether the assistant supports an evolving explanation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep the candidate in control&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.linkjob.ai/&quot;&gt;real-time AI interview assistant&lt;/a&gt; should leave room to ignore a suggestion, pause assistance, or request clarification. Interviewers may also have specific rules about external tools, so candidates should establish what assistance is permitted before using it in an assessed conversation.&lt;/p&gt;
&lt;p&gt;The design goal I care about is straightforward: help someone explain their reasoning clearly as the question changes. In a technical interview, that means tracking constraints, discussing failure cases, and knowing which assumptions still need to be checked.&lt;/p&gt;
&lt;p&gt;A useful suggestion gives the candidate something they can understand, evaluate, and explain. That is a more meaningful standard than how much text an assistant can generate.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Disclosure: I’m the founder of Linkjob AI. The product links in this article lead to my company’s website.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/27239/real-time-ai-interview-help-keeping-technical-answers-relevant-as-questions-change</guid>
<pubDate>Tue, 15 Sep 2026 08:22:45 +0000</pubDate>
</item>
<item>
<title>Booting ARM64 Linux in my Rust KVM Hypervisor: Debugging a Memory Issue</title>
<link>https://coderlegion.com/26346/booting-arm64-linux-in-my-rust-kvm-hypervisor-debugging-a-memory-issue</link>
<description>&lt;h3 id=&quot;motivation&quot;&gt;Motivation&lt;/h3&gt;
&lt;p&gt;The time has come: the big test. I got some simple binaries running on my Rust&lt;br&gt;
KVM hypervisor (at this point I am thinking about giving it a proper name). The&lt;br&gt;
next thing has to be Linux. For this series I will show how to actually boot&lt;br&gt;
Linux on my Rust KVM hypervisor on ARM64 CPU (I am using Raspberry Pi 4 which&lt;br&gt;
supports hardware accelerated virtualization).&lt;/p&gt;
&lt;h3 id=&quot;cross-compiling-linux-kernel&quot;&gt;Cross-compiling Linux kernel&lt;/h3&gt;
&lt;p&gt;In one of my &lt;a rel=&quot;nofollow&quot; href=&quot;../kvm-rust-arm-part1&quot;&gt;previous articles&lt;/a&gt; I have described how to&lt;br&gt;
set up the cross-compilation environment with a simple &lt;code&gt;Dockerfile&lt;/code&gt;. We can&lt;br&gt;
reuse it to cross-compile the Linux kernel for ARM64 (after adding some Linux&lt;br&gt;
build dependencies like &lt;code&gt;bc&lt;/code&gt;, &lt;code&gt;flex&lt;/code&gt;, &lt;code&gt;python3&lt;/code&gt;, &lt;code&gt;libssl-dev&lt;/code&gt; and &lt;code&gt;bison&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;docker run --network host -it --rm \
       -e CROSS_COMPILE=aarch64-linux-gnu- -e ARCH=arm64 \
       -v &quot;$(pwd)&quot;:&quot;/home/docker/kvm-rust&quot; -w &quot;/home/docker/kvm-rust/linux-7.1.3&quot; \
       arm-rust-build /bin/bash -c 'make defconfig &amp;amp;&amp;amp; make'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The resulting ARM64 Linux kernel image (&lt;code&gt;Image&lt;/code&gt;) should be located in&lt;br&gt;
&lt;code&gt;arch/arm64/boot/&lt;/code&gt; directory.&lt;/p&gt;
&lt;h3 id=&quot;arm64-boot-protocol&quot;&gt;ARM64 boot protocol&lt;/h3&gt;
&lt;p&gt;The full ARM64 Linux boot protocol is quite straightforward and can be found in&lt;br&gt;
the Linux kernel documentation:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.kernel.org/doc/html/v7.1/arch/arm64/booting.html&quot;&gt;https://www.kernel.org/doc/html/v7.1/arch/arm64/booting.html&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;We simply need to parse the 64-byte header and verify the magic field (it should&lt;br&gt;
equal &lt;code&gt;0x644d5241&lt;/code&gt;). I will just highlight a few important points. We should&lt;br&gt;
read &lt;code&gt;image_size&lt;/code&gt; and &lt;code&gt;text_offset&lt;/code&gt; fields and note that the image should be&lt;br&gt;
loaded at an offset of text_offset bytes  at a 2 MiB boundary. For our kernel,&lt;br&gt;
as I checked, &lt;code&gt;text_offset&lt;/code&gt; is zero and &lt;code&gt;image_size&lt;/code&gt; is the amount of memory the&lt;br&gt;
decompressed kernel image occupies in RAM, which is not necessarily the same as&lt;br&gt;
the file size as reported by &lt;code&gt;stat&lt;/code&gt;. Finally, the registers &lt;code&gt;x1&lt;/code&gt;, &lt;code&gt;x2&lt;/code&gt; and &lt;code&gt;x3&lt;/code&gt;&lt;br&gt;
must be set to zero, while &lt;code&gt;x0&lt;/code&gt; must point to the device tree blob (&lt;code&gt;dtb&lt;/code&gt;) in&lt;br&gt;
the RAM. So, for this I chose to load the &lt;code&gt;dtb&lt;/code&gt; at address &lt;code&gt;0x40000000&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;Image&lt;/code&gt; itself on the first 2 MiB boundary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;const LOAD_ADDR: u64 = 0x40_000_000;
const KERN_OFFS: u64 = 0x200_000;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;obtaining-the-device-tree-blob&quot;&gt;Obtaining the device tree blob&lt;/h4&gt;
&lt;p&gt;So, for ARM64 we do need a device tree blob that will tell the Linux kernel on&lt;br&gt;
what kind of hardware it is running on. And this step is quite easy, we will&lt;br&gt;
can simply reuse our command for running QEMU, with one small addition (i.e.&lt;br&gt;
adding &lt;code&gt;dumpdtb&lt;/code&gt; option to &lt;code&gt;-M virt&lt;/code&gt; argument):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;#!/bin/sh

KERNEL=/home/pi/Image
qemu-system-aarch64                                             \
        -M virt,dumpdtb=virt.dtb                                \
        -smp 1                                                  \
        -enable-kvm                                             \
        -cpu host                                               \
        -kernel ${KERNEL}                                       \
        -append &quot;console=ttyAMA0&quot;                               \
        -serial stdio                                           \
        -nographic                                              \
        -nodefaults
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This gives us a consistent snapshot of the hardware we were running on (in this &lt;br&gt;
case it's emulated hardware for QEMU &lt;code&gt;virt&lt;/code&gt; machine). We could (and we should)&lt;br&gt;
edit and prune it for our own purposes, but let's leave that for another time.&lt;/p&gt;
&lt;h4 id=&quot;implementing-boot-in-rust&quot;&gt;Implementing boot in Rust&lt;/h4&gt;
&lt;p&gt;So, loading the kernel in Rust is quite straightforward:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;impl VM {
    pub fn arch_load_linux(&amp;amp;mut self, vcpu: &amp;amp;mut VCPU, args: &amp;amp;Args) -&amp;gt; io::Result&amp;lt;()&amp;gt; {
        let header = get_linux_header(&amp;amp;args.binary)?;
        let linux_mem_idx = self.add_mem_region((KERN_OFFS + header.image_size) as usize, LOAD_ADDR)?;

        match &amp;amp;args.dtb {
            Some(dtb_path) =&amp;gt; { self.load_file_to_memory(linux_mem_idx, &amp;amp;dtb_path, 0x0)?; },
            None =&amp;gt; { return Err(io::Error::other(&quot;Device tree blob not specified.&quot;)); }
        }

        self.load_file_to_memory(linux_mem_idx, &amp;amp;args.binary, KERN_OFFS)?;

        vcpu.set_one_reg(&quot;x0&quot;, LOAD_ADDR)?;
        vcpu.set_one_reg(&quot;x1&quot;, 0x0)?;
        vcpu.set_one_reg(&quot;x2&quot;, 0x0)?;
        vcpu.set_one_reg(&quot;x3&quot;, 0x0)?;

        vcpu.set_one_reg(&quot;pc&quot;, LOAD_ADDR + KERN_OFFS)?;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that the &lt;code&gt;get_linux_header()&lt;/code&gt; function was omitted here as it only parses&lt;br&gt;
the aforementioned ARM64 Linux image headers in a boilerplate manner.&lt;/p&gt;
&lt;h3 id=&quot;function-not-implemented&quot;&gt;Function not implemented&lt;/h3&gt;
&lt;p&gt;As soon as I tried to run my hypervisor with Linux &lt;code&gt;Image&lt;/code&gt;, I ran into an issue:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;pi@raspberrypi:~ $ ./rust -d virt Image 
x0 = 0x0
pc = 0x1000
magic 0x644d5241, image_size: 38731776, file_size: 38040064, text_offset: 0x0
Loading &quot;virt&quot; to 0x0...
Loading &quot;Image&quot; to 0x200000...
x0 = 0x40000000
pc = 0x40200000
x0 = 0xfffffbfffda35000
pc = 0xffffd902f0f8a42c
Error: Os { code: 38, kind: Unsupported, message: &quot;Function not implemented&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;trying-strace&quot;&gt;Trying strace&lt;/h4&gt;
&lt;p&gt;First thing I turned to was &lt;code&gt;strace&lt;/code&gt;; it's easy to use and gets some nice&lt;br&gt;
information really fast:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ioctl(5, KVM_ARM_SET_DEVICE_ADDR or KVM_GET_ONE_REG, 0x7fcee98768) = 0
x0 = 0x40000000
ioctl(5, KVM_ARM_SET_DEVICE_ADDR or KVM_GET_ONE_REG, 0x7fcee98768) = 0
pc = 0x40200000
ioctl(5, KVM_RUN, 0)                    = -1 ENOSYS (Function not implemented)
ioctl(5, KVM_ARM_SET_DEVICE_ADDR or KVM_GET_ONE_REG, 0x7fcee988a8) = 0
x0 = 0xfffffbfffda35000
ioctl(5, KVM_ARM_SET_DEVICE_ADDR or KVM_GET_ONE_REG, 0x7fcee988a8) = 0
pc = 0xffffd902f0f8a42c
Error: Os { code: 38, kind: Unsupported, message: &quot;Function not implemented&quot; }
+++ exited with 1 +++
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I didn't learn much other than the fact that &lt;code&gt;KVM_RUN&lt;/code&gt; failed completely with&lt;br&gt;
&lt;code&gt;ENOSYS&lt;/code&gt; error code. My first attempt to find out what was going on was&lt;br&gt;
searching the ARM64 KVM source code for &lt;code&gt;ENOSYS&lt;/code&gt; in Linux repository. It didn't&lt;br&gt;
take long, as it appeared only in one place which was a promising lead:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://elixir.bootlin.com/linux/v7.1.3/source/arch/arm64/kvm/mmio.c#L188&quot;&gt;https://elixir.bootlin.com/linux/v7.1.3/source/arch/arm64/kvm/mmio.c#L188&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As we can see, the function in question is &lt;code&gt;io_mem_abort()&lt;/code&gt; in the Linux kernel.&lt;br&gt;
Now, &lt;code&gt;strace&lt;/code&gt; got us that far, but this is a Linux kernel function and not much&lt;br&gt;
we can find out more about it with &lt;code&gt;strace&lt;/code&gt; (which traces only system calls like&lt;br&gt;
&lt;code&gt;ioctl&lt;/code&gt;). I haven't even considered &lt;code&gt;GDB&lt;/code&gt; because this was the host kernel,&lt;br&gt;
and this would require a really complex time-consuming setup. Luckily, there was&lt;br&gt;
a much easier way.&lt;/p&gt;
&lt;h4 id=&quot;analyzing-io-memory-abort-with-ebpf&quot;&gt;Analyzing IO memory abort with eBPF&lt;/h4&gt;
&lt;p&gt;The first thing I did was to list &lt;code&gt;kprobe&lt;/code&gt;s in &lt;code&gt;bpftrace&lt;/code&gt; and see if I can hook&lt;br&gt;
into the &lt;code&gt;io_mem_abort&lt;/code&gt; at all via eBPF:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;pi@raspberrypi:~ $ sudo bpftrace -l 'kprobe:io_mem_abort'
kprobe:io_mem_abort
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was in luck, so I could at least confirm that this function was the one&lt;br&gt;
causing the issue, so I attached a &lt;code&gt;kretprobe&lt;/code&gt; to it, ran &lt;code&gt;bpftrace&lt;/code&gt; alongside&lt;br&gt;
my hypervisor and got the confirmation I needed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;pi@raspberrypi:~ $ sudo bpftrace -e '
&amp;gt;    kretprobe:io_mem_abort {
&amp;gt;        printf(&quot;ret=%d\n&quot;, retval);
&amp;gt;    }'
Attaching 1 probe...
ret=-38
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;finding-out-what-really-happened&quot;&gt;Finding out what really happened&lt;/h4&gt;
&lt;p&gt;Now that I have confirmed &lt;code&gt;io_mem_abort&lt;/code&gt; was the culprit, it was time to&lt;br&gt;
analyze it more deeply. I attached an additional &lt;code&gt;kprobe&lt;/code&gt; so I could print both&lt;br&gt;
the return value and the &lt;code&gt;fault_ipa&lt;/code&gt; argument:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int io_mem_abort(struct kvm_vcpu *vcpu, phys_addr_t fault_ipa)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The updated &lt;code&gt;bpftrace&lt;/code&gt; script printed the fault address.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;pi@raspberrypi:~ $ sudo bpftrace -e '
&amp;gt;    kretprobe:io_mem_abort {
&amp;gt;        printf(&quot;ret=%d\n&quot;, retval);
&amp;gt;    }
&amp;gt;    kprobe:io_mem_abort {
&amp;gt;        printf(&quot;0x%llx\n&quot;, arg1);
&amp;gt;    }'
Attaching 2 probes...
0x47fff000
ret=-38
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So the result surprised me as I initially suspected it was trying to access some&lt;br&gt;
device that I haven't created; however, I realized that this was actually just&lt;br&gt;
beyond the memory I had allocated for the Linux kernel to use. So it clicked: I probably hadn't allocated enough guest memory. Instead of allocating just&lt;br&gt;
&lt;code&gt;header.image_size + KERN_OFFS&lt;/code&gt; I increased it to 1 GiB:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let linux_mem_idx = self.add_mem_region(1024 * 1024 * 1024, LOAD_ADDR)?;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;With that adjustment, the &lt;code&gt;ENOSYS&lt;/code&gt; error disappeared. The guest was clearly&lt;br&gt;
executing code, although I still wasn't seeing any output on the serial console.&lt;br&gt;
That meant the memory issue was solved, but there was at least one more thing&lt;br&gt;
left before we would actually see Linux boot.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26346/booting-arm64-linux-in-my-rust-kvm-hypervisor-debugging-a-memory-issue</guid>
<pubDate>Sun, 06 Sep 2026 18:39:35 +0000</pubDate>
</item>
<item>
<title>Can Rust Make Unsafe AI Agent Actions Unrepresentable?</title>
<link>https://coderlegion.com/26174/can-rust-make-unsafe-ai-agent-actions-unrepresentable</link>
<description>&lt;p&gt;&lt;em&gt;I went looking for a better runtime check and found a different way to think about the problem.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I have spent a lot of time recently thinking about what an AI agent should be allowed to do.&lt;/p&gt;
&lt;p&gt;Reading data is one thing. Writing durable state is another. Sending an email, approving a refund, changing a production configuration, or deleting a record moves farther along the same spectrum.&lt;/p&gt;
&lt;p&gt;The usual answer is to put a guardrail in front of the dangerous operation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.kenwalger.com/blog/wp-content/uploads/2026/09/mermaid-diagram-2026-09-03-134241.png&quot; alt=&quot;Flowchart of a conventional runtime guardrail. An agent proposes an action, which flows through validate action, then check policy, then an allow-or-deny decision. Allow leads to execute; deny leads to stop. The dangerous execute step sits at the end of a chain of checks that every caller must remember to run.&quot;&gt;&lt;/p&gt;
&lt;p&gt;That makes sense, and I have built examples that work exactly this way.&lt;/p&gt;
&lt;p&gt;Then I started looking at how I might implement the same kind of boundary in Rust.&lt;/p&gt;
&lt;p&gt;Rust asked a more interesting question:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why does the dangerous function accept an unchecked action in the first place?&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Runtime Checks Are Still Checks&lt;/h2&gt;
&lt;p&gt;The complete runnable example is &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/kenwalger/unrepresentable&quot;&gt;on GitHub&lt;/a&gt; if you want to make the compiler angry yourself.&lt;/p&gt;
&lt;p&gt;Consider a simplified AI agent that can write information into durable memory.&lt;/p&gt;
&lt;p&gt;The agent proposes a write:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;struct ProposedWrite {
    key: String,
    value: String,
    authority: String,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Somewhere else in the application we have a function that persists it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn persist(write: ProposedWrite) {
    // write to durable storage
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Obviously we should validate the write first.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn validate(write: &amp;amp;ProposedWrite) -&amp;gt; bool {
    // validate provenance
    // evaluate authority
    // apply policy
    true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then our application does this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;if validate(&amp;amp;write) {
    persist(write);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Perfectly reasonable.&lt;/p&gt;
&lt;p&gt;It also means &lt;code&gt;persist()&lt;/code&gt; will happily accept a &lt;code&gt;ProposedWrite&lt;/code&gt; that has never been validated. We are relying on every caller to remember the protocol.&lt;/p&gt;
&lt;p&gt;That is not necessarily a problem in a small example. In a large agentic system with multiple tools, services, developers, and execution paths, it becomes a much more interesting assumption.&lt;/p&gt;
&lt;p&gt;What happens when somebody adds this six months later?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;persist(write);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The compiler sees nothing wrong. The type is correct. The application is wrong.&lt;/p&gt;
&lt;h2&gt;Make the State Part of the Type&lt;/h2&gt;
&lt;p&gt;Rust gives us another option. Instead of treating &quot;proposed&quot; and &quot;approved&quot; as metadata attached to the same object, we can make them different types.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;struct ProposedWrite {
    key: String,
    value: String,
    authority: String,
}

struct AdmittedWrite {
    key: String,
    value: String,
    authority: Authority,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now persistence accepts only the second one:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn persist(write: AdmittedWrite) {
    // write to durable storage
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This seemingly small change alters the boundary. An agent can produce a &lt;code&gt;ProposedWrite&lt;/code&gt;. It cannot produce an &lt;code&gt;AdmittedWrite&lt;/code&gt; directly, provided we control how that type is constructed. Something trusted has to perform the transition.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.kenwalger.com/blog/wp-content/uploads/2026/09/mermaid-diagram-2026-09-03-134220.png&quot; alt=&quot;Flowchart of the custody transition. A ProposedWrite enters an evaluate decision point. If the authority is valid, it becomes an AdmittedWrite, shown in the trusted deep-green state. If the authority is invalid, it becomes Rejected, shown in red. Evaluate is the only path from proposed to admitted.&quot;&gt;&lt;/p&gt;
&lt;p&gt;The persistence layer no longer asks:&lt;/p&gt;
&lt;blockquote&gt;&lt;div&gt;
  Has somebody remembered to validate this?
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;Its API says:&lt;/p&gt;
&lt;blockquote&gt;&lt;div&gt;
  Give me something that has already crossed the admission boundary.
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;That is a much stronger contract.&lt;/p&gt;
&lt;p&gt;This has a name in Rust circles: the &lt;a rel=&quot;nofollow&quot; href=&quot;https://cliffle.com/blog/rust-typestate/&quot;&gt;typestate pattern&lt;/a&gt;, encoding a value's state in its type so that only valid transitions type-check. It is the type-level cousin of a principle Alexis King named &quot;&lt;a rel=&quot;nofollow&quot; href=&quot;https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/&quot;&gt;parse, don't validate&lt;/a&gt;&quot;. Instead of checking a value and handing the same type onward, hoping every later caller re-checks, you transform it into a new type whose very existence proves the check already happened. The check is not something you remember to run. It is something the type system will not let you skip.&lt;/p&gt;
&lt;h2&gt;The Compiler Becomes Part of the Boundary&lt;/h2&gt;
&lt;p&gt;The phrase &quot;provided we control how that type is constructed&quot; is doing all the work in the previous section, so let us actually deliver that control. This is where the earlier version of this article was too loose, and where Rust rewards precision.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;mod custody {
    use super::ProposedWrite;
    // Authority, Rejection, and validate_authority are defined in this module.

    pub struct AdmittedWrite {
        key: String,
        value: String,
        authority: Authority,
    }

    impl AdmittedWrite {
        pub fn key(&amp;amp;self) -&amp;gt; &amp;amp;str {
            &amp;amp;self.key
        }
        pub fn value(&amp;amp;self) -&amp;gt; &amp;amp;str {
            &amp;amp;self.value
        }
        pub fn authority(&amp;amp;self) -&amp;gt; &amp;amp;Authority {
            &amp;amp;self.authority
        }
    }

    pub fn evaluate(write: ProposedWrite) -&amp;gt; Result&amp;lt;AdmittedWrite, Rejection&amp;gt; {
        let authority = validate_authority(&amp;amp;write.authority)?;

        Ok(AdmittedWrite {
            key: write.key,
            value: write.value,
            authority,
        })
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important detail is what is not marked &lt;code&gt;pub&lt;/code&gt;. The struct is public, so other modules can name the type and accept it in their signatures. Its fields are private.&lt;/p&gt;
&lt;p&gt;That distinction is the whole boundary. In Rust, a struct literal like &lt;code&gt;custody::AdmittedWrite { key, value, authority }&lt;/code&gt; requires every field to be visible at the construction site. Because the fields are private to the &lt;code&gt;custody&lt;/code&gt; module, no code outside that module can write that literal. And there is no other public constructor. The only way to obtain an &lt;code&gt;AdmittedWrite&lt;/code&gt; from outside is to hand a &lt;code&gt;ProposedWrite&lt;/code&gt; to &lt;code&gt;evaluate&lt;/code&gt; and have it succeed.&lt;/p&gt;
&lt;p&gt;So the persistence layer, which lives outside &lt;code&gt;custody&lt;/code&gt; and reads the data through the accessor methods, cannot be handed a value that skipped evaluation. Not because a reviewer will catch it, but because the code that would skip evaluation does not compile.&lt;/p&gt;
&lt;p&gt;Here is the shortcut a tired developer might reach for six months from now:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let proposed = ProposedWrite {
    key: &quot;refund_policy&quot;.into(),
    value: &quot;Refunds under $100 do not require manager approval.&quot;.into(),
    authority: &quot;policy&quot;.into(),
};

persist(proposed);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And here is what the compiler says about it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;~\Rust_AI_Actions is 📦 v0.1.0 via 🦀 v1.98.1
❯ cargo build
   Compiling unrepresentable v0.1.0 (~\Rust_AI_Actions)
error[E0308]: mismatched types
   --&amp;gt; src\main.rs:128:13
    |
128 |     persist(proposed);
    |     ------- ^^^^^^^^ expected `AdmittedWrite`, found `ProposedWrite`
    |     |
    |     arguments to this function are incorrect
    |
note: function defined here
   --&amp;gt; src\main.rs:95:4
    |
 95 | fn persist(write: AdmittedWrite) {
    |    ^^^^^^^ --------------------

For more information about this error, try `rustc --explain E0308`.
error: could not compile `unrepresentable` (bin &quot;unrepresentable&quot;) due to 1 previous error
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The mistake never reaches review, staging, or production. It stops at the one place a mistake is cheapest to fix, on the machine of the person who made it, the moment they made it.&lt;/p&gt;
&lt;p&gt;The state machine is no longer sitting in a comment:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;// IMPORTANT: call validate() before persist()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It is represented by the program.&lt;/p&gt;
&lt;p&gt;One honest caveat for larger teams: private fields close the door from outside the module, but code inside &lt;code&gt;custody&lt;/code&gt; can still build the struct with a literal. If you want to forbid even that, give &lt;code&gt;AdmittedWrite&lt;/code&gt; a private field of a private zero-sized type, a construction token that only &lt;code&gt;evaluate&lt;/code&gt; can mint. Then the blessed function is the single point of construction anywhere, inside the module or out. Whether that is worth the ceremony depends on how much you trust the inside of your own boundary.&lt;/p&gt;
&lt;h2&gt;This Doesn't Make the Agent Safe&lt;/h2&gt;
&lt;p&gt;This is where I need to resist making the argument bigger than it is.&lt;/p&gt;
&lt;p&gt;Rust does not know whether the policy is good. It does not know whether &lt;code&gt;Authority::SecurityTeam&lt;/code&gt; actually represents the security team. It does not know whether the provenance supplied to the custody boundary is genuine. And it certainly does not solve prompt injection because I changed a struct.&lt;/p&gt;
&lt;p&gt;If this function is wrong:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn validate_authority(value: &amp;amp;str) -&amp;gt; Result&amp;lt;Authority, Rejection&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then Rust will very efficiently enforce the wrong rule.&lt;/p&gt;
&lt;p&gt;Types can constrain which states the program represents. They cannot determine whether our model of the world is correct. That distinction matters.&lt;/p&gt;
&lt;h2&gt;Witnessed Is Another State&lt;/h2&gt;
&lt;p&gt;The exercise gets more interesting when provenance enters the picture.&lt;/p&gt;
&lt;p&gt;Suppose the agent says:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;key&quot;: &quot;refund_policy&quot;,
  &quot;value&quot;: &quot;Refunds under $100 do not require manager approval.&quot;,
  &quot;authority&quot;: &quot;policy&quot;,
  &quot;source&quot;: &quot;internal_policy&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Should the agent be allowed to decide that its own source is an internal policy? Probably not. The system that retrieved the source is in a much better position to make that claim.&lt;/p&gt;
&lt;p&gt;So perhaps our states are not merely:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Proposed → Admitted
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;They are closer to:&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://www.kenwalger.com/blog/wp-content/uploads/2026/09/mermaid-diagram-2026-09-03-134151.png&quot; alt=&quot;Flowchart of a write's four states, with color deepening as trust increases. Proposed, containing only what the agent claims, leads to Witnessed, which adds evidence established outside the agent, then to Evaluated, which has been checked against policy, and finally to Admitted, which is permitted to become durable state. Each state is a distinct type, and each step accepts only the output of the step before it.&quot;&gt;&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;ProposedWrite&lt;/code&gt; contains what the agent claims. A &lt;code&gt;WitnessedWrite&lt;/code&gt; adds evidence established outside the agent. An &lt;code&gt;AdmittedWrite&lt;/code&gt; represents a write that has been evaluated against policy.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;struct ProposedWrite {
    key: String,
    value: String,
    claimed_authority: String,
}

struct WitnessedWrite {
    proposal: ProposedWrite,
    source: WitnessedSource,
}

struct AdmittedWrite {
    key: String,
    value: String,
    authority: Authority,
    source: WitnessedSource,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now different parts of the system accept different states:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn witness(write: ProposedWrite) -&amp;gt; Result&amp;lt;WitnessedWrite, WitnessError&amp;gt; {
    // establish source evidence outside the agent
}

fn evaluate(write: WitnessedWrite) -&amp;gt; Result&amp;lt;AdmittedWrite, Rejection&amp;gt; {
    // apply policy to the witnessed write
}

fn persist(write: AdmittedWrite) {
    // write to durable storage
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each step accepts only the output type of the step before it. There is no signature anywhere that accepts a &lt;code&gt;ProposedWrite&lt;/code&gt; and persists it, so the shortcut is not something you have to remember not to take. It is not expressible. The types are the protocol; nothing else has to announce it.&lt;/p&gt;
&lt;h2&gt;What Rust Changed for Me&lt;/h2&gt;
&lt;p&gt;I started this experiment thinking about how to implement an AI safety boundary in another language. The more interesting lesson was that Rust made me reconsider where the boundary should live.&lt;/p&gt;
&lt;p&gt;In many systems, we encode state like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;status&quot;: &quot;approved&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then every downstream consumer has to inspect &lt;code&gt;status&lt;/code&gt; and behave correctly.&lt;/p&gt;
&lt;p&gt;Rust encourages another question:&lt;/p&gt;
&lt;blockquote&gt;&lt;div&gt;
  If these states permit fundamentally different operations, why are they represented by the same type?
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;That question matters for AI agents because agentic systems cross consequential boundaries constantly.&lt;/p&gt;
&lt;p&gt;A model proposes a tool call. A runtime authorizes it. A tool executes it. A result becomes memory. Memory later becomes context. Context influences another action.&lt;/p&gt;
&lt;p&gt;At each transition, we can either carry another flag saying what happened, or change what the next component is capable of accepting. Those are not equivalent designs.&lt;/p&gt;
&lt;h2&gt;Invalid States Versus Invalid Reality&lt;/h2&gt;
&lt;p&gt;There is an important limit here.&lt;/p&gt;
&lt;p&gt;Suppose a write was legitimately admitted yesterday because Alice had authority to approve it. Alice leaves the company today. The &lt;code&gt;AdmittedWrite&lt;/code&gt; type does not magically expire. Likewise, a policy can be superseded, a credential revoked, or evidence later discovered to be wrong.&lt;/p&gt;
&lt;p&gt;The compiler can enforce:&lt;/p&gt;
&lt;blockquote&gt;&lt;div&gt;
  This value crossed the required transition.
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;It cannot establish:&lt;/p&gt;
&lt;blockquote&gt;&lt;div&gt;
  Everything that justified that transition remains true forever.
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;That still requires runtime governance, lifecycle management, revocation, and revalidation.&lt;/p&gt;
&lt;p&gt;So I would not claim Rust makes unsafe AI actions impossible. What it can do is make certain classes of &lt;strong&gt;architecturally invalid transitions&lt;/strong&gt; harder to express accidentally.&lt;/p&gt;
&lt;p&gt;That is narrower. It is also much more believable.&lt;/p&gt;
&lt;h2&gt;Could I Do This in Another Language?&lt;/h2&gt;
&lt;p&gt;Of course.&lt;/p&gt;
&lt;p&gt;You can model state transitions in Go, Java, TypeScript, Python, C#, or plenty of other languages. You can build wrapper types, sealed classes, discriminated unions, private constructors, capability objects, and carefully designed APIs. Rust does not own the idea, and the typestate pattern predates it.&lt;/p&gt;
&lt;p&gt;What I found useful is that Rust keeps pushing the design conversation in this direction. Ownership asks who controls a value. Visibility asks who can construct it. The type system asks what operations are valid for it. &lt;code&gt;Result&lt;/code&gt; makes failure part of the function signature.&lt;/p&gt;
&lt;p&gt;None of those concepts exists specifically for AI safety. Together, though, they provide an unusually direct vocabulary for designing agent boundaries, and the defaults nudge you toward making the boundary structural instead of remembered.&lt;/p&gt;
&lt;h2&gt;The Bigger Lesson&lt;/h2&gt;
&lt;p&gt;A lot of AI safety architecture is necessarily dynamic. Policies change. Users have different authority. Tools expose different capabilities. Context changes what an action means. We are never going to compile all of that uncertainty away.&lt;/p&gt;
&lt;p&gt;But not every invariant is dynamic.&lt;/p&gt;
&lt;p&gt;If an unwitnessed write must never be persisted, perhaps &lt;code&gt;persist()&lt;/code&gt; should not accept unwitnessed writes. If an unevaluated tool call must never execute, perhaps &lt;code&gt;execute()&lt;/code&gt; should not accept unevaluated tool calls. If a rejected action must never cross a boundary, perhaps rejection should produce a state for which crossing that boundary is not an available operation.&lt;/p&gt;
&lt;p&gt;That is what Rust changed in how I think about this problem.&lt;/p&gt;
&lt;p&gt;I started by asking:&lt;/p&gt;
&lt;blockquote&gt;&lt;div&gt;
  How do I check that the agent is allowed to do this?
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;Rust made me ask:&lt;/p&gt;
&lt;blockquote&gt;&lt;div&gt;
  Why does this function accept something the agent is not allowed to do?
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;That is a much better question.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26174/can-rust-make-unsafe-ai-agent-actions-unrepresentable</guid>
<pubDate>Sun, 06 Sep 2026 14:56:00 +0000</pubDate>
</item>
<item>
<title>How to Create and Distribute Rust Packages with Cargo</title>
<link>https://coderlegion.com/26031/how-to-create-and-distribute-rust-packages-with-cargo</link>
<description>&lt;p&gt;Hey everyone! I put together a quick guide on building and releasing Rust crates using Cargo.&lt;/p&gt;
&lt;p&gt;Highlights covered:&lt;/p&gt;
&lt;p&gt;Distinguishing between binary crates (src/main.rs) and library crates (src/lib.rs).&lt;/p&gt;
&lt;p&gt;Manifest management in Cargo.toml.&lt;/p&gt;
&lt;p&gt;Pre-release checklist: testing, docs, and authentication on crates.io.&lt;/p&gt;
&lt;p&gt;Read the complete article on SunnyWriteUps. Feel free to share your thoughts or tips on your Rust package workflow!&lt;/p&gt;
&lt;p&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://sunnyblog.apexmansunny.workers.dev/posts/how-to-create-your-rust-package/&quot;&gt;https://sunnyblog.apexmansunny.workers.dev/posts/how-to-create-your-rust-package/&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26031/how-to-create-and-distribute-rust-packages-with-cargo</guid>
<pubDate>Tue, 01 Sep 2026 18:21:40 +0000</pubDate>
</item>
<item>
<title>When WebAssembly Is Worth The Build Step In A Web Game</title>
<link>https://coderlegion.com/25912/when-webassembly-is-worth-the-build-step-in-a-web-game</link>
<description>&lt;p&gt;Most of the wasm material floating around is about the toolchain: install Emscripten, pass these flags, ship the .wasm. The decision that comes before it gets far less attention, and for this crowd it is the more interesting one, so here is the version I would have wanted to read first.&lt;/p&gt;
&lt;h3 id=&quot;what-a-module-actually-is&quot;&gt;What A Module Actually Is&lt;/h3&gt;
&lt;p&gt;WebAssembly is a binary instruction format, not a language you write. You compile C, C++ or Rust into a .wasm file, the browser validates it and turns it into real machine code, and that compile step is where the speed comes from. There is no warm-up period while a JIT works out which functions are hot, so a wasm function runs at full speed from its first call.&lt;/p&gt;
&lt;p&gt;What surprises people is how little a module can do on its own. It has no DOM, no canvas, no network, no audio. It gets a block of linear memory, a set of exported functions JavaScript can call, and a set of imported functions JavaScript hands to it. Everything it does to the outside world crosses that boundary. Sandbox with a bridge is the mental model, and nearly every wasm performance surprise traces back to it.&lt;/p&gt;
&lt;h3 id=&quot;the-boundary-is-the-whole-design&quot;&gt;The Boundary Is The Whole Design&lt;/h3&gt;
&lt;p&gt;Calling into a wasm function is fast. It is not free. Cross the boundary a few thousand times a frame with tiny calls and you can spend the entire advantage you compiled for.&lt;/p&gt;
&lt;p&gt;The pattern that works is a coarse boundary. Pass data in batches, do as much work as possible inside the module before returning, and share state through linear memory instead of copying values back and forth. Rather than asking the module for each entity's position one call at a time, let it write every position into a shared buffer and read the whole buffer once on the JavaScript side.&lt;/p&gt;
&lt;p&gt;This is also why a wasm rewrite of ordinary game logic often benchmarks slower than the JavaScript it replaced. The logic got faster and the plumbing got more expensive, and the plumbing won.&lt;/p&gt;
&lt;h3 id=&quot;two-toolchains-cover-almost-everything&quot;&gt;Two Toolchains Cover Almost Everything&lt;/h3&gt;
&lt;p&gt;For C and C++ it is Emscripten, which does much more than emit a binary. It ships a C library, maps OpenGL onto WebGL, simulates a filesystem, and generates the glue that loads the module and wires it to the page. Point it at an existing SDL or OpenGL game and with the right flags you get something playable without rewriting the source.&lt;/p&gt;
&lt;p&gt;For Rust the support lives in the language ecosystem. wasm32-unknown-unknown is a first-class target, wasm-bindgen generates bindings with real types instead of raw numbers, and macroquad or Bevy sit on top depending on how ambitious the project is. Rust is what most people reach for when they are starting fresh rather than porting.&lt;/p&gt;
&lt;p&gt;C#, Go and AssemblyScript all target wasm too, but for games those two paths have the deepest ecosystems and the most shipped titles behind them.&lt;/p&gt;
&lt;h3 id=&quot;your-engine-already-made-this-choice&quot;&gt;Your Engine Already Made This Choice&lt;/h3&gt;
&lt;p&gt;If you export from Unity or Godot, you are already shipping wasm. Both compile their C++ runtime to a module and drive a WebGL canvas from it, which explains the complaint everyone has: a trivial Unity web build is many megabytes because the engine runtime is the download, not your game. The load time you see is the browser compiling that binary, and streaming compilation is what hides most of it behind the download.&lt;/p&gt;
&lt;p&gt;We put the longer version together on our own site, &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.abratabia.com/webassembly/&quot;&gt;WebAssembly for web games&lt;/a&gt;, which goes through the toolchains, how each engine handles its web export, and the things wasm still cannot do without JavaScript holding its hand.&lt;/p&gt;
&lt;p&gt;Reach for wasm when you have a hot loop running thousands of times per frame, a large existing native codebase, or a workload like physics or a software rasterizer where fixed types and explicit memory genuinely pay. Skip it when the honest answer is that your game logic runs fine in JavaScript. A build step you did not need is a real cost, and the speedup you imagined is often a rounding error.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25912/when-webassembly-is-worth-the-build-step-in-a-web-game</guid>
<pubDate>Mon, 31 Aug 2026 11:53:33 +0000</pubDate>
</item>
<item>
<title>Why GitWand is Rust, not Electron: what native performance actually buys a Git client</title>
<link>https://coderlegion.com/25097/why-gitwand-is-rust-not-electron-what-native-performance-actually-buys-a-git-client</link>
<description>&lt;p&gt;It's been a while since the last post here (v3.5, July 10). Since then, three releases have shipped: v3.6.0 (a handful of workflow polish items), v3.6.6 (a pure dev-loop and CI performance pass, no product surface), and v3.7.0 (Commit Review, a local AI pass over your staged changes). A straight changelog recap would cover all three, but it would miss the thing that actually connects them.&lt;/p&gt;
&lt;p&gt;Every one of those releases is a direct consequence of a decision made on day one: build on Tauri 2 and Rust instead of Electron, and keep the actual conflict-resolution logic in a deliberately portable, zero-dependency TypeScript package instead of folding it into the Rust backend. That split sounds like an implementation detail. It isn't. It's the reason a 5-repo Launchpad refresh takes 30ms instead of most of a second, the reason a 30-second timeout bug fixed a few days ago (#166) had a one-line fix instead of a rewrite, and the reason the new Commit Review feature never has to touch a GitWand server. This post is about that decision, with real numbers pulled straight from the codebase instead of the usual &quot;native is faster, trust us.&quot;&lt;/p&gt;
&lt;h3 id=&quot;the-bet-and-who-else-made-it&quot;&gt;The bet, and who else made it&lt;/h3&gt;
&lt;p&gt;GitWand ships as roughly an 8 MB installed binary. GitKraken, built on Electron, ships in the ~150 MB class: a full bundled Chromium plus a full bundled Node.js runtime, running alongside each other, for an app that mostly renders text and spawns &lt;code&gt;git&lt;/code&gt;. GitHub Desktop is the same story. The gap shows up at startup (GitWand is sub-1 second) and it shows up on large repos, where an Electron app's single JS heap has to hold and garbage-collect everything the UI touches.&lt;/p&gt;
&lt;p&gt;To be fair to the field: GitWand isn't the only one making this bet anymore. GitButler is Tauri + Rust too. GitComet skips the webview entirely and goes pure Rust with GPUI. Strand is Tauri + React. Being native, in 2026, is closer to table stakes for a new Git client than a differentiator; RelaGit is the interesting counter-example, choosing Electron even with a SolidJS frontend for its fine-grained reactivity. The real question isn't &quot;who avoided Electron,&quot; it's what each of these tools actually does with the native layer once it has it. That's the part worth walking through.&lt;/p&gt;
&lt;h3 id=&quot;what-rust-buys-fearless-concurrency-with-a-number-attached&quot;&gt;What Rust buys: fearless concurrency, with a number attached&lt;/h3&gt;
&lt;p&gt;Rust's ownership model turns a whole class of concurrency bugs into compile errors instead of production incidents. That's a nice sentence to put in a pitch deck. Here's what it looks like in a file that ships in the app today.&lt;/p&gt;
&lt;p&gt;GitWand's multi-repo dashboard (Launchpad) needs the status of every repo in your workspace: branch, ahead/behind, modified-file count. The naive version spawns four &lt;code&gt;git&lt;/code&gt; subprocesses per repo, one after another. GitWand's Rust backend does two things instead: each repo's status comes from a single in-process &lt;code&gt;libgit2&lt;/code&gt; call rather than four subprocess spawns, and every repo in the workspace gets processed in parallel via &lt;code&gt;rayon&lt;/code&gt;'s &lt;code&gt;into_par_iter()&lt;/code&gt;, one core per repo instead of one core doing all of them in sequence. The comment sitting next to that code in the source states the measured result plainly: a 5-repo workspace listing went from about 750ms wall-clock to about 30ms in the typical case. That's not a marketing number, it's a comment next to a &lt;code&gt;#[tauri::command]&lt;/code&gt; that ships today.&lt;/p&gt;
&lt;p&gt;The same pattern shows up all over the backend, because once the tool exists, using it costs nothing extra: PR &lt;code&gt;+/-&lt;/code&gt; stat enrichment across GitHub, GitLab, Azure DevOps, and Bitbucket all fan out per-PR diff computation across cores instead of looping; submodule status checks across a repo's submodules run in parallel; per-branch top-author lookups for the branch picker do too. A few days ago, a regression brought back a 30-second &lt;code&gt;gh_list_prs&lt;/code&gt; timeout: the per-PR stats loop had gone back to running one &lt;code&gt;git diff --numstat&lt;/code&gt; at a time. The fix wasn't a rewrite or a new thread pool, it was swapping a &lt;code&gt;for&lt;/code&gt; loop for &lt;code&gt;.par_iter_mut()&lt;/code&gt;. None of these needed hand-rolled synchronization or a bespoke worker pool. The parallel version and the serial version are the same number of lines, and if two of those parallel closures ever raced on the same mutable data, that's a compile error, not a bug report from a user with a corrupted status badge.&lt;/p&gt;
&lt;h3 id=&quot;what-rust-doesn-t-need-to-buy-the-conflict-engine-stays-typescript-on-purpose&quot;&gt;What Rust doesn't need to buy: the conflict engine stays TypeScript, on purpose&lt;/h3&gt;
&lt;p&gt;Here's the part a &quot;Rust is fast&quot; pitch usually skips: the actual pattern-classification engine, the code that decides &lt;code&gt;same_change&lt;/code&gt; versus &lt;code&gt;one_side_change&lt;/code&gt; versus &lt;code&gt;complex&lt;/code&gt; and merges JSON, YAML, and lockfiles key by key, is not written in Rust. It lives in &lt;code&gt;@gitwand/core&lt;/code&gt;, and that package is deliberately zero-Node, browser-safe TypeScript.&lt;/p&gt;
&lt;p&gt;That's not a compromise, it's a second, equally deliberate architectural line. Classifying a few hundred lines of diff text against nine pattern rules finishes in single-digit milliseconds no matter what language it's written in; Rust's raw throughput wouldn't move that number in any way a user could feel. What Rust &lt;em&gt;would&lt;/em&gt; have cost is portability: because the engine has no Node dependency, the exact same code runs inside the desktop app, in the CLI (&lt;code&gt;npx @gitwand/cli resolve&lt;/code&gt;), inside the MCP server that AI agents call, and could run directly in a browser tomorrow with no changes. The rule GitWand actually follows isn't &quot;everything is Rust.&quot; It's narrower and, we think, more useful: Rust owns the boundary with the operating system (spawning &lt;code&gt;git&lt;/code&gt;, walking the filesystem, talking to forge APIs, the whole app shell), and the deterministic algorithmic core stays a small, dependency-light, portable package that isn't tied to any one runtime.&lt;/p&gt;
&lt;h3 id=&quot;the-unglamorous-release-v3-6-6&quot;&gt;The unglamorous release: v3.6.6&lt;/h3&gt;
&lt;p&gt;v3.6.6 shipped no new product surface at all, just a dev-loop and CI performance pass, and it's the release that best shows the bet compounding on itself. Because the entire backend is one Rust crate, the same profiling discipline used to make the app fast also applies to making the &lt;em&gt;build&lt;/em&gt; fast: a &lt;code&gt;[profile.dev]&lt;/code&gt; tuned for lighter debug info cut a post-edit &lt;code&gt;cargo build&lt;/code&gt; from about 21 seconds to about 6.5. On the CI side, a &lt;code&gt;desktop&lt;/code&gt; job that produced three fully-signed release bundles on every single push to &lt;code&gt;main&lt;/code&gt;, bundles nobody downloaded, was replaced with a fast &lt;code&gt;rust-check&lt;/code&gt; job (fmt, clippy, check, test) that finally runs the app's 217 Rust unit tests in CI for the first time, saving roughly 230 billed CI minutes per push. On the TypeScript side, Vitest's global &lt;code&gt;jsdom&lt;/code&gt; environment (needed by maybe a fifth of the test files, paid by all of them) was scoped down per-file, cutting environment setup time from about 96 seconds to about 17 across the suite.&lt;/p&gt;
&lt;p&gt;Worth mentioning: before touching any of it, every claim in the plan for this work got re-measured against the actual codebase, not assumed. Several turned out to be wrong. &quot;Any edit recompiles and relinks everything&quot; wasn't true, &lt;code&gt;cargo check&lt;/code&gt; after a real edit came back in under 2 seconds; the real cost was in the codegen-and-link step of a full &lt;code&gt;cargo build&lt;/code&gt;. &quot;The old &lt;code&gt;jsdom&lt;/code&gt; setup costs 100 to 300ms per file&quot; was an underestimate by roughly 10x once actually profiled. That's the same discipline the conflict engine's confidence scores are built on: don't ship a number you haven't checked.&lt;/p&gt;
&lt;h3 id=&quot;commit-review-and-why-local-first-is-the-real-differentiator&quot;&gt;Commit Review, and why local-first is the real differentiator&lt;/h3&gt;
&lt;p&gt;v3.7.0's headline feature, Commit Review, runs an AI pass over your staged changes right in the Changes panel before you commit, with inline findings and a &quot;Fix with agent&quot; action that hands them to a terminal AI session (Claude Code, opencode, or Codex) already running against your repo. What matters about it architecturally is what it &lt;em&gt;doesn't&lt;/em&gt; do: it never calls a GitWand-run server. The review runs against whichever AI CLI you already have installed, on your machine, over your own already-open agent session.&lt;/p&gt;
&lt;p&gt;That's the same &quot;zero mandatory cloud&quot; value the conflict engine already embodies, applied to review instead of merge. It's also the sharper contrast with where a chunk of the market is headed: tools positioning code-intelligence features as a metered cloud API (Greptile's own &quot;Genius API&quot; is priced at $0.45 per request) are betting that developers will pay per call for something GitWand ships as a local, free, opt-in feature built on tools you already run. Being local-first for conflict resolution and local-first for review isn't two separate decisions, it's the same one, applied twice.&lt;/p&gt;
&lt;h3 id=&quot;the-small-stuff-v3-6-0&quot;&gt;The small stuff: v3.6.0&lt;/h3&gt;
&lt;p&gt;Between the two, v3.6.0 shipped things that don't need an architecture section to justify: a one-click &quot;Update branch&quot; prompt when you check out something behind its upstream, and a non-blocking handoff when an interactive rebase hits a conflict (issue #128), so the conflict editor no longer traps you behind a modal. These are the kind of small interactions that only feel good because there's no Electron process and no garbage-collector pause standing between the click and the result. The architecture doesn't just enable the big numbers, it's also why the boring stuff feels instant.&lt;/p&gt;
&lt;h3 id=&quot;where-the-bet-goes-next&quot;&gt;Where the bet goes next&lt;/h3&gt;
&lt;p&gt;The roadmap's next stop, v3.9.0 (Live Repo), applies the same philosophy one layer up: a Rust &lt;code&gt;notify&lt;/code&gt; crate watcher replaces the current 2-second status poll with real filesystem events, more read paths move to in-process &lt;code&gt;libgit2&lt;/code&gt;, and separately, the portable TypeScript core's diff/parse hot path gets moved off the main thread into a Web Worker. Same rule, two languages: Rust handles OS-level events natively and cheaply, and the portable core gets pushed off the render thread the way any JavaScript does it. One architecture, one non-negotiable: the UI thread never waits on either of them.&lt;/p&gt;
&lt;h3 id=&quot;the-market-read&quot;&gt;The market read&lt;/h3&gt;
&lt;p&gt;The Git GUI space is splitting into two camps: Electron incumbents charging a subscription for cloud AI features (GitKraken at $8/mo, with its advanced and AI features gated behind an account), and a newer wave of native, mostly free, often open-source tools built on Rust or a comparable native stack (GitButler, GitComet, Strand, and GitWand, with RelaGit staying on Electron by choice). Being native is fast becoming the price of entry for that second group, not what sets any one of them apart. What actually differentiates GitWand inside it is what the native layer gets used for: a conflict-resolution engine that classifies instead of guesses, multi-repo workflows that get genuinely (and measurably) faster because of how the backend is built, and an AI layer that never assumes your code should leave your machine.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Download GitWand for macOS, Linux, or Windows from &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/devlint/GitWand/releases&quot;&gt;GitHub Releases&lt;/a&gt;, read the &lt;a rel=&quot;nofollow&quot; href=&quot;/changelog&quot;&gt;full changelog&lt;/a&gt;, or see how the numbers stack up against specific tools on the &lt;a rel=&quot;nofollow&quot; href=&quot;/compare/&quot;&gt;compare pages&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/25097/why-gitwand-is-rust-not-electron-what-native-performance-actually-buys-a-git-client</guid>
<pubDate>Fri, 21 Aug 2026 15:52:00 +0000</pubDate>
</item>
<item>
<title>Bun Rust Rewrite: 64 Claude Agents, 535k Lines of Zig</title>
<link>https://coderlegion.com/23937/bun-rust-rewrite-64-claude-agents-535k-lines-of-zig</link>
<description>&lt;p&gt;A couple of weeks ago, in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/pnpm-v12-rust-rewrite&quot;&gt;post about pnpm rewriting its install engine in Rust&lt;/a&gt;, I called Bun &quot;the odd one out: it's written in Zig, not Rust.&quot; That line is wrong, and the awkward part is that it was wrong the week I wrote it. Jarred Sumner announced Bun's Zig-to-Rust rewrite on July 8; the pnpm post went up July 15, so the claim was stale at publication. I've since corrected that post so it acknowledges the rewrite and links here, which means if you click through you won't find the quoted line anymore. &lt;a rel=&quot;nofollow&quot; href=&quot;https://bun.sh&quot;&gt;Bun&lt;/a&gt; is rewriting itself from Zig to Rust, and the rewrite is in canary for Bun 1.4, the first release where the runtime, bundler, and test runner are all Rust. &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/jarredsumner&quot;&gt;Jarred Sumner&lt;/a&gt; didn't do it by hand. He ran about 50 Claude Code workflows over 11 days, a few at a time, and let them loose on 535,496 lines of Zig.&lt;/p&gt;
&lt;p&gt;This is the part of the JavaScript toolchain story I want to get right, because it's the one most likely to be flattened into a press release. The headline is real: Bun 1.4 fixes 128 bugs, ships a roughly 20% smaller binary, and runs a bit faster. The headline is also doing some work the underlying numbers don't fully support, and there is a genuine, public argument between Sumner and &lt;a rel=&quot;nofollow&quot; href=&quot;https://andrewkelley.me&quot;&gt;Andrew Kelley&lt;/a&gt;, the creator of Zig, over what actually happened. Both of them are partly right.&lt;/p&gt;
&lt;h3 id=&quot;tl-dr&quot;&gt;TL;DR&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Bun ported its entire runtime from Zig to Rust in 11 days with 64 Claude agents, and the rewrite is in canary for Bun 1.4, not stable.&lt;/li&gt;
&lt;li&gt;The numbers that hold up are the leak fixes. An in-process build loop that ballooned to 6.7 GB in 1.3.14 levels off at 609 MB in 1.4, and the use-after-free and double-free bugs that dogged the Zig runtime are the class of failure safe Rust's &lt;code&gt;Drop&lt;/code&gt; and borrow checker turn into compile errors.&lt;/li&gt;
&lt;li&gt;What the announcement overclaims is the framing. The 2 to 5% speed gain and the roughly 20% smaller binary come from cross-language LTO, ICU trimming, and linker work that could have shipped in Zig, not from Rust as a language.&lt;/li&gt;
&lt;li&gt;Don't move to 1.4 in production yet. Stable is still v1.3.14; run &lt;code&gt;bun upgrade --canary&lt;/code&gt; on a throwaway branch, hammer the 2,000-build leak scenario against whatever you ship, and watch memory hold flat before you trust it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;what-the-bun-rust-rewrite-actually-is&quot;&gt;What the Bun Rust rewrite actually is&lt;/h3&gt;
&lt;p&gt;Bun 1.4 is a mechanical port of the whole runtime from Zig to Rust, done one &lt;code&gt;.zig&lt;/code&gt; file at a time, holding behavior as close to 1.3.14 as the port allows. The last Zig release is &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/oven-sh/bun/releases/tag/bun-v1.3.14&quot;&gt;Bun v1.3.14&lt;/a&gt;, tagged May 13, 2026. The first Rust release is v1.4.0, currently &lt;a rel=&quot;nofollow&quot; href=&quot;https://bun.com/blog/bun-in-rust&quot;&gt;in canary&lt;/a&gt;: run &lt;code&gt;bun upgrade --canary&lt;/code&gt; to pull it.&lt;/p&gt;
&lt;p&gt;&quot;Mechanical port&quot; is the load-bearing phrase. This is not a greenfield rewrite where Claude gets to redesign Bun. The Zig and Rust shown side by side in Sumner's &lt;a rel=&quot;nofollow&quot; href=&quot;https://bun.com/blog/bun-in-rust&quot;&gt;announcement&lt;/a&gt; are deliberately near-identical: same function names, same scoping, same comments. The plan is to refactor toward idiomatic Rust &lt;em&gt;after&lt;/em&gt; 1.4 ships, not during the port. That choice is what makes an 11-day timeline plausible, and it's what keeps the existing TypeScript test suite meaningful as the source of truth. The tests were never tied to the implementation language, so a faithful port should pass the same assertions.&lt;/p&gt;
&lt;p&gt;The scope is everything Bun is: the JavaScript, TypeScript, and CSS transpiler, minifier, and bundler; the npm-compatible package manager; the Jest-like test runner; the Node.js API surface (&lt;code&gt;fs&lt;/code&gt;, &lt;code&gt;net&lt;/code&gt;, &lt;code&gt;tls&lt;/code&gt;, &lt;code&gt;http&lt;/code&gt;, &lt;code&gt;http2&lt;/code&gt;, &lt;code&gt;node:zlib&lt;/code&gt;); and the HTTP server. About 780,000 lines of Rust now sit where 535,496 lines of Zig used to. The &quot;million lines&quot; Kelley keeps citing is the diff: the announcement counts the port as adding a million-plus lines of new Rust. The 780,000 is the resulting codebase, the denominator for the unsafe-percentage figure. Both numbers are right; one is what went in, the other is what's there.&lt;/p&gt;
&lt;h3 id=&quot;why-rewrite-bun-from-zig-to-rust-&quot;&gt;Why rewrite Bun from Zig to Rust?&lt;/h3&gt;
&lt;p&gt;The honest case isn't speed. It's stability. Sumner opens the &lt;a rel=&quot;nofollow&quot; href=&quot;https://bun.com/blog/bun-in-rust&quot;&gt;announcement&lt;/a&gt; with a list of bugs fixed in 1.3.14, bugs the Zig runtime shipped until that release, and it's grim reading for a runtime people run in production:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;heap-use-after-free in &lt;code&gt;node:zlib&lt;/code&gt; when &lt;code&gt;.reset()&lt;/code&gt; fires during an async &lt;code&gt;.write()&lt;/code&gt; still in flight on the threadpool&lt;/li&gt;
&lt;li&gt;use-after-free in &lt;code&gt;node:http2&lt;/code&gt; when a reentrant JS callback triggers a hashmap rehash that invalidates internal stream pointers&lt;/li&gt;
&lt;li&gt;use-after-free in &lt;code&gt;UDPSocket.send()&lt;/code&gt; where a &lt;code&gt;valueOf()&lt;/code&gt; callback detaches the &lt;code&gt;ArrayBuffer&lt;/code&gt; between payload capture and the actual send&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;tlsSocket.setSession()&lt;/code&gt; leak of about 6.5 KB per call, every call, from a missing &lt;code&gt;SSL_SESSION_free&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fs.watch()&lt;/code&gt; watchers never collected after &lt;code&gt;.close()&lt;/code&gt;, pinned as GC roots by a reference-count underflow&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are the canonical memory-safety failures: use-after-free, double-free, leaks on error paths. In safe Rust most of them become compile errors, and the rest get &lt;code&gt;Drop&lt;/code&gt; running cleanup exactly once when a value goes out of scope. The argument is that compiler errors are a better feedback loop than a style guide enforced by review, and for this class of bug, lifetimes of garbage-collected values mixed with manually managed ones, it's hard to argue with. Zig's &lt;code&gt;defer&lt;/code&gt;/&lt;code&gt;errdefer&lt;/code&gt; model puts cleanup at every call site and trusts the author to get it right every time. Bun, at half a million lines, did not get it right every time, and nobody reasonable expected it to.&lt;/p&gt;
&lt;p&gt;This is the part I'd already half-gotten wrong. When I lumped Bun into the &quot;everything's Rust now&quot; trend in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/pnpm-v12-rust-rewrite&quot;&gt;pnpm v12 post&lt;/a&gt; and flagged it as the Zig holdout, I treated language choice as a settled, one-way decision. Sumner says explicitly that it used to be one, and that AI-assisted porting is what made it reversible at this scale. Take that claim seriously even if you're skeptical of the rest.&lt;/p&gt;
&lt;h3 id=&quot;how-do-you-port-535-000-lines-in-11-days-&quot;&gt;How do you port 535,000 lines in 11 days?&lt;/h3&gt;
&lt;p&gt;You don't ask one agent to &quot;rewrite Bun.&quot; Sumner's post is most useful as an engineering breakdown of the loops, and the loops are ordinary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Pseudocode, not real code:
let task;
while ((task = todoList.pop())) {
  const result = task();
  const feedback = await Promise.all([review(result), review(result)]);
  await apply(feedback, result);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each workflow is one implementer plus two or more adversarial reviewers plus a fixer, with the reviewers running in separate context windows so the Claude that wrote the code isn't the Claude grading it. At peak, four workflows ran at once, each with 16 Claudes, for about 64 concurrent agents across four &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/git-worktrees-are-underrated&quot;&gt;git worktrees&lt;/a&gt;. They started on May 3 and merged May 14, across 6,778 commits and 1,448 &lt;code&gt;.zig&lt;/code&gt; files moved to &lt;code&gt;.rs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The part worth copying is the prep work. Before any porting, Sumner spent about three hours with Claude producing a &lt;code&gt;PORTING.md&lt;/code&gt; that mapped Zig patterns to Rust patterns, then ran a workflow that traced every struct field's lifetime across the codebase and wrote the results to a &lt;code&gt;LIFETIMES.tsv&lt;/code&gt;. Three files were ported first as a trial, reviewed against the guides, then the full sweep ran. The false starts are instructive: two Claudes ran &lt;code&gt;git stash&lt;/code&gt; and &lt;code&gt;git reset --hard&lt;/code&gt; over each other's work, so the workflow rule became &quot;no &lt;code&gt;git&lt;/code&gt; except committing one specific file, no &lt;code&gt;cargo&lt;/code&gt;, no slow commands.&quot; Claude also tried to &quot;fix&quot; compile errors by stubbing functions out, so a rule was added: if a workaround needs a paragraph-long comment, the code is wrong, fix the code.&lt;/p&gt;
&lt;p&gt;The token bill for all of this is roughly 5.9 billion uncached input tokens, 690 million output tokens, and 72 billion cached reads, about $165,000 at API pricing, on what was then a pre-release build of Claude Fable 5, the Mythos-class model Anthropic shipped a month later. That's the part the search summaries will quote and the part that most obscures what happened. The money bought an engineered loop with human supervision, not a prompt and a prayer.&lt;/p&gt;
&lt;h3 id=&quot;do-the-numbers-hold-up-&quot;&gt;Do the numbers hold up?&lt;/h3&gt;
&lt;p&gt;Here's the skeptic's reading. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://bun.com/blog/bun-in-rust&quot;&gt;announcement&lt;/a&gt; lists 128 bugs fixed versus 1.3.14, 19 known regressions (all fixed), zero tests skipped or deleted, and 1,386,826 &lt;code&gt;expect()&lt;/code&gt; calls on Debian. The leak numbers are the most convincing: every in-process &lt;code&gt;Bun.build()&lt;/code&gt; leaked about 3 MB in 1.3.14, so 2,000 builds ran the process to 6,745 MB. In 1.4 it levels off at 609 MB.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Builds &lt;/th&gt;&lt;th&gt;Bun 1.3.14 &lt;/th&gt;&lt;th&gt;Bun 1.4 (canary)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;500    &lt;/td&gt;&lt;td&gt;1,914 MB   &lt;/td&gt;&lt;td&gt;526 MB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1,000  &lt;/td&gt;&lt;td&gt;3,506 MB   &lt;/td&gt;&lt;td&gt;586 MB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1,500  &lt;/td&gt;&lt;td&gt;5,097 MB   &lt;/td&gt;&lt;td&gt;608 MB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2,000  &lt;/td&gt;&lt;td&gt;6,745 MB   &lt;/td&gt;&lt;td&gt;609 MB&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If you run a dev server that bundles on every request, that's the difference between an OOM restart at 3 AM and a stable process. A previous Zig attempt at this fix &lt;a rel=&quot;nofollow&quot; href=&quot;https://bun.com/blog/bun-in-rust&quot;&gt;never merged&lt;/a&gt; because, Sumner says, the lack of &lt;code&gt;Drop&lt;/code&gt; made it too risky to trust. That's the strongest single argument for the rewrite.&lt;/p&gt;
&lt;p&gt;The speed and size numbers want more scrutiny. Bun 1.4 is 2 to 5% faster on HTTP and app workloads: &lt;code&gt;Bun.serve&lt;/code&gt; goes 169.6k to 177.7k req/s (up 4.8%), and &lt;code&gt;next build&lt;/code&gt; goes 13.62s to 13.03s. The binary shrinks roughly 20%, from 94 to 76 MB on Windows and 88 to 70 MB on Linux. But the announcement itself attributes the throughput to cross-language LTO between C/C++ and Rust, and attributes the binary shrink partly to ICU trimming and Identical Code Folding. None of that is Rust-only. The initial Rust-only binary reduction came from dropping Zig's heavy &lt;code&gt;comptime&lt;/code&gt; use; the rest came from linker work that could have shipped in Zig.&lt;/p&gt;
&lt;p&gt;That's where Andrew Kelley's pushback lands hardest. The short version: a meaningful slice of the &quot;Bun is better in Rust&quot; win was engineering work Bun could have shipped without changing languages, and the post doesn't separate the two clearly.&lt;/p&gt;
&lt;h3 id=&quot;the-sumner-vs-kelley-fight-both-sides&quot;&gt;The Sumner vs. Kelley fight, both sides&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://andrewkelley.me/post/my-thoughts-bun-rust-rewrite.html&quot;&gt;Andrew Kelley's response&lt;/a&gt; is not a calm post, and it's not framed as a technical critique. It opens on Sumner's &quot;beginner energy&quot; and builds to Kelley calling him &quot;a stinky manager. Poor communication, unrealistic expectations, low empathy, no experience.&quot; But buried in the personal framing are four technical objections that hold up on their own:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The test-suite contradiction. The argument for merging a million lines of largely unreviewed code is that the test suite catches everything. But the same post opens with a long list of bugs in the Zig version the test suite didn't catch. Kelley's question: if it wasn't sufficient to catch Zig bugs, why is it sufficient to declare one million lines of unreviewed AI-authored Rust clean?&lt;/li&gt;
&lt;li&gt;The style-guide sleight of hand. The post frames the choice as &quot;style guide vs. language feature,&quot; which skips the main way projects actually eliminate bugs, which is putting engineers on it. Kelley points at TigerBeetle, another Zig project, as the team that did the work Bun didn't.&lt;/li&gt;
&lt;li&gt;The fuzzing claim. The announcement implies diligent Zig-side fuzzing; Kelley says Bun told the Zig Software Foundation directly that they weren't fuzzing anything. The 24/7 coverage-guided fuzzing the post now brags about is new and Rust-side.&lt;/li&gt;
&lt;li&gt;The omitted build speed. The Zig compiler, roughly 600,000 lines, builds clean in 16s and recompiles in 90ms with incremental enabled, Kelley writes. Bun's post doesn't give post-rewrite build numbers, and for a mechanical port of a codebase this size that's a conspicuous gap.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Each of those is fair. The first is the sharpest. Sumner's &quot;0 tests deleted, 1.4M expect() calls, adversarial review, human in the loop&quot; defense addresses process, not the logical gap. A suite that missed a &lt;code&gt;setSession&lt;/code&gt; leak bleeding 6.5 KB a call for years is not a suite you can then cite as proof a million lines are correct. The suite is necessary and it caught a lot, the 128 fixes prove that, but &quot;the test suite caught regressions&quot; and &quot;the test suite proves correctness&quot; are different claims, and the announcement slides between them.&lt;/p&gt;
&lt;p&gt;Sumner's side holds up too, and it's the one that matters more for users. The Zig bug list isn't marketing; those are real crashes and real leaks in a runtime with 22 million monthly CLI downloads that now backs Claude Code. The &lt;code&gt;Drop&lt;/code&gt; argument is the real argument, and Kelley doesn't really answer it. He shifts to &quot;you should have put in engineering hours instead,&quot; which is true and also not a refutation of the fact that Zig's manual-lifetime model was bleeding. You can fault how Bun got to Rust and still accept that being in Rust, with &lt;code&gt;Drop&lt;/code&gt; and a borrow checker and Miri, materially lowers the chance of the next &lt;code&gt;setSession&lt;/code&gt; leak.&lt;/p&gt;
&lt;p&gt;Where I land: the rewrite is net good for Bun's users, the AI-authorship process is more disciplined than its critics assume, and the announcement overclaims by bundling genuinely Rust-driven gains (leaks, use-after-free) with engineering work that didn't need a rewrite (LTO, ICU, &lt;code&gt;comptime&lt;/code&gt; cleanup). Read the two posts as correcting each other and you get closer to the truth than either one alone.&lt;/p&gt;
&lt;h3 id=&quot;what-about-the-unsafe-code-and-undefined-behavior-&quot;&gt;What about the &lt;code&gt;unsafe&lt;/code&gt; code and undefined behavior?&lt;/h3&gt;
&lt;p&gt;The thing people will actually worry about with AI-authored Rust is &lt;code&gt;unsafe&lt;/code&gt;. The announcement gives the number: about 4% of the Rust sits in &lt;code&gt;unsafe&lt;/code&gt; blocks, roughly 13,000 &lt;code&gt;unsafe&lt;/code&gt; keyword uses across the ~780,000-line codebase, and 78% of those blocks are a single line, a pointer from JavaScriptCore or one call into a C library. That's lower than I expected, and it's the floor, not a ceiling. Bun embeds JSC, uWebSockets, BoringSSL, and SQLite, so &lt;code&gt;unsafe&lt;/code&gt; will never hit zero. The plan is to push it down as the mechanical port gets refactored toward idiomatic Rust.&lt;/p&gt;
&lt;p&gt;The worry has a concrete footprint. Issue &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/oven-sh/bun/issues/30719&quot;&gt;#30719&lt;/a&gt;, filed May 14 by AwesomeQubic, is titled &quot;PathString::slice dangling reference UB - add Miri to CI&quot;: a &lt;code&gt;core::slice::from_raw_parts&lt;/code&gt; call constructing a dangling &lt;code&gt;&amp;amp;[u8]&lt;/code&gt;. Miri was not in Bun's CI when the bug was filed; the reporter ran it locally to find it. The issue is closed, fixed by &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/oven-sh/bun/pull/30876&quot;&gt;#30876&lt;/a&gt; (&quot;Add cargo-miri support and fix HiveArray aliasing UB&quot;, merged May 17), with &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/oven-sh/bun/pull/30728&quot;&gt;#30728&lt;/a&gt; an earlier attempt still open on GitHub. The reporter's parting line, &quot;Please consider not vibe coding rust as AIs are not good at writing Rust and also hire a real rust dev,&quot; is the tweet-sized version of the whole debate. The bug is real, the kind of UB that turns up in any large &lt;code&gt;unsafe&lt;/code&gt;-touching codebase, and Miri is exactly the tool meant to catch it. What's worth being precise about is who supplied the mechanism: the reporter ran it on a build Bun hadn't instrumented yet, not Bun's CI. The announcement now says Miri runs on a growing chunk of code in CI, which is the state after #30876 landed. So it's still the system working, not failing, but it's the community catching the bug and Bun turning the catch into infrastructure. And it's the proof that &quot;AI wrote it&quot; doesn't free you from reviewing &lt;code&gt;unsafe&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id=&quot;where-this-leaves-the-javascript-toolchain&quot;&gt;Where this leaves the JavaScript toolchain&lt;/h3&gt;
&lt;p&gt;The pattern is established enough that I'd stop calling it a trend and start calling it the default. &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/typescript-7-is-here&quot;&gt;TypeScript&lt;/a&gt; went to Go for build speed. &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/pnpm-v12-rust-rewrite&quot;&gt;pnpm&lt;/a&gt; is going to Rust for install speed. &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/astro-7&quot;&gt;Astro 7&lt;/a&gt; rebuilt its compiler in Rust. Deno is Rust from the ground up. Bun just went to Rust for stability. The reasons differ, but the move is the same: the JavaScript world's foundation is being rewritten in languages with a real memory model, and the engines running the JS itself, V8 and JavaScriptCore with their &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/five-things-you-might-not-know-about-javascript&quot;&gt;tiered JIT pipelines&lt;/a&gt;, are the one layer staying put.&lt;/p&gt;
&lt;p&gt;What's new in the Bun story is who's doing it and who owns it. Anthropic &lt;a rel=&quot;nofollow&quot; href=&quot;https://bun.com/blog/bun-joins-anthropic&quot;&gt;acquired Bun in December 2025&lt;/a&gt;. Claude Code ships as a Bun executable to millions of users, and the rewrite ran on Claude Fable 5 a month before Anthropic shipped it. Prisma launched a Prisma Compute beta on the Rust rewrite; Alexey Orlenko is quoted in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://bun.com/blog/bun-in-rust&quot;&gt;announcement&lt;/a&gt; on the memory leaks and the connection pool &quot;that couldn't recover after a VM was paused and resumed,&quot; handled &quot;perfectly&quot; by the Rust build. That's the financial and structural context the announcement's lead leaves out: this is Anthropic making its own tooling's runtime safer, with Anthropic's model. The independence question is real, even if the code stays MIT.&lt;/p&gt;
&lt;p&gt;The subtler shift is what &quot;an engineer can do in a year&quot; means. Sumner's closing line, that one engineer can do a lot more today than a year ago, is the part that's broader than Bun. If a faithful port of half a million lines, supervised, costs $165k and 11 days, the bottleneck for a class of large rewrites stops being the typing and starts being taste: knowing which port to do, how to scope it, and how to review the output. Bun's loop is a template people will copy, and the teams that internalize it first get to attempt things that used to require freezing development for a year.&lt;/p&gt;
&lt;h3 id=&quot;should-you-run-bun-1-4-today-&quot;&gt;Should you run Bun 1.4 today?&lt;/h3&gt;
&lt;p&gt;If you're on Bun in production on 1.3.x, the answer is &quot;not yet, but soon, and watch the bug list.&quot; As I write this, the latest stable tag in Bun's &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/oven-sh/bun/releases.atom&quot;&gt;release feed&lt;/a&gt; is still v1.3.14 from May 13. The only newer entries are &lt;code&gt;consolidation-step-N-green&lt;/code&gt; canary tags from Bun's automation, not a stable v1.4.0.&lt;/p&gt;
&lt;p&gt;To try it on a throwaway branch:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;bun upgrade --canary
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The honest test isn't the benchmark. Run the leaky-build scenario from the announcement, 2,000 &lt;code&gt;Bun.build()&lt;/code&gt; calls in one process, against whatever you ship, and watch memory. If it holds flat the way the announcement's table shows, the rewrite did the thing you actually care about. If it doesn't, file the bug, because the whole premise is that this class of issue is now catchable and fixable instead of permanent.&lt;/p&gt;
&lt;p&gt;I wrote that Bun was the Zig holdout two and a half weeks ago, and the line was stale the week it went up: Sumner had already announced the rewrite. The less comfortable lesson, for anyone writing about this stack, is that &quot;X is written in Y&quot; is now a claim with a short half-life, short enough that being a week behind the news is enough to be wrong. Bun's Rust rewrite is the cleanest example I've seen of AI-assisted porting at a scale that used to take a year, and the open questions about it, how much of the win was the language versus the engineering hours, whether machine-authored &lt;code&gt;unsafe&lt;/code&gt; can be trusted, who owns the runtime your agent runs on, are the ones the rest of the JavaScript toolchain is going to answer next, one rewrite at a time.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23937/bun-rust-rewrite-64-claude-agents-535k-lines-of-zig</guid>
<pubDate>Sat, 01 Aug 2026 14:39:27 +0000</pubDate>
</item>
<item>
<title>Porting my Rust KVM hypervisor to ARM64: Running the binary</title>
<link>https://coderlegion.com/23276/porting-my-rust-kvm-hypervisor-to-arm64-running-the-binary</link>
<description>&lt;h3 id=&quot;recap&quot;&gt;Recap&lt;/h3&gt;
&lt;p&gt;In the &lt;a rel=&quot;nofollow&quot; href=&quot;../kvm-rust-arm-part1&quot;&gt;previous article&lt;/a&gt; we ported the register handling&lt;br&gt;
of my Rust KVM hypervisor from x86 to ARM64. By tracing QEMU with strace and&lt;br&gt;
inspecting the Linux headers, we discovered that ARM64 accesses CPU state&lt;br&gt;
through the &lt;code&gt;KVM_GET_ONE_REG&lt;/code&gt; and &lt;code&gt;KVM_SET_ONE_REG&lt;/code&gt; interface and reconstructed&lt;br&gt;
the register &lt;code&gt;id&lt;/code&gt; encoding in Rust.&lt;/p&gt;
&lt;p&gt;At that point the hypervisor compiled successfully, but trying to access even&lt;br&gt;
the program counter resulted in an &lt;code&gt;ENOEXEC&lt;/code&gt; error. In this article we'll&lt;br&gt;
continue the reverse-engineering process, initialize the vCPU correctly, and&lt;br&gt;
finally execute our first ARM64 guest.&lt;/p&gt;
&lt;h3 id=&quot;second-roadblock-enoexec-when-getting-setting-pc&quot;&gt;Second roadblock: ENOEXEC when getting / setting PC&lt;/h3&gt;
&lt;p&gt;I was rather optimistic this would work out of the box, but even after trying&lt;br&gt;
to do something as simple as reading the &lt;code&gt;PC&lt;/code&gt; register I hit a snag:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ioctl(5&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, KVM_ARM_SET_DEVICE_ADDR or KVM_GET_ONE_REG, 0x7fdf2c81e8) = -1 ENOEXEC (Exec format error)
Error: Os { code: 8, kind: Uncategorized, message: &quot;Exec format error&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Luckily, I had a concrete lead now: error code and KVM command. So I searched&lt;br&gt;
for the &lt;code&gt;KVM_GET_ONE_REG&lt;/code&gt; in the Linux kernel code on Bootlin Elixir and found&lt;br&gt;
the following code in &lt;a rel=&quot;nofollow&quot; href=&quot;https://elixir.bootlin.com/linux/v6.1.21/source/arch/arm64/kvm/arm.c#L1298&quot;&gt;arch/arm64/kvm/arm.c&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;case KVM_SET_ONE_REG:
case KVM_GET_ONE_REG: {
struct kvm_one_reg reg;

r = -ENOEXEC;
if (unlikely(!kvm_vcpu_initialized(vcpu)))
break;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is exactly the error we were getting and we immediately found what was&lt;br&gt;
missing: vCPU initialization. Actually, if we take a closer look at the code we&lt;br&gt;
will see the comment kernel developers have left us:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;int kvm_arch_vcpu_create(struct kvm_vcpu *vcpu)
{
int err;

/* Force users to call KVM_ARM_VCPU_INIT */
vcpu-&amp;gt;arch.target = -1;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;initializing-the-vcpu&quot;&gt;Initializing the vCPU&lt;/h4&gt;
&lt;p&gt;Following the lead I looked for &lt;code&gt;KVM_ARM_VCPU_INIT&lt;/code&gt; in the original &lt;code&gt;strace&lt;/code&gt; log&lt;br&gt;
and found:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;137152 ioctl(12&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4020aeae /* KVM_ARM_VCPU_INIT */, 0x7fff57e028) = 0
137156 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4020aeae /* KVM_ARM_VCPU_INIT */, 0x7f82f70e68) = 0
137152 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4020aeae /* KVM_ARM_VCPU_INIT */, 0x7fff57dd78) = 0
137152 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4020aeae /* KVM_ARM_VCPU_INIT */, 0x7fff57e048) = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This wasn't really helpful: we again only see addresses, but we see no hints of&lt;br&gt;
underlying structure. So let's first take a look at how &lt;code&gt;KVM_ARM_VCPU_INIT&lt;/code&gt; is&lt;br&gt;
defined in the Linux headers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ grep -Rn KVM_ARM_VCPU_INIT /usr/include/
/usr/include/linux/kvm.h:1622:#define KVM_ARM_VCPU_INIT   _IOW(KVMIO,  0xae, struct kvm_vcpu_init)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A quick search for &lt;code&gt;struct kvm_vcpu_init&lt;/code&gt; revealed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ grep -Rn 'struct kvm_vcpu_init' /usr/include/ -A3
/usr/include/aarch64-linux-gnu/asm/kvm.h:112:struct kvm_vcpu_init {
/usr/include/aarch64-linux-gnu/asm/kvm.h-113-   __u32 target;
/usr/include/aarch64-linux-gnu/asm/kvm.h-114-   __u32 features[7];
/usr/include/aarch64-linux-gnu/asm/kvm.h-115-};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;getting-struct-data-with-python-gdb&quot;&gt;Getting struct data with Python GDB&lt;/h4&gt;
&lt;p&gt;I didn't want to spend much time understanding every target and feature flag. My&lt;br&gt;
goal was simply to reproduce QEMU's behavior as quickly as possible (and I&lt;br&gt;
didn't even have to understand it completely). Luckily, this structure was&lt;br&gt;
really simple (basically an array of eight &lt;code&gt;32&lt;/code&gt;-bit integers), so I decided to&lt;br&gt;
tackle this with Python GDB automation. I created a breakpoint to stop exactly&lt;br&gt;
on &lt;code&gt;ioctl&lt;/code&gt; call when we are in &lt;code&gt;KVM_ARM_VCPU_INIT&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;import gdb
import struct

def log(line):
    print(line)
    with open(&quot;gdb-debug.out&quot;, &quot;a&quot;) as f:
        f.write(line + &quot;\n&quot;)

class IoctlBreakpoint(gdb.Breakpoint):

    def stop(self):
        if x1 == 0x4020aeae: # KVM_ARM_VCPU_INIT
            ptr = int(gdb.parse_and_eval(&quot;$x2&quot;))
            vcpu_init_mem = gdb.selected_inferior().read_memory(ptr, 32) # u32 + 7 * u32
            target_features = struct.unpack(&quot;&amp;lt;8I&quot;, bytes(vcpu_init_mem))
            log(f&quot;KVM_ARM_VCPU_INIT: target={target_features[0]} features={target_features[1:]}&quot;)        
            return False

if __name__ == &quot;__main__&quot;:
    tracer = IoctlBreakpoint(&quot;ioctl&quot;)
    gdb.execute(&quot;handle SIGUSR1 SIGUSR2 nostop noprint pass&quot;)
    gdb.execute(&quot;run&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I ran my QEMU command with GDB:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;gdb -ex 'source ./trace-arm.py' ./start-qemu.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This has automatically yielded great results:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;KVM_ARM_VCPU_INIT: target=5 features=(0, 0, 0, 0, 0, 0, 0)
KVM_ARM_VCPU_INIT: target=5 features=(12, 0, 0, 0, 0, 0, 0)
KVM_ARM_VCPU_INIT: target=5 features=(12, 0, 0, 0, 0, 0, 0)
KVM_ARM_VCPU_INIT: target=5 features=(12, 0, 0, 0, 0, 0, 0)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For now I want to try just setting &lt;code&gt;target&lt;/code&gt;, but leave &lt;code&gt;features&lt;/code&gt; zeroed out. I&lt;br&gt;
could immediately reconstruct this in Rust:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;impl VCPU {
    pub fn arm_vcpu_init(&amp;amp;self) -&amp;gt; io::Result&amp;lt;()&amp;gt; {
        let mut vcpu_init : kvm_vcpu_init = unsafe { std::mem::zeroed() };
        // KVM_ARM_VCPU_INIT: target=5 features=(0, 0, 0, 0, 0, 0, 0)
        vcpu_init.target = 5;
        let ret = unsafe { libc::ioctl(self.fd, KVM_ARM_VCPU_INIT, &amp;amp;mut vcpu_init) };
        if ret &amp;lt; 0 {
            return Err(io::Error::last_os_error());
        }
        Ok(())
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;third-roadblock-no-kvm-exit&quot;&gt;Third roadblock: No KVM exit&lt;/h3&gt;
&lt;p&gt;Soon after this I was able to get and set registers properly. The first ARM test&lt;br&gt;
binary I compiled was based on working UART code from my hobby operating system&lt;br&gt;
I started on Raspberry Pi. I expected at least a fault exit, if not an MMIO exit&lt;br&gt;
when UART code was called. However, &lt;code&gt;KVM_RUN&lt;/code&gt; never returned.&lt;/p&gt;
&lt;p&gt;This is where I was stuck for a while. I considered creating a separate thread&lt;br&gt;
that repeatedly queried &lt;code&gt;KVM_GET_ONE_REG&lt;/code&gt; for &lt;code&gt;PC&lt;/code&gt;, but from my own working&lt;br&gt;
experience I knew that calling KVM &lt;code&gt;ioctl&lt;/code&gt; calls asynchronously was a really bad&lt;br&gt;
idea. I also considered adding hardware breakpoint capabilities to at least&lt;br&gt;
trigger &lt;code&gt;KVM_EXIT_DEBUG&lt;/code&gt;, but this would require more infrastructure for just&lt;br&gt;
checking simple binary.&lt;/p&gt;
&lt;h4 id=&quot;writing-a-simple-binary&quot;&gt;Writing a simple binary&lt;/h4&gt;
&lt;p&gt;I decided to try adding a signal handler instead. If I interrupted &lt;code&gt;KVM_RUN&lt;/code&gt;&lt;br&gt;
call with &lt;code&gt;SIGINT&lt;/code&gt; maybe I could get registers then synchronously. I lowered&lt;br&gt;
my expectations for initial binary and just created something really simple:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-asm&quot;&gt;.section .text

.macro curr_el_to reg
mrs\reg, CurrentEL
lsr\reg, \reg, #2
and\reg, \reg, #0xf
.endm

.globl _start
_start:
mrsx0, mpidr_el1
andx0, x0, #0xFF
cbzx0, control
b .

control:
curr_el_to x0
b .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It looks like much, but this code will just load current CPU privilege level to&lt;br&gt;
&lt;code&gt;x0&lt;/code&gt; register (being that level &lt;code&gt;EL=1&lt;/code&gt; is operating-system level, &lt;code&gt;x0&lt;/code&gt; should be&lt;br&gt;
set to &lt;code&gt;0x1&lt;/code&gt;). The &lt;code&gt;mpidr_el1&lt;/code&gt; code is for checking if we are on control CPU&lt;br&gt;
(this should be unnecessary here, but I added this out of paranoia caused by&lt;br&gt;
real-life issues I had on ARM).&lt;/p&gt;
&lt;h4 id=&quot;installing-signal-handler&quot;&gt;Installing signal handler&lt;/h4&gt;
&lt;p&gt;Finally, I added a signal handler:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;extern &quot;C&quot; fn handler(_sig: libc::c_int) { }

unsafe fn install_interrupt_signal() {
    let mut sa: libc::sigaction = std::mem::zeroed();
    sa.sa_sigaction = handler as *const() as usize;
    libc::sigemptyset(&amp;amp;mut sa.sa_mask);
    sa.sa_flags = 0;
    libc::sigaction(libc::SIGINT, &amp;amp;sa, std::ptr::null_mut());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The handler itself does nothing. I only needed the signal to interrupt the blocking &lt;code&gt;KVM_RUN&lt;/code&gt; call. So my final KVM loop looked like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;    unsafe { install_interrupt_signal() };

    let run = vcpu.kvm_run_mem as *mut kvm_run;

    loop {
        let ret = unsafe { libc::ioctl(vcpu.fd, KVM_RUN, 0usize) };
        if ret &amp;lt; 0 {
            if io::Error::last_os_error().raw_os_error() == Some(libc::EINTR) {
                vcpu.print_regs()?;
            }
            return Err(io::Error::last_os_error());
        }

        let exit_reason = unsafe { (*run).exit_reason };
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;running-the-binary&quot;&gt;Running the binary&lt;/h3&gt;
&lt;p&gt;Finally, it was time to run the gues:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;pi@raspberrypi:~ $ ./rust hello-world-arm.img
x0 = 0x0
pc = 0x1000
Loading &quot;hello-world-arm.img&quot; to 0x1000...
^Cx0 = 0x1
pc = 0x101c
Error: Os { code: 4, kind: Interrupted, message: &quot;Interrupted system call&quot; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see that &lt;code&gt;PC&lt;/code&gt; did change and that &lt;code&gt;x0&lt;/code&gt; really is set to current CPU&lt;br&gt;
privilege level: &lt;code&gt;0x1&lt;/code&gt;. Why it didn't work for my UART code is probably that the&lt;br&gt;
guest itself requires more initialization than I though (x86 went more smoothly&lt;br&gt;
and felt more plug-and-play).&lt;/p&gt;
&lt;h3 id=&quot;conclusion-1&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Although x86 and ARM64 share the same KVM interface at a high level, their&lt;br&gt;
userspace APIs differ significantly. Porting my hypervisor turned out to involve&lt;br&gt;
much more than renaming registers: ARM64 uses per-register &lt;code&gt;id&lt;/code&gt;s instead of a&lt;br&gt;
single register structure, requires explicit vCPU initialization through&lt;br&gt;
&lt;code&gt;KVM_ARM_VCPU_INIT&lt;/code&gt; and demanded a fair amount of reverse engineering before I&lt;br&gt;
could successfully execute guest code. Fortunately, the same workflow that&lt;br&gt;
worked on x86 like combining strace, GDB and the Linux kernel source proved just&lt;br&gt;
as effective on ARM64 (although required a bit more effort).&lt;/p&gt;
&lt;p&gt;The code from this article can be found on my GitHub page:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/StjepanPoljak/kvm-rust/tree/kvm-arm-part2-code&quot;&gt;https://github.com/StjepanPoljak/kvm-rust/tree/kvm-arm-part2-code&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23276/porting-my-rust-kvm-hypervisor-to-arm64-running-the-binary</guid>
<pubDate>Wed, 22 Jul 2026 18:02:25 +0000</pubDate>
</item>
<item>
<title>Astro 7: What's New, What's Faster, and What Breaks</title>
<link>https://coderlegion.com/23165/astro-7-whats-new-whats-faster-and-what-breaks</link>
<description>&lt;p&gt;Astro 7, released &lt;a rel=&quot;nofollow&quot; href=&quot;https://astro.build/blog/astro-7&quot;&gt;June 22, 2026&lt;/a&gt;, is a speed release. The &lt;code&gt;.astro&lt;/code&gt; compiler got rewritten in Rust, the Markdown pipeline got rebuilt in Rust, and the bundler switched to Vite 8 with Rolldown. It is the same framework on faster infrastructure. Builds on real production Astro sites are 15 to 61 percent faster, with the biggest wins on sites where Markdown and &lt;code&gt;.astro&lt;/code&gt; compilation dominate the build.&lt;/p&gt;
&lt;h3 id=&quot;what-astro-7-actually-is&quot;&gt;What Astro 7 actually is&lt;/h3&gt;
&lt;p&gt;Astro 7 is an infrastructure release. The component format, the island architecture, the routing model: all the same. What changed is what sits underneath. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://astro.build/blog/astro-7&quot;&gt;official announcement&lt;/a&gt; calls it the speed release, and that is the honest framing. Four systems got rebuilt or swapped, five experimental features went stable, and the result is faster builds without a new mental model.&lt;/p&gt;
&lt;p&gt;The closest parallel is &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/typescript-7-is-here&quot;&gt;TypeScript 7 rewriting its compiler in Go&lt;/a&gt;: keep the surface still, rebuild the engine. Astro 7 does the same, with Rust, across more layers.&lt;/p&gt;
&lt;h3 id=&quot;vite-8-and-rolldown&quot;&gt;Vite 8 and Rolldown&lt;/h3&gt;
&lt;p&gt;Astro 7 upgrades to &lt;a rel=&quot;nofollow&quot; href=&quot;https://astro.build/blog/astro-7&quot;&gt;Vite 8&lt;/a&gt;, which ships &lt;a rel=&quot;nofollow&quot; href=&quot;https://rolldown.rs&quot;&gt;Rolldown&lt;/a&gt; as its bundler. Rolldown is a Rust bundler that replaces both esbuild and Rollup in the Vite pipeline, and runs 10 to 30 times faster than Rollup in benchmarks. If you had &lt;code&gt;esbuild&lt;/code&gt; options or &lt;code&gt;rollupOptions&lt;/code&gt; in your config, Vite 8 auto-converts them. The plugin API is the same, so existing Vite plugins keep working.&lt;/p&gt;
&lt;p&gt;Vite is Astro's build core: dev server, dependency optimization, production bundling. A faster bundler there is a faster everything.&lt;/p&gt;
&lt;h3 id=&quot;astro-7-s-new-rust-compiler&quot;&gt;Astro 7's new Rust compiler&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;.astro&lt;/code&gt; compiler was rewritten in Rust. It was Go before. The new compiler is built on &lt;a rel=&quot;nofollow&quot; href=&quot;https://oxc.rs&quot;&gt;oxc&lt;/a&gt; and &lt;a rel=&quot;nofollow&quot; href=&quot;https://lightningcss.dev&quot;&gt;Lightning CSS&lt;/a&gt;, and Astro reports about a 6 percent improvement in isolation. That sounds modest next to the Vite 8 jump, and it is. The compiler was already fast. But 6 percent compounds on a large build that runs thousands of &lt;code&gt;.astro&lt;/code&gt; files.&lt;/p&gt;
&lt;p&gt;Two behavior changes, both making the compiler stricter. It no longer auto-corrects HTML: markup is treated as-is. Unclosed tags are now errors, not warnings. If you had sloppy markup the Go compiler quietly fixed, the Rust compiler will tell you. It ships as native binaries per platform, with a WASM fallback.&lt;/p&gt;
&lt;h3 id=&quot;s-tteri-markdown-and-mdx-in-rust&quot;&gt;Sätteri: Markdown and MDX in Rust&lt;/h3&gt;
&lt;p&gt;Sätteri is the new default Markdown and MDX pipeline. It is Rust-powered, built by Erika on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/pulldown-cmark/pulldown-cmark&quot;&gt;pulldown-cmark&lt;/a&gt; and oxc, and it replaces the unified/remark/rehype stack Astro used before.&lt;/p&gt;
&lt;p&gt;The performance is real. Astro's docs build and Cloudflare's docs build each shed over a minute. On a content-heavy site like this one, that is the win you feel.&lt;/p&gt;
&lt;p&gt;Sätteri ships built-in support for GFM, smartypants, heading IDs, directives, math, frontmatter, and wikilinks. The plugin API works differently from unified: plugins declare the node types they care about, and Sätteri routes only those. If you need the old stack, &lt;code&gt;unified()&lt;/code&gt; is still available via &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.astro.build/en/guides/upgrade-to/v7/&quot;&gt;&lt;code&gt;@astrojs/markdown-remark&lt;/code&gt;&lt;/a&gt;. But for most sites, Sätteri is the default and the faster path.&lt;/p&gt;
&lt;h3 id=&quot;queued-rendering-is-now-the-default&quot;&gt;Queued rendering is now the default&lt;/h3&gt;
&lt;p&gt;Queued rendering was experimental in Astro 6. In Astro 7 it is stable and default. The old renderer walked the component tree recursively. Queued rendering uses a queue-based approach instead, and Astro reports roughly 2.4x faster rendering on expression-dense pages. You do not configure this. It is just on. The &lt;code&gt;queuedRendering&lt;/code&gt; experimental flag is gone.&lt;/p&gt;
&lt;h3 id=&quot;advanced-routing-with-src-fetch-ts&quot;&gt;Advanced routing with src/fetch.ts&lt;/h3&gt;
&lt;p&gt;Advanced routing gives you a &lt;code&gt;src/fetch.ts&lt;/code&gt; entrypoint with full control over the request pipeline. It uses the standard &lt;a rel=&quot;nofollow&quot; href=&quot;https://developers.cloudflare.com/workers/runtime-apis/handlers/fetch/&quot;&gt;fetch handler&lt;/a&gt; pattern, the same &lt;code&gt;Request&lt;/code&gt; in, &lt;code&gt;Response&lt;/code&gt; out shape you see in &lt;a rel=&quot;nofollow&quot; href=&quot;https://developers.cloudflare.com/workers/&quot;&gt;Cloudflare Workers&lt;/a&gt; and WinterCG runtimes. It is Hono-compatible, so Hono middleware drops in directly.&lt;/p&gt;
&lt;p&gt;The interesting part is composability. Individual Astro features can be composed as middleware: auth before your actions, a logging layer before everything, all in one file that owns the request lifecycle. &lt;code&gt;src/fetch.ts&lt;/code&gt; is a reserved filename, so rename any existing file with that name.&lt;/p&gt;
&lt;h3 id=&quot;route-caching-and-cdn-providers&quot;&gt;Route caching and CDN providers&lt;/h3&gt;
&lt;p&gt;Route caching is now stable. You configure it with &lt;code&gt;routeRules&lt;/code&gt; specifying &lt;code&gt;maxAge&lt;/code&gt; and &lt;code&gt;swr&lt;/code&gt; per route. Astro also ships experimental CDN cache providers for Netlify, Vercel, and Cloudflare, so cached responses live at the edge instead of in your origin's memory.&lt;/p&gt;
&lt;p&gt;For a docs site or a blog, this is straightforward. Set a &lt;code&gt;maxAge&lt;/code&gt; on your content routes, a shorter one with &lt;code&gt;swr&lt;/code&gt; for pages that update occasionally, and the framework handles invalidation. The CDN providers mean the cache survives deploys on those platforms, which is the part that was missing before.&lt;/p&gt;
&lt;h3 id=&quot;what-breaks-when-upgrading-to-astro-7&quot;&gt;What breaks when upgrading to Astro 7&lt;/h3&gt;
&lt;p&gt;The &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.astro.build/en/guides/upgrade-to/v7/&quot;&gt;migration guide&lt;/a&gt; lists the breaking changes. Here is what to expect:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rust compiler is stricter.&lt;/strong&gt; Unclosed tags error. No more HTML auto-correction. Fix your markup.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sätteri is the default Markdown processor.&lt;/strong&gt; If you depend on a specific remark or rehype plugin, install &lt;code&gt;@astrojs/markdown-remark&lt;/code&gt; to keep the unified pipeline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;compressHTML&lt;/code&gt; defaults to &lt;code&gt;jsx&lt;/code&gt;&lt;/strong&gt; instead of &lt;code&gt;true&lt;/code&gt;. Update your config if you relied on the old default.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Experimental flags removed:&lt;/strong&gt; &lt;code&gt;rustCompiler&lt;/code&gt;, &lt;code&gt;queuedRendering&lt;/code&gt;, &lt;code&gt;advancedRouting&lt;/code&gt;, &lt;code&gt;cache&lt;/code&gt;, and &lt;code&gt;logger&lt;/code&gt;. Their behaviors are now standard or gone.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;@astrojs/db&lt;/code&gt; is removed.&lt;/strong&gt; If you used the experimental database integration, you need a replacement.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;src/fetch.ts&lt;/code&gt; is a reserved filename.&lt;/strong&gt; Rename any existing file with that name.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Container renderer imports changed&lt;/strong&gt; to the &lt;code&gt;/container-renderer&lt;/code&gt; entrypoint.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There are also two AI-oriented additions: a background dev server mode for agents, and JSON logging output. Both are aimed at tooling that drives Astro programmatically.&lt;/p&gt;
&lt;h3 id=&quot;should-you-upgrade-to-astro-7-&quot;&gt;Should you upgrade to Astro 7?&lt;/h3&gt;
&lt;p&gt;Yes, and soon if your site is content-heavy. The build speedup alone is worth it. The migration is mostly mechanical: remove the dead experimental flags, check your Markdown plugins, fix any markup the new compiler rejects.&lt;/p&gt;
&lt;p&gt;Do the upgrade in an isolated &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/git-worktrees-are-underrated&quot;&gt;git worktree&lt;/a&gt; so your main checkout keeps building while you shake out the Sätteri differences and the stricter compiler.&lt;/p&gt;
&lt;p&gt;The broader story is the one I keep writing about. The JavaScript ecosystem is rewriting its foundations in native code. &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/typescript-7-is-here&quot;&gt;TypeScript went to Go&lt;/a&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/pnpm-v12-rust-rewrite&quot;&gt;pnpm went to Rust&lt;/a&gt;, Vite swapped its bundler for Rolldown. And now Astro has rewritten its compiler, its Markdown pipeline, and its rendering engine in Rust, all in one release. The tools you use every day are getting faster underneath you, and the surface you write against is staying still. That is the best kind of progress.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23165/astro-7-whats-new-whats-faster-and-what-breaks</guid>
<pubDate>Tue, 21 Jul 2026 20:52:08 +0000</pubDate>
</item>
<item>
<title>Why I chose Rust over Go for an uptime monitor</title>
<link>https://coderlegion.com/23075/why-i-chose-rust-over-go-for-an-uptime-monitor</link>
<description>&lt;p&gt;I build Uptimepage, an &lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev&quot;&gt;open-source uptime monitor&lt;/a&gt; and status page written in Rust. People ask why Rust and not Go, since Go is the usual pick for this kind of network service. Here is the honest answer. It is not that Rust wins everywhere. It is that one part of this job made the choice for me.&lt;/p&gt;
&lt;p&gt;The product is one promise: tell you fast and honestly when your site is slow or down. The numbers I show you, like your p99 response time, have to be clean. If my own code adds random delay, I blur the exact signal you pay for. So the runtime under the prober matters more here than it would for a normal app.&lt;/p&gt;
&lt;h3 id=&quot;garbage-collection-shows-up-in-the-tail&quot;&gt;Garbage collection shows up in the tail&lt;/h3&gt;
&lt;p&gt;Go has a garbage collector. It is fast, and most apps never feel it. But it still has to do work to free memory, and that work can land inside the millisecond timings I report. Run tens of thousands of checks at once and a pause at the wrong moment lifts a p99 number. At that point I am measuring my own runtime, not your server.&lt;/p&gt;
&lt;p&gt;Rust has no garbage collector. Memory is freed at a point I can see in the code. There is no background pause I did not write. For a tool that sells timing, that control is worth the extra work Rust asks for.&lt;/p&gt;
&lt;h3 id=&quot;what-that-looks-like-in-memory&quot;&gt;What that looks like in memory&lt;/h3&gt;
&lt;p&gt;Here are some real numbers, with a warning attached. They come from a load test on a developer laptop, not a production server. I use them to catch a slowdown between two versions of my code, not to plan capacity. A real server does better, a small box does worse. Treat them as a floor.&lt;/p&gt;
&lt;p&gt;In one run, a single machine held 50,000 checks in flight and peaked at 933 MiB of memory. That is under one gigabyte for fifty thousand live checks. My running server uses about 42 MiB while watching its monitors, and it sits quiet when there is nothing to do. That kind of density is normal for a service with no garbage collector, and it means one small box covers a lot of monitors. I go deeper on the prober and the throughput numbers in &lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev/blog/building-an-uptime-monitor-in-rust&quot;&gt;the build story&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-bug-that-never-compiles&quot;&gt;The bug that never compiles&lt;/h3&gt;
&lt;p&gt;Speed is only half of it. The other half is a class of bug that Rust refuses to build.&lt;/p&gt;
&lt;p&gt;Picture many workers writing to one shared map of results at the same time. In Go this compiles and runs. Sometimes it is fine. Sometimes two goroutines write at once, you get corrupt data or a crash, and it only happens under load, which is the worst time to find out:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-go&quot;&gt;// compiles fine, races at runtime
results := map[string]int{}
for _, check := range checks {
    go func(c Check) {
        results[c.ID] = c.Latency // data race
    }(check)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Go gives you good tools for this. There is a race detector, a sync.Mutex, and channels. But remembering to reach for them is on you.&lt;/p&gt;
&lt;p&gt;In Rust the same concurrent write does not compile. The compiler stops you until the shared map is wrapped in a lock:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;// will not compile unless the shared map is a Mutex
let results = Mutex::new(HashMap::new());
std::thread::scope(|s| {
    for check in checks {
        let results = &amp;amp;results;
        s.spawn(move || {
            // each worker locks only for its own insert
            results.lock().unwrap().insert(check.id, check.latency);
        });
    }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each worker holds the lock only for its own insert, so the writes stay safe without blocking the others for long. For code that runs day and night across many machines, &quot;the compiler will not let you ship the race&quot; removes a whole set of late-night bugs before they exist.&lt;/p&gt;
&lt;h3 id=&quot;where-go-is-the-better-pick&quot;&gt;Where Go is the better pick&lt;/h3&gt;
&lt;p&gt;None of this makes Go a bad choice. Often it is the better one. Go is faster to learn. It builds in seconds. Its standard library for network services is excellent, and a new engineer can be useful in days. If I were building a normal web service, or something I had to ship this week, Go would be on the table and might win.&lt;/p&gt;
&lt;p&gt;Rust asks more from you first. The compiler argues with you. The build is slower. You spend time on things Go would just handle. I take that trade because this job is narrow and it rewards tight control over memory and timing.&lt;/p&gt;
&lt;h3 id=&quot;the-compiler-as-a-safety-net&quot;&gt;The compiler as a safety net&lt;/h3&gt;
&lt;p&gt;Heavy code review is how large teams catch data races and memory bugs. Rust gives you a lot of that for free. The compiler turns those mistakes into build errors, so they never reach production and never wake anyone up. Every change gets a strong, automatic check before it ships, which is a big part of why I trust the service to run unattended.&lt;/p&gt;
&lt;p&gt;It is also why Uptimepage is open source and self-hostable. You can read the code, run it on your own hardware, and export your data whenever you want, so you are never locked into a single vendor. You can start from &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/uptimepage/uptimepage&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;the-honest-version&quot;&gt;The honest version&lt;/h3&gt;
&lt;p&gt;The honest version is not &quot;Rust beats Go.&quot; It is &quot;for a tool that lives or dies by timing and runs at high concurrency, Rust fit better.&quot; Pick the language for the job in front of you. Mine happened to be a job that Rust is very good at.&lt;/p&gt;
&lt;p&gt;If you have shipped a heavily concurrent network service, did you reach for Go or Rust, and did the GC ever actually show up in your tail latencies? Curious where people land when the timing is the product.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/23075/why-i-chose-rust-over-go-for-an-uptime-monitor</guid>
<pubDate>Mon, 20 Jul 2026 14:44:36 +0000</pubDate>
</item>
<item>
<title>pnpm v12 Is Being Rewritten in Rust</title>
<link>https://coderlegion.com/22954/pnpm-v12-is-being-rewritten-in-rust</link>
<description>&lt;p&gt;Every package manager has a moment where you just wait. You add one dependency, or you pull a branch and run install, and there's a beat where the terminal sits there doing file I/O while you tab away. &lt;a rel=&quot;nofollow&quot; href=&quot;https://pnpm.io&quot;&gt;pnpm&lt;/a&gt; already made that beat shorter than most, staying ahead of npm and Yarn on most install scenarios thanks to its content-addressable store and hard-linked &lt;code&gt;node_modules&lt;/code&gt;. But it was still a Node.js program, paying the Node startup cost and pushing tens of thousands of file operations through a &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/five-things-you-might-not-know-about-javascript&quot;&gt;single JavaScript runtime&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That is what changes in pnpm v12: it rewrites the install engine, the part that fetches and links packages, in &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rust-lang.org&quot;&gt;Rust&lt;/a&gt;, and leaves the CLI, lockfile, and &lt;code&gt;node_modules&lt;/code&gt; layout untouched. It's the same move &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/typescript-7-is-here&quot;&gt;TypeScript just made with Go&lt;/a&gt;, aimed at a different bottleneck. And like that one, the interesting detail is how little is supposed to change for you.&lt;/p&gt;
&lt;h3 id=&quot;what-pnpm-v12-actually-is&quot;&gt;What pnpm v12 actually is&lt;/h3&gt;
&lt;p&gt;pnpm v12 is the same pnpm you already use, ported to Rust. pnpm has always been TypeScript running on Node.js, and the Rust work lives under the codename &lt;strong&gt;&lt;code&gt;pacquet&lt;/code&gt;&lt;/strong&gt;, whose repo describes it in one line: &quot;The official pnpm rewrite in Rust.&quot; The key word there is &lt;em&gt;rewrite&lt;/em&gt;, not &lt;em&gt;reimagining&lt;/em&gt;. It's a port. The flags, defaults, error codes, lockfile format, and &lt;code&gt;node_modules&lt;/code&gt; layout are all meant to match pnpm exactly.&lt;/p&gt;
&lt;p&gt;Zoltan Kochan, pnpm's creator, &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/orgs/pnpm/discussions/11292&quot;&gt;put it plainly&lt;/a&gt; when the v12 plan came together: &quot;v12 should behave the same as v11, the biggest change will be the full rewrite to Rust.&quot; That's the frame to hold onto. This is a performance release wearing a major version number, not a redesign of how pnpm works.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pacquet&lt;/code&gt; started as a separate experimental repo and &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/zkochan/status/2054966567692099943&quot;&gt;moved into the main pnpm monorepo&lt;/a&gt; in May 2026, so the TypeScript and Rust versions now evolve side by side. The eventual goal is for the Rust engine to be what you run.&lt;/p&gt;
&lt;h3 id=&quot;why-did-pnpm-choose-rust-&quot;&gt;Why did pnpm choose Rust?&lt;/h3&gt;
&lt;p&gt;The motivation isn't abstract. Two things were capping how fast a Node-based pnpm could go.&lt;/p&gt;
&lt;p&gt;The first is startup cost. Every invocation booted a Node.js runtime before it did any work. pnpm v12 ships as native per-platform binaries with &lt;a rel=&quot;nofollow&quot; href=&quot;https://pnpm.io/blog/releases/11.10&quot;&gt;no Node.js launcher&lt;/a&gt;, so a command pays no runtime bootstrap cost at all. On a fast, cached install where the actual work is milliseconds, that bootstrap was a real fraction of the wall-clock time.&lt;/p&gt;
&lt;p&gt;The second is the file I/O itself. A cold install on a large monorepo is really a lot of filesystem operations: fetching tarballs, unpacking them into the store, and hard-linking thousands of files into place. Running all of that through a single JavaScript thread leaves throughput on the table. Rust handles that fan-out natively, without the runtime sitting in the middle of every syscall.&lt;/p&gt;
&lt;p&gt;There's a softer reason too, and it's the same one that's driving the broader trend: the tooling to do this kind of rewrite has gotten dramatically better, and AI-assisted porting made a 1:1 translation of a large codebase far more tractable than it would have been a few years ago.&lt;/p&gt;
&lt;h3 id=&quot;how-much-faster-is-pnpm-v12-&quot;&gt;How much faster is pnpm v12?&lt;/h3&gt;
&lt;p&gt;pnpm publishes a &lt;a rel=&quot;nofollow&quot; href=&quot;https://pnpm.io/benchmarks&quot;&gt;benchmark suite&lt;/a&gt; comparing v11 against the Rust engine directly. As of this writing (July 2026), here's what it shows:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Scenario                          &lt;/th&gt;&lt;th&gt;pnpm v11 &lt;/th&gt;&lt;th&gt;pnpm (Rust)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Everything warm (repeat install)  &lt;/td&gt;&lt;td&gt;381 ms   &lt;/td&gt;&lt;td&gt;12 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;node_modules present, no cache    &lt;/td&gt;&lt;td&gt;460 ms   &lt;/td&gt;&lt;td&gt;40 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Clean install (nothing cached)    &lt;/td&gt;&lt;td&gt;6.5 s    &lt;/td&gt;&lt;td&gt;2.2 s&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The warm case is everything already in place, so the install just re-verifies the tree, which is what happens on every CI job. That drops from 381ms to 12ms, roughly a 30x difference, and it's the number you'll feel most, because it runs constantly. The colder cases improve less dramatically but still meaningfully: a fully clean install with nothing cached goes from 6.5s to 2.2s, about 3x.&lt;/p&gt;
&lt;p&gt;Treat these as pnpm's own numbers on pnpm's own hardware, and expect your mileage to vary with disk speed and project size. But the direction is not subtle, and the team's claim is that the Rust engine is faster than v11 in every scenario they measured.&lt;/p&gt;
&lt;h3 id=&quot;it-s-already-rolling-out-incrementally&quot;&gt;It's already rolling out, incrementally&lt;/h3&gt;
&lt;p&gt;Here's the part that's easy to get wrong: pnpm v12 isn't a big-bang release. The Rust engine has been landing piece by piece through the v11 line, opt-in first.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;pnpm 11.2&lt;/strong&gt; let you opt into pacquet as the install backend for materialization only. Rust did the fetch and link, while pnpm still resolved dependencies and wrote the lockfile.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pnpm 11.7&lt;/strong&gt; went further: a standard install could be &lt;a rel=&quot;nofollow&quot; href=&quot;https://pnpm.io/blog/releases/11.7&quot;&gt;delegated to pacquet end-to-end&lt;/a&gt;, with resolution, lockfile write, and linking all in a single Rust pass.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pnpm 11.10&lt;/strong&gt; added &lt;code&gt;pnpm self-update next-12&lt;/code&gt;, so you can pull the v12 line and try the Rust binary directly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In v12, the Rust engine for fetching and linking becomes the default rather than an opt-in. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/pnpm/pnpm/issues/11633&quot;&gt;official roadmap&lt;/a&gt; then works outward from there: first the headless frozen-lockfile installer, then full dependency resolution (&lt;code&gt;add&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, &lt;code&gt;remove&lt;/code&gt;, catalogs, overrides, peers), and finally the remaining commands like &lt;code&gt;run&lt;/code&gt;, &lt;code&gt;store&lt;/code&gt;, and &lt;code&gt;publish&lt;/code&gt;. So even after v12 ships, parts of pnpm will still run through the TypeScript implementation for a while. The lockfile is what lets the two halves cooperate.&lt;/p&gt;
&lt;h3 id=&quot;what-changes-for-you-&quot;&gt;What changes for you?&lt;/h3&gt;
&lt;p&gt;The honest answer is: not much, on purpose. Same CLI, same lockfile, same &lt;code&gt;node_modules&lt;/code&gt;. The whole point of a 1:1 port is that your muscle memory and your CI scripts keep working.&lt;/p&gt;
&lt;p&gt;The breaking changes slated for v12 are &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/orgs/pnpm/discussions/11292&quot;&gt;deliberately small&lt;/a&gt;: removing some old migration code for git-hosted tarballs, rejecting unscoped auth settings, and deprecating the &lt;code&gt;$&lt;/code&gt; syntax in &lt;code&gt;overrides&lt;/code&gt; in favor of catalogs. If none of those describe your setup, a v12 upgrade should be close to a no-op.&lt;/p&gt;
&lt;h4 id=&quot;will-the-upgrade-break-your-ci-&quot;&gt;Will the upgrade break your CI?&lt;/h4&gt;
&lt;p&gt;Probably not, but the one thing worth checking is feature parity during the transition. Because the Rust engine is being ported incrementally, some newer or niche capabilities may lag the TypeScript version at any given moment. If your install depends on something specific (a particular linker mode, lockfile-verification settings, or &lt;code&gt;pnpm deploy&lt;/code&gt;), test the bump in an isolated &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/git-worktrees-are-underrated&quot;&gt;git worktree&lt;/a&gt; before you flip it on in CI, the same sandbox I used while &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/solidjs-2-async-data&quot;&gt;exploring SolidJS 2.0's async data model&lt;/a&gt;. This is moving quickly, so the right list of gaps is whatever the roadmap says on the day you upgrade.&lt;/p&gt;
&lt;h3 id=&quot;the-bigger-picture&quot;&gt;The bigger picture&lt;/h3&gt;
&lt;p&gt;pnpm is not doing this alone. The last few years have quietly rebuilt the JavaScript toolchain's foundations in native languages: &lt;a rel=&quot;nofollow&quot; href=&quot;https://biomejs.dev&quot;&gt;Biome&lt;/a&gt; for linting and formatting, &lt;a rel=&quot;nofollow&quot; href=&quot;https://oxc.rs&quot;&gt;Oxc&lt;/a&gt; for parsing and transforming, &lt;a rel=&quot;nofollow&quot; href=&quot;https://rolldown.rs&quot;&gt;Rolldown&lt;/a&gt; and Turbopack for bundling. TypeScript's compiler went to Go. Deno is Rust from the ground up. (Bun is the odd one out: it's written in Zig, not Rust, so don't lump it into the &quot;everything's Rust now&quot; story.)&lt;/p&gt;
&lt;p&gt;There's a competitive angle underneath it, too. Bun and Deno both ship as single fast binaries with package management built in, and a chunk of their appeal is exactly that no-runtime-bootstrap speed. A native-binary pnpm with no Node launcher closes that gap while keeping the thing that makes pnpm pnpm: the strict, content-addressable store and the non-flat &lt;code&gt;node_modules&lt;/code&gt; that catches phantom dependencies.&lt;/p&gt;
&lt;h3 id=&quot;should-you-upgrade-to-pnpm-v12-yet-&quot;&gt;Should you upgrade to pnpm v12 yet?&lt;/h3&gt;
&lt;p&gt;If you're waiting for a stable release: keep waiting, but not for long. As of mid-July 2026, v12 is in alpha. The latest tag is &lt;code&gt;v12.0.0-alpha.12&lt;/code&gt;, and the stable line is still v11.13.0. This is not something to point production CI at today.&lt;/p&gt;
&lt;p&gt;But if you want to feel the difference now, you already can. Run &lt;code&gt;pnpm self-update next-12&lt;/code&gt; on a branch, or opt into the pacquet backend on your current v11, and watch a warm install that used to take a beat return before you've finished reading the command. The type of release this is, where the language underneath gets an order of magnitude faster while everything on top stays exactly where you left it, is a rare and good one. pnpm was already the fast package manager. It's about to stop being one you ever wait on.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22954/pnpm-v12-is-being-rewritten-in-rust</guid>
<pubDate>Sat, 18 Jul 2026 22:37:54 +0000</pubDate>
</item>
<item>
<title>I built a programming language because C++ almost killed my drone</title>
<link>https://coderlegion.com/22891/i-built-a-programming-language-because-c-almost-killed-my-drone</link>
<description>&lt;p&gt;I'm 17. Last year I was writing drone firmware in C++ and Python. One afternoon my ultrasonic sensor returned &lt;code&gt;null&lt;/code&gt; during a landing sequence. The code didn't check for it. The drone tried to divide by zero, the motor controller panicked, and my DJI Tello-sized quadcopter dropped from 3 meters onto concrete.&lt;/p&gt;
&lt;p&gt;That was a $400 lesson in why runtime errors in robotics aren't just bugs. They're physics problems.&lt;/p&gt;
&lt;p&gt;So I built a programming language.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-problem-nobody-talks-about&quot;&gt;The problem nobody talks about&lt;/h3&gt;
&lt;p&gt;When you're writing a web app and something crashes, you reload the page. When you're writing drone code and something crashes, the drone crashes. Into whatever's below it. Usually something expensive or fragile or alive.&lt;/p&gt;
&lt;p&gt;C++ and Python dominate robotics. Both assume you'll handle edge cases yourself. Sensor returns &lt;code&gt;null&lt;/code&gt;? That's your problem. Timing deadline missed? Good luck. No fallback path for when the lidar fails over water? Figure it out.&lt;/p&gt;
&lt;p&gt;ROS (Robot Operating System) helps with some of this, but it's an entire infrastructure layer. What I wanted was something simpler: a language where the compiler catches the stuff that kills robots before you ever compile it.&lt;/p&gt;
&lt;p&gt;Here's what I mean. In C++, you write:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cpp&quot;&gt;float distance = sensor.read();
float speed = target_distance / distance;
motor.set(speed);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;code&gt;distance&lt;/code&gt; is 0, you get a division by zero. If &lt;code&gt;sensor.read()&lt;/code&gt; returns &lt;code&gt;NaN&lt;/code&gt;, you get &lt;code&gt;NaN&lt;/code&gt; propagation through your motor controller. The compiler doesn't care. It'll happily compile this and let your drone fall out of the sky.&lt;/p&gt;
&lt;p&gt;In Fabric (the language I built), you'd write:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;sensor distance: Sensor&amp;lt;f32, ±0.02&amp;gt;
actuator motor: Motor

loop {
    let speed = target_distance / distance
    motor.write(speed)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you try to use &lt;code&gt;distance&lt;/code&gt; without checking that the sensor returned a valid reading, the compiler throws an error. If &lt;code&gt;distance&lt;/code&gt; could be zero and you didn't add a fallback path, the compiler throws an error. If your sensor has an uncertainty bound of ±0.02 and you're doing math that could amplify that uncertainty past a safety threshold, the compiler throws an error.&lt;/p&gt;
&lt;p&gt;The compiler knows about physics. Not perfectly, but enough to catch the stuff that actually kills robots.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-it-works-under-the-hood&quot;&gt;How it works under the hood&lt;/h3&gt;
&lt;p&gt;I wrote the whole thing in Rust. About 4,500 lines across 8 crates. Here's the pipeline:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;.fab source → Lexer → Parser → AST → Type Checker → Fallback Graph → IPET Timing → CodeGen → .py / .c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each step catches a different category of mistakes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The type system&lt;/strong&gt; tracks sensor uncertainty as a first-class concept. When you declare &lt;code&gt;Sensor&amp;lt;f32, ±0.02&amp;gt;&lt;/code&gt;, that &lt;code&gt;±0.02&lt;/code&gt; isn't decoration. It propagates through every math operation. Add two sensors with ±0.02 uncertainty and the result has ±0.04. Multiply by a constant and the uncertainty scales accordingly. If the final result's uncertainty exceeds what's safe for your actuator, the compiler catches it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The fallback graph&lt;/strong&gt; checks that every sensor has a plan B. Not just declared, but actually reachable. If your fallback function A calls fallback function B, and B calls A, that's a cycle. The compiler detects it. If sensor X has no fallback at all, the compiler refuses to build.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;IPET timing analysis&lt;/strong&gt; uses an Integer Linear Program to prove worst-case execution time at compile time. It builds a control flow graph, assigns cycle costs to each instruction (ARM Cortex-M4 model: ALU = 1 cycle, float division = 14 cycles, sensor read = 2 cycles), and solves for the maximum possible execution time across all code paths including loops.&lt;/p&gt;
&lt;p&gt;I didn't come up with this technique. It's from Li &amp;amp; Malik's 1994 paper on implicit path enumeration. But I implemented it in Rust using a pure-Rust ILP solver called &lt;code&gt;good_lp&lt;/code&gt; with the &lt;code&gt;microlp&lt;/code&gt; backend. No external solver dependency. No C FFI. Just Rust solving linear programs.&lt;/p&gt;
&lt;p&gt;The solver maximizes &lt;code&gt;sum(cost_i * x_i)&lt;/code&gt; subject to flow conservation constraints. If the solver fails (which happens on really complex control flow), the system falls back to a conservative estimate instead of crashing.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-two-target-code-generation&quot;&gt;The two-target code generation&lt;/h3&gt;
&lt;p&gt;Fabric compiles to two backends: Python for Webots simulation, and C for ARM Cortex-M hardware.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Python output&lt;/strong&gt; generates a Webots controller class with sensor initialization, motor setup, fallback state tracking, and all the logic. You can test your drone code in simulation before touching hardware.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;C output&lt;/strong&gt; generates code that includes a &lt;code&gt;hal.h&lt;/code&gt; hardware abstraction layer. Static sensor handles, &lt;code&gt;#define TIMEOUT_MS&lt;/code&gt; for fallback deadlines, proper C types (&lt;code&gt;float&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;), and deadline enforcement using &lt;code&gt;hal_get_time_ms()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Same source file, two completely different targets. No &lt;code&gt;#ifdef&lt;/code&gt; in your code. No conditional compilation. The compiler handles the target-specific stuff.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-i-d-do-differently&quot;&gt;What I'd do differently&lt;/h3&gt;
&lt;p&gt;The parser is hand-rolled. About 700 lines of Pratt parser with recursive descent for statements. I started with chumsky (a Rust parser combinator library) but their API changed between versions and the documentation was sparse when I needed it most. So I wrote my own.&lt;/p&gt;
&lt;p&gt;It's fine. But it means every time I want to add a new syntax construct, I'm writing parser code by hand. A proper parser generator would've saved time in the long run, even with the initial investment.&lt;/p&gt;
&lt;p&gt;The loop bound estimation is a heuristic. If your loop body has 1-3 statements, the compiler assumes it runs 10 times. 4-6 statements? 5 times. 7+? 3 times. This is obviously wrong for real programs. Proper static bound analysis would make the IPET results much more accurate. It's on my list.&lt;/p&gt;
&lt;p&gt;The C output references a &lt;code&gt;clamp()&lt;/code&gt; function that it never defines. If you actually try to compile the generated C code on hardware, you'll get a linker error. I know about it. I just haven't fixed it yet.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-i-m-publishing-this&quot;&gt;Why I'm publishing this&lt;/h3&gt;
&lt;p&gt;I put all 8 crates on crates.io. The lexer, parser, type checker, everything. The repo is at github.com/subhansh-dev/fabric.&lt;/p&gt;
&lt;p&gt;Partly because I want other people to try it. If you're working on robotics or drones and you've been bitten by runtime errors, Fabric might save you some pain.&lt;/p&gt;
&lt;p&gt;Mostly because I think the idea matters more than my implementation. The safety-critical systems world has had formal methods and static analysis for decades. But it's all locked behind expensive tools and enterprise licenses. I wanted to build something that a 17-year-old with a Raspberry Pi could use.&lt;/p&gt;
&lt;p&gt;Is my implementation perfect? No. The IPET loop bounds are guesses. The C backend doesn't handle drone swarms. The match expression codegen references some variables it doesn't always generate. I know about all of these. They're bugs, not features, and I'll fix them.&lt;/p&gt;
&lt;p&gt;But the core idea is sound: compile-time safety for robotics shouldn't require a $50,000 license for Simulink. Sometimes the right answer to &quot;how do we prevent this bug&quot; isn't &quot;write better tests&quot; or &quot;add more runtime checks.&quot; Sometimes it's &quot;make the compiler say no.&quot;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-s-next&quot;&gt;What's next&lt;/h3&gt;
&lt;p&gt;I'm working on adding hardware-in-the-loop testing support. The idea is that Fabric could instrument your actual hardware and compare runtime sensor readings against the compile-time uncertainty bounds. If reality diverges from what the compiler predicted, you get a warning before your next flight.&lt;/p&gt;
&lt;p&gt;Also working on proper static loop bound analysis. The current heuristic is fine for demos but useless for real deployments. I'm looking at abstract interpretation techniques to actually prove bounds instead of guessing.&lt;/p&gt;
&lt;p&gt;If any of this sounds interesting, check out the repo. Open an issue. Try it on your own robot. Break it and tell me how.&lt;/p&gt;
&lt;p&gt;That's how this gets better.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Fabric is MIT licensed. The crates are published on crates.io. The docs are in the README because I'm 17 and haven't written proper docs yet.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;gh repo link :- &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/subhansh-dev/fabric&quot;&gt;https://github.com/subhansh-dev/fabric&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22891/i-built-a-programming-language-because-c-almost-killed-my-drone</guid>
<pubDate>Fri, 17 Jul 2026 14:10:22 +0000</pubDate>
</item>
<item>
<title>pnpm v12 Is Being Rewritten in Rust</title>
<link>https://coderlegion.com/22762/pnpm-v12-is-being-rewritten-in-rust</link>
<description>&lt;p&gt;Every package manager has a moment where you just wait. You add one dependency, or you pull a branch and run install, and there's a beat where the terminal sits there doing file I/O while you tab away. &lt;a rel=&quot;nofollow&quot; href=&quot;https://pnpm.io&quot;&gt;pnpm&lt;/a&gt; already made that beat shorter than most, staying ahead of npm and Yarn on most install scenarios thanks to its content-addressable store and hard-linked &lt;code&gt;node_modules&lt;/code&gt;. But it was still a Node.js program, paying the Node startup cost and pushing tens of thousands of file operations through a &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/five-things-you-might-not-know-about-javascript&quot;&gt;single JavaScript runtime&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That is what changes in pnpm v12: it rewrites the install engine, the part that fetches and links packages, in &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.rust-lang.org&quot;&gt;Rust&lt;/a&gt;, and leaves the CLI, lockfile, and &lt;code&gt;node_modules&lt;/code&gt; layout untouched. It's the same move &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/typescript-7-is-here&quot;&gt;TypeScript just made with Go&lt;/a&gt;, aimed at a different bottleneck. And like that one, the interesting detail is how little is supposed to change for you.&lt;/p&gt;
&lt;h3 id=&quot;what-pnpm-v12-actually-is-1&quot;&gt;What pnpm v12 actually is&lt;/h3&gt;
&lt;p&gt;pnpm v12 is the same pnpm you already use, ported to Rust. pnpm has always been TypeScript running on Node.js, and the Rust work lives under the codename &lt;strong&gt;&lt;code&gt;pacquet&lt;/code&gt;&lt;/strong&gt;, whose repo describes it in one line: &quot;The official pnpm rewrite in Rust.&quot; The key word there is &lt;em&gt;rewrite&lt;/em&gt;, not &lt;em&gt;reimagining&lt;/em&gt;. It's a port. The flags, defaults, error codes, lockfile format, and &lt;code&gt;node_modules&lt;/code&gt; layout are all meant to match pnpm exactly.&lt;/p&gt;
&lt;p&gt;Zoltan Kochan, pnpm's creator, &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/orgs/pnpm/discussions/11292&quot;&gt;put it plainly&lt;/a&gt; when the v12 plan came together: &quot;v12 should behave the same as v11, the biggest change will be the full rewrite to Rust.&quot; That's the frame to hold onto. This is a performance release wearing a major version number, not a redesign of how pnpm works.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pacquet&lt;/code&gt; started as a separate experimental repo and &lt;a rel=&quot;nofollow&quot; href=&quot;https://x.com/zkochan/status/2054966567692099943&quot;&gt;moved into the main pnpm monorepo&lt;/a&gt; in May 2026, so the TypeScript and Rust versions now evolve side by side. The eventual goal is for the Rust engine to be what you run.&lt;/p&gt;
&lt;h3 id=&quot;why-did-pnpm-choose-rust--1&quot;&gt;Why did pnpm choose Rust?&lt;/h3&gt;
&lt;p&gt;The motivation isn't abstract. Two things were capping how fast a Node-based pnpm could go.&lt;/p&gt;
&lt;p&gt;The first is startup cost. Every invocation booted a Node.js runtime before it did any work. pnpm v12 ships as native per-platform binaries with &lt;a rel=&quot;nofollow&quot; href=&quot;https://pnpm.io/blog/releases/11.10&quot;&gt;no Node.js launcher&lt;/a&gt;, so a command pays no runtime bootstrap cost at all. On a fast, cached install where the actual work is milliseconds, that bootstrap was a real fraction of the wall-clock time.&lt;/p&gt;
&lt;p&gt;The second is the file I/O itself. A cold install on a large monorepo is really a lot of filesystem operations: fetching tarballs, unpacking them into the store, and hard-linking thousands of files into place. Running all of that through a single JavaScript thread leaves throughput on the table. Rust handles that fan-out natively, without the runtime sitting in the middle of every syscall.&lt;/p&gt;
&lt;p&gt;There's a softer reason too, and it's the same one that's driving the broader trend: the tooling to do this kind of rewrite has gotten dramatically better, and AI-assisted porting made a 1:1 translation of a large codebase far more tractable than it would have been a few years ago.&lt;/p&gt;
&lt;h3 id=&quot;how-much-faster-is-pnpm-v12--1&quot;&gt;How much faster is pnpm v12?&lt;/h3&gt;
&lt;p&gt;pnpm publishes a &lt;a rel=&quot;nofollow&quot; href=&quot;https://pnpm.io/benchmarks&quot;&gt;benchmark suite&lt;/a&gt; comparing v11 against the Rust engine directly. As of this writing (July 2026), here's what it shows:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Scenario                          &lt;/th&gt;&lt;th&gt;pnpm v11 &lt;/th&gt;&lt;th&gt;pnpm (Rust)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Everything warm (repeat install)  &lt;/td&gt;&lt;td&gt;381 ms   &lt;/td&gt;&lt;td&gt;12 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;node_modules present, no cache    &lt;/td&gt;&lt;td&gt;460 ms   &lt;/td&gt;&lt;td&gt;40 ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Clean install (nothing cached)    &lt;/td&gt;&lt;td&gt;6.5 s    &lt;/td&gt;&lt;td&gt;2.2 s&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The warm case is everything already in place, so the install just re-verifies the tree, which is what happens on every CI job. That drops from 381ms to 12ms, roughly a 30x difference, and it's the number you'll feel most, because it runs constantly. The colder cases improve less dramatically but still meaningfully: a fully clean install with nothing cached goes from 6.5s to 2.2s, about 3x.&lt;/p&gt;
&lt;p&gt;Treat these as pnpm's own numbers on pnpm's own hardware, and expect your mileage to vary with disk speed and project size. But the direction is not subtle, and the team's claim is that the Rust engine is faster than v11 in every scenario they measured.&lt;/p&gt;
&lt;h3 id=&quot;it-s-already-rolling-out-incrementally-1&quot;&gt;It's already rolling out, incrementally&lt;/h3&gt;
&lt;p&gt;Here's the part that's easy to get wrong: pnpm v12 isn't a big-bang release. The Rust engine has been landing piece by piece through the v11 line, opt-in first.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;pnpm 11.2&lt;/strong&gt; let you opt into pacquet as the install backend for materialization only. Rust did the fetch and link, while pnpm still resolved dependencies and wrote the lockfile.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pnpm 11.7&lt;/strong&gt; went further: a standard install could be &lt;a rel=&quot;nofollow&quot; href=&quot;https://pnpm.io/blog/releases/11.7&quot;&gt;delegated to pacquet end-to-end&lt;/a&gt;, with resolution, lockfile write, and linking all in a single Rust pass.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pnpm 11.10&lt;/strong&gt; added &lt;code&gt;pnpm self-update next-12&lt;/code&gt;, so you can pull the v12 line and try the Rust binary directly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In v12, the Rust engine for fetching and linking becomes the default rather than an opt-in. The &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/pnpm/pnpm/issues/11633&quot;&gt;official roadmap&lt;/a&gt; then works outward from there: first the headless frozen-lockfile installer, then full dependency resolution (&lt;code&gt;add&lt;/code&gt;, &lt;code&gt;update&lt;/code&gt;, &lt;code&gt;remove&lt;/code&gt;, catalogs, overrides, peers), and finally the remaining commands like &lt;code&gt;run&lt;/code&gt;, &lt;code&gt;store&lt;/code&gt;, and &lt;code&gt;publish&lt;/code&gt;. So even after v12 ships, parts of pnpm will still run through the TypeScript implementation for a while. The lockfile is what lets the two halves cooperate.&lt;/p&gt;
&lt;h3 id=&quot;what-changes-for-you--1&quot;&gt;What changes for you?&lt;/h3&gt;
&lt;p&gt;The honest answer is: not much, on purpose. Same CLI, same lockfile, same &lt;code&gt;node_modules&lt;/code&gt;. The whole point of a 1:1 port is that your muscle memory and your CI scripts keep working.&lt;/p&gt;
&lt;p&gt;The breaking changes slated for v12 are &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/orgs/pnpm/discussions/11292&quot;&gt;deliberately small&lt;/a&gt;: removing some old migration code for git-hosted tarballs, rejecting unscoped auth settings, and deprecating the &lt;code&gt;$&lt;/code&gt; syntax in &lt;code&gt;overrides&lt;/code&gt; in favor of catalogs. If none of those describe your setup, a v12 upgrade should be close to a no-op.&lt;/p&gt;
&lt;h4 id=&quot;will-the-upgrade-break-your-ci--1&quot;&gt;Will the upgrade break your CI?&lt;/h4&gt;
&lt;p&gt;Probably not, but the one thing worth checking is feature parity during the transition. Because the Rust engine is being ported incrementally, some newer or niche capabilities may lag the TypeScript version at any given moment. If your install depends on something specific (a particular linker mode, lockfile-verification settings, or &lt;code&gt;pnpm deploy&lt;/code&gt;), test the bump in an isolated &lt;a rel=&quot;nofollow&quot; href=&quot;https://morello.dev/blog/git-worktrees-are-underrated&quot;&gt;git worktree&lt;/a&gt; before you flip it on in CI. This is moving quickly, so the right list of gaps is whatever the roadmap says on the day you upgrade.&lt;/p&gt;
&lt;h3 id=&quot;the-bigger-picture-1&quot;&gt;The bigger picture&lt;/h3&gt;
&lt;p&gt;pnpm is not doing this alone. The last few years have quietly rebuilt the JavaScript toolchain's foundations in native languages: &lt;a rel=&quot;nofollow&quot; href=&quot;https://biomejs.dev&quot;&gt;Biome&lt;/a&gt; for linting and formatting, &lt;a rel=&quot;nofollow&quot; href=&quot;https://oxc.rs&quot;&gt;Oxc&lt;/a&gt; for parsing and transforming, &lt;a rel=&quot;nofollow&quot; href=&quot;https://rolldown.rs&quot;&gt;Rolldown&lt;/a&gt; and Turbopack for bundling. TypeScript's compiler went to Go. Deno is Rust from the ground up. (Bun is the odd one out: it's written in Zig, not Rust, so don't lump it into the &quot;everything's Rust now&quot; story.)&lt;/p&gt;
&lt;p&gt;There's a competitive angle underneath it, too. Bun and Deno both ship as single fast binaries with package management built in, and a chunk of their appeal is exactly that no-runtime-bootstrap speed. A native-binary pnpm with no Node launcher closes that gap while keeping the thing that makes pnpm pnpm: the strict, content-addressable store and the non-flat &lt;code&gt;node_modules&lt;/code&gt; that catches phantom dependencies.&lt;/p&gt;
&lt;h3 id=&quot;should-you-upgrade-to-pnpm-v12-yet--1&quot;&gt;Should you upgrade to pnpm v12 yet?&lt;/h3&gt;
&lt;p&gt;If you're waiting for a stable release: keep waiting, but not for long. As of mid-July 2026, v12 is in alpha. The latest tag is &lt;code&gt;v12.0.0-alpha.12&lt;/code&gt;, and the stable line is still v11.13.0. This is not something to point production CI at today.&lt;/p&gt;
&lt;p&gt;But if you want to feel the difference now, you already can. Run &lt;code&gt;pnpm self-update next-12&lt;/code&gt; on a branch, or opt into the pacquet backend on your current v11, and watch a warm install that used to take a beat return before you've finished reading the command. The type of release this is, where the language underneath gets an order of magnitude faster while everything on top stays exactly where you left it, is a rare and good one. pnpm was already the fast package manager. It's about to stop being one you ever wait on.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22762/pnpm-v12-is-being-rewritten-in-rust</guid>
<pubDate>Wed, 15 Jul 2026 15:13:28 +0000</pubDate>
</item>
<item>
<title>Postgres vs ClickHouse? I use both. 4 tricks from the split.</title>
<link>https://coderlegion.com/22131/postgres-vs-clickhouse-i-use-both-4-tricks-from-the-split</link>
<description>&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;TL;DR.&lt;/strong&gt; My uptime monitor keeps config and incidents in Postgres and check results in ClickHouse. The split is one rule: does a row ever change? Config gets edited, so it wants Postgres transactions and constraints. A check result is written once and never touched again, so it goes to ClickHouse, where the right codec, a careful sort key, and a per-row TTL make billions of rows cheap. Four tricks and a bonus below, useful even if you only ever run one database.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Every monitor I run writes a row every 20 seconds, from every region, and never stops. One monitor is about 4,300 rows a day, per region. Multiply by every monitor on the platform and the rows only ever go up.&lt;/p&gt;
&lt;p&gt;That stream would slowly crush a normal Postgres table, and watching it is the whole product. So the check results do not live in Postgres. They live in ClickHouse. Everything else, the monitors and incidents and teams, lives in Postgres. The interesting part is the line between them.&lt;/p&gt;
&lt;p&gt;People ask why not one database. &quot;Postgres vs ClickHouse&quot; is the wrong question, because the two are not fighting over the same job. Here is the line I draw, and the one rule under all of it: split your data by how it is written, not by which engine is faster.&lt;/p&gt;
&lt;h3 id=&quot;1-does-the-row-change-that-picks-the-database&quot;&gt;1. Does the row change? That picks the database&lt;/h3&gt;
&lt;p&gt;Forget benchmarks for a second. One question sorts a table into one store or the other: after you write a row, will you ever change it?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;one monitor, two stores:

  you edit a monitor  -&amp;gt;  Postgres   (targets, incidents, team)
                          the row changes, has constraints, lives in a transaction

  a probe checks it   -&amp;gt;  ClickHouse (check_results)
                          one row, written once, never updated
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A monitor is a row that changes. You toggle it off, edit the URL, change the interval. So it lives in Postgres:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE targets (
    id            UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    name          TEXT NOT NULL,
    check_spec    JSONB NOT NULL,
    interval_secs INTEGER NOT NULL CHECK (interval_secs &amp;gt;= 10),
    enabled       BOOLEAN NOT NULL DEFAULT true,
    updated_at    TIMESTAMPTZ NOT NULL DEFAULT now()
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;CHECK&lt;/code&gt; means a 3-second interval can never reach the table, no matter which part of the app tried to write it. This is Postgres doing the thing it is best at: a small set of rows that must stay correct while many callers change them at once.&lt;/p&gt;
&lt;p&gt;A check result is the opposite. It is written once when a probe finishes, and then it never changes. Nothing ever updates it, so it does not need any of that:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE TABLE check_results (
    org_id      UUID,
    target_id   UUID,
    region      LowCardinality(String),
    timestamp   DateTime('UTC') CODEC(DoubleDelta, ZSTD(1)),
    status      Enum8('up' = 1, 'down' = 2, 'degraded' = 3, 'error' = 4),
    duration_ms UInt32 CODEC(T64, ZSTD(1))
) ENGINE = MergeTree
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;No &lt;code&gt;updated_at&lt;/code&gt;, no foreign key, no transaction. Just an append-only stream that grows forever. That is the shape ClickHouse is built for and the shape that slowly hurts Postgres.&lt;/p&gt;
&lt;p&gt;Notice the types too. &lt;code&gt;status&lt;/code&gt; is an &lt;code&gt;Enum8&lt;/code&gt;, one byte on disk, not the string &lt;code&gt;&quot;up&quot;&lt;/code&gt;. &lt;code&gt;region&lt;/code&gt; is &lt;code&gt;LowCardinality&lt;/code&gt;, stored once in a dictionary and referenced by a small id instead of repeating the text on every row. On a table that only ever grows, a byte saved per row is a byte saved times billions.&lt;/p&gt;
&lt;h3 id=&quot;2-the-right-codec-turns-a-day-of-timestamps-into-almost-nothing&quot;&gt;2. The right codec turns a day of timestamps into almost nothing&lt;/h3&gt;
&lt;p&gt;Once the results are in a column store, the type on each column is most of the compression, and the default setting wastes a lot of space.&lt;/p&gt;
&lt;p&gt;Look at that timestamp again. A monitor checks every 20 seconds, so for one monitor a day of timestamps is a run of 20, 20, 20, 20, over and over. &lt;code&gt;DoubleDelta&lt;/code&gt; stores the change in the change. For a steady interval the first delta is a constant 20 and the second delta is zero, so each row after the first packs down to about a bit. The codec is not a ClickHouse invention: it comes from Facebook's Gorilla time-series paper, built for exactly this, measurements taken at a steady rate.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;timestamp   DateTime('UTC')  CODEC(DoubleDelta, ZSTD(1)),
duration_ms UInt32           CODEC(T64, ZSTD(1)),
dns_ms      Nullable(UInt16) CODEC(T64, ZSTD(1)),
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also store the timestamp at whole seconds on purpose. The smallest interval is 20 seconds, so no two checks for one monitor ever land in the same second, and the sub-second detail lives in &lt;code&gt;duration_ms&lt;/code&gt; where it belongs. Whole seconds that step by a fixed amount compress far harder than milliseconds would.&lt;/p&gt;
&lt;p&gt;The latency columns get &lt;code&gt;T64&lt;/code&gt; instead, because they are small integers that stay in a small range, a different shape from a steady clock. &lt;code&gt;T64&lt;/code&gt; crops the unused high bits off a block of values, so a &lt;code&gt;UInt32&lt;/code&gt; that never climbs past a few thousand milliseconds stops paying for all 32 bits. That is the trick worth copying, and it is not the specific codec names. It is that a timestamp ticking by a fixed step and a latency staying in a small range are two different shapes, and telling the database which is which does the work.&lt;/p&gt;
&lt;h3 id=&quot;3-put-the-tenant-in-the-sort-key-not-the-partition&quot;&gt;3. Put the tenant in the sort key, not the partition&lt;/h3&gt;
&lt;p&gt;I got this one wrong first, so learn it from my mistake instead of your own.&lt;/p&gt;
&lt;p&gt;This is a multi-tenant app, so every row carries an &lt;code&gt;org_id&lt;/code&gt; and almost every query filters by it. My first schema partitioned by org, one slot per customer, because it felt tidy. ClickHouse turned that into a flood of tiny parts, the merges could not keep up, and startup got slower the more customers I had. I ripped it out. A partition is not a folder for tidiness. It is a physical unit ClickHouse merges and expires, and you want few large ones, not many small ones.&lt;/p&gt;
&lt;p&gt;Here is what it should be:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;) ENGINE = MergeTree
PARTITION BY toYYYYMMDD(timestamp)
ORDER BY (org_id, target_id, region, timestamp)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;org_id&lt;/code&gt; leads the &lt;code&gt;ORDER BY&lt;/code&gt;, so a per-org query walks the sorted primary index and reads only that org's slice. But it stays out of &lt;code&gt;PARTITION BY&lt;/code&gt;. The rule I follow now: partition by something low-cardinality that you also delete by, here the day, and put the high-cardinality tenant key in the sort order. Sort key answers &quot;find this org fast&quot;. Partition answers &quot;drop old data cheaply&quot;, which is the next trick.&lt;/p&gt;
&lt;h3 id=&quot;4-make-retention-a-number-in-a-row-not-a-schema-change&quot;&gt;4. Make retention a number in a row, not a schema change&lt;/h3&gt;
&lt;p&gt;Different plans keep history for different lengths of time. The clumsy way is a migration or a cleanup job per plan. The clean way is to make the retention window a column:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;ttl_days UInt16 DEFAULT 30 CODEC(ZSTD(1))
) ENGINE = MergeTree
PARTITION BY toYYYYMMDD(timestamp)
ORDER BY (org_id, target_id, region, timestamp)
TTL timestamp + toIntervalDay(ttl_days)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each row carries its own &lt;code&gt;ttl_days&lt;/code&gt;, stamped from the org's plan at the moment it is written. A free plan keeps 30 days, a paid plan keeps more, and changing that needs no &lt;code&gt;ALTER&lt;/code&gt;, no migration, no backfill. The next write just stores a different number. The daily partitions line up with the TTL, so ClickHouse expires old data by dropping whole parts, close to free, and a whole column of the same &lt;code&gt;30&lt;/code&gt; compresses away to nothing. The flexibility costs no space.&lt;/p&gt;
&lt;p&gt;One more piece makes reading that history cheap. A materialized view rolls raw checks into per-minute and per-hour summaries as they land:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE MATERIALIZED VIEW check_results_1m
ENGINE = AggregatingMergeTree
ORDER BY (org_id, target_id, region, minute)
AS SELECT
    org_id, target_id, region,
    toStartOfMinute(timestamp)  AS minute,
    countState()                AS total_checks,
    countIfState(status = 'up') AS up_checks,
    avgState(duration_ms)       AS avg_duration_ms
FROM check_results
GROUP BY org_id, target_id, region, minute;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So a dashboard showing 30 days across a thousand monitors reads a few thousand minute-buckets, not millions of raw rows. Recent views read the minute rollup, long history reads an hour rollup, and the raw rows underneath expire on their own TTL. The firehose is there when you need to drill into one bad minute, and left alone the rest of the time.&lt;/p&gt;
&lt;h3 id=&quot;one-more-because-everyone-hits-it-make-inserts-idempotent&quot;&gt;One more, because everyone hits it: make inserts idempotent&lt;/h3&gt;
&lt;p&gt;Here is the trick I wish someone had told me first. My agents batch check results and send them to ClickHouse. A network blip after the server commits but before my side gets the ack means the batch retries and sends the exact same block again. Without protection, that double-counts every row in it, and your uptime numbers quietly drift.&lt;/p&gt;
&lt;p&gt;ClickHouse has a fix built in, but for a plain (non-Replicated) MergeTree it is off until you turn it on:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;SETTINGS index_granularity = 8192,
         non_replicated_deduplication_window = 1000;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The server hashes each inserted block and remembers the last 1,000 hashes. A retry sends an identical block, the hash matches, and the server drops it instead of appending it again. The retry becomes safe to do blindly, so the client stays simple: send, and if unsure, send again. Make the window bigger than the most blocks a single retry could resend, and you stop having to worry about duplicate writes.&lt;/p&gt;
&lt;h3 id=&quot;so-one-database-or-two-&quot;&gt;So, one database or two?&lt;/h3&gt;
&lt;p&gt;For almost every app, one. If your data fits in Postgres and stays fast, a second database is a cost you should not pay: two schemas, two clients, two things to back up and think about.&lt;/p&gt;
&lt;p&gt;Reach for the second store only when one table stops looking like the rest of your tables. For me that table was &lt;code&gt;check_results&lt;/code&gt;. It is written once and never edited, it grows without end, and every question I ask it is a summary over a time range. That is a different shape from my monitors and my incidents, so it wanted a different database.&lt;/p&gt;
&lt;p&gt;The rule I would give my past self: do not split by &quot;which database is faster&quot;. Split by how the data is written. Rows that change and must stay correct want Postgres. An append-only stream you only ever summarize wants a column store like ClickHouse. Most of the tricks above are that one idea pushed down into the schema.&lt;/p&gt;
&lt;p&gt;Uptimepage is open source, AGPL-3.0, and both schemas are in the repo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/uptimepage/uptimepage&quot;&gt;github.com/uptimepage/uptimepage&lt;/a&gt;. The probe that writes those rows is &lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev/blog/http-prober-in-rust-no-reqwest&quot;&gt;its own post&lt;/a&gt;, and the wider build story, one binary and two databases, is &lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev/blog/building-an-uptime-monitor-in-rust&quot;&gt;here&lt;/a&gt;. Or &lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev&quot;&gt;start free on the hosted tier&lt;/a&gt; and point a check at something.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22131/postgres-vs-clickhouse-i-use-both-4-tricks-from-the-split</guid>
<pubDate>Thu, 09 Jul 2026 05:57:37 +0000</pubDate>
</item>
<item>
<title>4 things writing an HTTP prober in Rust taught me</title>
<link>https://coderlegion.com/22048/4-things-writing-an-http-prober-in-rust-taught-me</link>
<description>&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;TL;DR.&lt;/strong&gt; An uptime prober is a strange HTTP client, so I swapped reqwest for raw hyper. It needs the opposite of what a normal client gives you: no connection pool, so the cold path gets measured instead of hidden; every phase timed, even when connect fails; a hard SSRF block on the URL the user typed; and exact failure reasons instead of one flat error. Four tricks below, each worth copying even if you never build a monitor.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I built an uptime checker in Rust. Like most people, I used &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/seanmonstar/reqwest&quot;&gt;reqwest&lt;/a&gt; first. Then I dropped it and moved down to raw &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/hyperium/hyper&quot;&gt;hyper&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Not because reqwest is slow. It is great, and for app code you should use it. But a prober is a strange kind of HTTP client. It makes one request, and its whole job is to measure that request and report exactly what happened. A normal client is built to do the opposite: make requests fast and hide the messy details. Every trick below is a messy detail I needed to keep.&lt;/p&gt;
&lt;p&gt;Here are four things building it taught me. Each one is a habit worth copying, even if you never write a monitor.&lt;/p&gt;
&lt;h3 id=&quot;1-a-connection-pool-lies-to-a-monitor&quot;&gt;1. A connection pool lies to a monitor&lt;/h3&gt;
&lt;p&gt;The first thing reqwest gives you is a connection pool, and it is the first thing I had to remove.&lt;/p&gt;
&lt;p&gt;A pool keeps TCP connections open and reuses them. The second request to a host skips DNS, skips the TCP handshake, skips TLS, and comes back much faster. For app code that is a free speed boost. For a monitor it is a lie. If I reuse a warm connection, the &quot;connect time&quot; I report is near zero, because I did not connect, I borrowed. A user watching a slow TLS handshake slowly rise in one region would see nothing, because the pool hid the handshake.&lt;/p&gt;
&lt;p&gt;So the connector makes exactly one request per connection, then drops it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;/// No pooling: the caller drives exactly one request over the returned
/// stream, then drops it.
pub(crate) async fn timed_connect(
    p: &amp;amp;ConnectParams&amp;lt;'_&amp;gt;,
    host: &amp;amp;str,
    port: u16,
    is_https: bool,
) -&amp;gt; Result&amp;lt;TimedConnection, ConnectError&amp;gt; {
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Every check pays the full cost of a fresh connection, because the full cost is the thing I am trying to measure. The lesson is general: if you are timing a request, a warm pool is measuring the wrong thing. Reuse is a feature that erases exactly the numbers a monitor exists to show.&lt;/p&gt;
&lt;h3 id=&quot;2-put-the-timings-inside-the-error&quot;&gt;2. Put the timings inside the error&lt;/h3&gt;
&lt;p&gt;Splitting a request into DNS, TCP, TLS, and time to first byte is easy on the happy path. The interesting question is what happens when connect fails.&lt;/p&gt;
&lt;p&gt;The naive design loses everything on failure. You get an &lt;code&gt;Err&lt;/code&gt; and no numbers, so a hung TLS handshake looks the same as a dead DNS server. Both are just &quot;failed.&quot; That is the worst moment to have no data.&lt;/p&gt;
&lt;p&gt;The fix is small, and I now use it everywhere: make the error carry the phases that finished before the one that broke.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;pub(crate) enum ConnectError {
    Dns(anyhow::Error),
    NoAddrs { dns_ms: u16 },
    Connect { err: io::Error, dns_ms: u16 },
    Tls { err: io::Error, dns_ms: u16, connect_ms: u16 },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A TLS failure still reports how long DNS and TCP took. So when a cert handshake hangs, the chart still shows DNS was 12ms, connect was 45ms, and then TLS took the rest. The shape of the error tells you what broke. You are never left guessing which layer stalled, even on the path where everything went wrong. reqwest gives you one flat &lt;code&gt;Error&lt;/code&gt; at the very end. Here the error type holds the answer.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;one probe, one fresh connection, each phase on its own clock:

  DNS         TCP connect      TLS handshake     request + TTFB
  +-----------+----------------+-----------------+----------------&amp;gt;
   12ms        45ms             (hung)            never reached

  TLS failed. The error still carries dns_ms=12, connect_ms=45,
  so you know exactly which layer stalled, not just &quot;it failed&quot;.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;3-the-ssrf-check-goes-after-dns-not-before&quot;&gt;3. The SSRF check goes after DNS, not before&lt;/h3&gt;
&lt;p&gt;This is the one most people building a fetch-any-URL feature get wrong, and it is the scary one.&lt;/p&gt;
&lt;p&gt;A checker fetches whatever URL the user typed. That is the feature. It is also a classic &lt;a rel=&quot;nofollow&quot; href=&quot;https://owasp.org/www-community/attacks/Server_Side_Request_Forgery&quot;&gt;SSRF&lt;/a&gt; engine. Someone signs up, points a check at &lt;code&gt;http://169.254.169.254/latest/meta-data/&lt;/code&gt;, and now your prober is reading cloud credentials from inside your own network and showing them the reply. Point it at &lt;code&gt;http://10.0.0.5:6379&lt;/code&gt; and it is a port scanner for your private subnet.&lt;/p&gt;
&lt;p&gt;The trap is thinking you can block this by checking the hostname. You cannot. The classic bypass is DNS rebinding: the name looks public and passes your check, but it resolves to &lt;code&gt;127.0.0.1&lt;/code&gt;. The block has to happen after resolution, on the real IP you are about to dial:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;let addrs: Vec&amp;lt;SocketAddr&amp;gt; = p
    .resolver
    .resolve_addrs(host)
    .await
    .map_err(ConnectError::Dns)?
    .into_iter()
    .filter(|ip| p.ssrf_guard.allow(*ip))   // block loopback, private, link-local, metadata
    .map(|ip| SocketAddr::new(ip, port))
    .collect();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Resolve first, filter every resolved IP, then connect only to what survives. And because each redirect hop reconnects through this same guarded path, a &lt;code&gt;Location:&lt;/code&gt; header pointing at &lt;code&gt;169.254.169.254&lt;/code&gt; gets rejected exactly like a directly typed one. The safety sits in the single place all connections pass through, so there is nothing to forget on the redirect path. If you ever fetch a user-supplied URL server-side, this is the check that matters, and the hostname version is not it.&lt;/p&gt;
&lt;h3 id=&quot;4-stop-grepping-your-error-strings&quot;&gt;4. Stop grepping your error strings&lt;/h3&gt;
&lt;p&gt;When a check goes red the user gets one line, and that line has to be right. &quot;Something went wrong&quot; trains people to ignore alerts. &quot;Certificate expired&quot; tells them what to fix in five seconds.&lt;/p&gt;
&lt;p&gt;The tempting way to produce that line is to match on error message text. It is a trap: those strings change between library versions and read differently on every OS. The message is not an API. So instead the connector downcasts to the real error and reads the reason straight out of it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn tls_reason(io: &amp;amp;io::Error) -&amp;gt; &amp;amp;'static str {
    use rustls::CertificateError;
    let Some(rustls::Error::InvalidCertificate(cert)) =
        io.get_ref().and_then(|e| e.downcast_ref::&amp;lt;rustls::Error&amp;gt;())
    else {
        return &quot;tls&quot;;
    };
    match cert {
        CertificateError::Expired =&amp;gt; &quot;certificate expired&quot;,
        CertificateError::UnknownIssuer =&amp;gt; &quot;certificate not trusted&quot;,
        CertificateError::NotValidForName =&amp;gt; &quot;certificate hostname mismatch&quot;,
        CertificateError::Revoked =&amp;gt; &quot;certificate revoked&quot;,
        _ =&amp;gt; &quot;certificate invalid&quot;,
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;An expired cert, an untrusted issuer, and a hostname mismatch are three different problems with three different fixes, and the user should never have to guess which one they have. The &lt;code&gt;io::ErrorKind&lt;/code&gt; and the rustls error already carry the truth. The trick is to refuse to flatten it into a string and then re-parse the string you just made.&lt;/p&gt;
&lt;h3 id=&quot;so-reqwest-or-not-&quot;&gt;So, reqwest or not?&lt;/h3&gt;
&lt;p&gt;For your program? Almost certainly reqwest. Going down to hyper means you own redirects, decompression with a size cap, timeout budgeting per hop, and the request-target rules for HTTP/1 versus HTTP/2. That is real code with real edge cases, and every one is a place reqwest would have been correct for free. If I needed to fetch a config file at startup, writing this connector would be a mistake.&lt;/p&gt;
&lt;p&gt;It is worth it here for one reason: measuring and guarding the request is the product, not a detail of it. When the request itself is the thing you sell, you want to own every millisecond and every IP it touches.&lt;/p&gt;
&lt;p&gt;Uptimepage is open source, AGPL-3.0, and the whole probe path is in the repo: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/uptimepage/uptimepage&quot;&gt;github.com/uptimepage/uptimepage&lt;/a&gt;. If you want the wider build story, one binary and two databases, &lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev/blog/building-an-uptime-monitor-in-rust&quot;&gt;that is a separate post&lt;/a&gt;. Or just &lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev&quot;&gt;start free on the hosted tier&lt;/a&gt; and point a check at something.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22048/4-things-writing-an-http-prober-in-rust-taught-me</guid>
<pubDate>Tue, 07 Jul 2026 14:10:44 +0000</pubDate>
</item>
<item>
<title>This Smart-Home Agent Treats Its Own 1B Model as Untrusted Input</title>
<link>https://coderlegion.com/22040/this-smart-home-agent-treats-its-own-1b-model-as-untrusted-input</link>
<description>&lt;p&gt;Reliability in an AI agent is a harness property, not a model property.&lt;/p&gt;
&lt;p&gt;The cleanest proof arrived at the bottom of the model-size ladder: a 688 MB model controlling a smart home, showcased by the model's own maker. The part worth studying is the 25 MB of Rust wrapped around it.&lt;/p&gt;
&lt;p&gt;EdgeHome Harness pairs MiniCPM5-1B, 688 MB on disk and about 1 GB at runtime, with a Rust harness between 25 and 64 MB, targeting devices with 2 GB of RAM. A community developer built it; OpenBMB put it on stage. The line everyone will quote is that a full agent stack fits on ultra-constrained edge hardware. The design decision that matters is different: the model is not allowed to do anything.&lt;/p&gt;
&lt;h3 id=&quot;everything-after-the-json-is-deterministic&quot;&gt;Everything After the JSON Is Deterministic&lt;/h3&gt;
&lt;p&gt;EdgeHome Harness is a Rust framework that makes a 1B model act as a reliable smart-home controller. The model only generates a candidate JSON command. Deterministic Rust code validates the schema, resolves devices against a registry, checks capabilities, applies fail-closed policy gates, plans a dry run, and records a replayable trace.&lt;/p&gt;
&lt;p&gt;The repo states the thesis outright: the model sits at the top of the pipeline, and everything after candidate generation belongs to the harness. There is a trust table in the docs. The model's output type is labeled untrusted. Not partially trusted. Untrusted, the way you treat user input in a web form.&lt;/p&gt;
&lt;p&gt;The harness ships payload adapters for Home Assistant, MQTT, and MIoT or Matter bridges. The model itself talks to nothing but Ollama, through a structured output request.&lt;/p&gt;
&lt;h3 id=&quot;the-model-is-the-first-thing-sacrificed&quot;&gt;The Model Is the First Thing Sacrificed&lt;/h3&gt;
&lt;p&gt;A 1B model repeats itself, rambles past the closing brace, and emits JSON that almost parses. EdgeHome budgets for all three: an output governor with dead-loop detection, a retry policy, and fallback classification. Short-term memory stays out of the context window too; Rust tracks the last target and confirmed aliases, because context is the scarcest resource on a 2 GB board.&lt;/p&gt;
&lt;p&gt;The sharpest detail is the pressure policy. When memory runs low, the harness shrinks the model's context, then its output budget, and under critical pressure drops the model entirely and falls back to rules. In this architecture, the language model is the most disposable component in the stack. Execution backends ship disabled by default, every plan is a dry run first, and a 108-case eval gate decides releases.&lt;/p&gt;
&lt;h3 id=&quot;the-same-boundary-holds-at-frontier-scale&quot;&gt;The Same Boundary Holds at Frontier Scale&lt;/h3&gt;
&lt;p&gt;I run the identical pattern at the opposite end of the size ladder. My coding agent works inside a sandbox where the harness owns the filesystem and network boundaries, whatever the model believes. My wallet experiment puts a policy gate with spend caps and an allowlist between an agent and the chain, because an approval step the agent can call is an approval step the agent can forge. Enforcement lives outside the model or it does not exist.&lt;/p&gt;
&lt;p&gt;I have made this argument for &lt;a rel=&quot;nofollow&quot; href=&quot;https://renezander.com/blog/sandbox-ai-coding-agent/&quot;&gt;coding agents&lt;/a&gt; and for &lt;a rel=&quot;nofollow&quot; href=&quot;https://renezander.com/blog/ai-harness-cheap-model/&quot;&gt;cheap-model pipelines&lt;/a&gt;. EdgeHome is the same argument compiled to 25 MB and pointed at a light switch.&lt;/p&gt;
&lt;p&gt;This project settles a different question than the one in the announcement. Not whether a 1B model can run your smart home. How few decisions a model of any size should be making. Trace tonight's agent failures back to their source: how many were the model being too small, and how many were your harness letting the model decide something a validator should own? EdgeHome's answer is a trust table. Most agent stacks never wrote one.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;I write field notes from real builds — AI integration, cron-driven automation, and the parts that break in production. New posts every two weeks; if this one was useful, &lt;a rel=&quot;nofollow&quot; href=&quot;https://renezander.com/hitl-approval/&quot;&gt;notes on human-in-the-loop approval boundaries for agents&lt;/a&gt; is the companion download.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/22040/this-smart-home-agent-treats-its-own-1b-model-as-untrusted-input</guid>
<pubDate>Tue, 07 Jul 2026 12:19:43 +0000</pubDate>
</item>
<item>
<title>The Security Liability of Memory Allocation in TEEs: A Design Decision Log (no_std Rust)</title>
<link>https://coderlegion.com/21792/the-security-liability-of-memory-allocation-in-tees-a-design-decision-log-nostd-rust</link>
<description>&lt;p&gt;Memory allocation is not a feature — it is a security liability.&lt;/p&gt;
&lt;p&gt;In high-assurance Trusted Execution Environments (TEEs), you cannot afford the jitter or the fragmentation of a probabilistic global heap. When building the sakshi-core attestation loop for the Sovereign Spine architecture, the requirement was absolute: determinism.&lt;/p&gt;
&lt;p&gt;Following my previous teardown on binding LLM context to a TPM root of trust, this post shifts the focus to the memory substrate itself — specifically, why &lt;code&gt;GlobalAlloc&lt;/code&gt; creates an unacceptable attack surface within an enclave.&lt;/p&gt;
&lt;h3 id=&quot;the-problem-why-globalalloc-fails-the-tee-test&quot;&gt;The Problem: Why GlobalAlloc Fails the TEE Test&lt;/h3&gt;
&lt;p&gt;In a standard Rust environment, we lean on the global allocator. In a TEE, however, the global allocator is a massive attack surface.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Jitter:&lt;/strong&gt; Allocation time varies based on heap state, leaking metadata through timing side-channels.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Fragmentation:&lt;/strong&gt; Heap fragmentation can lead to unpredictable exhaustion, a vector for Denial of Service (DoS) within the enclave.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TCB Bloat:&lt;/strong&gt; The allocator logic itself adds thousands of lines of code to your audit surface, increasing the Trusted Computing Base (TCB) size beyond what is strictly necessary.&lt;/p&gt;
&lt;h3 id=&quot;the-solution-session-scoped-bump-buffer&quot;&gt;The Solution: Session-Scoped Bump Buffer&lt;/h3&gt;
&lt;p&gt;To enforce architectural certainty, I stripped away the dependency on standard heap allocation in the enclave. Instead, I implemented a session-scoped bump buffer.&lt;/p&gt;
&lt;p&gt;This is a contract-based memory model:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Constant-time execution:&lt;/strong&gt; Allocation is a pointer increment operation, taking 1-2 CPU cycles.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Zero-fragmentation:&lt;/strong&gt; Memory is allocated linearly and cleared atomically at the session boundary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Simplified TCB:&lt;/strong&gt; By removing GlobalAlloc, the enclave memory logic is reduced to a handful of lines of verifiable code.&lt;/p&gt;
&lt;h3 id=&quot;implementation-concept&quot;&gt;Implementation Concept&lt;/h3&gt;
&lt;p&gt;The core logic relies on a pre-allocated static region. We do not ask the system for memory; we own a dedicated slab of silicon-backed memory and manage it strictly within the request lifecycle.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Conceptual implementation of the session-scoped buffer
pub struct BumpBuffer {
    buffer: &amp;amp;'static mut [u8],
    offset: usize,
}

impl BumpBuffer {
    pub fn alloc(&amp;amp;mut self, size: usize) -&amp;gt; Option&amp;lt;&amp;amp;mut [u8]&amp;gt; {
        if self.offset + size &amp;lt;= self.buffer.len() {
            let start = self.offset;
            self.offset += size;
            Some(&amp;amp;mut self.buffer[start..self.offset])
        } else {
            None // Enforce strict limit, no OOM panic
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-architectural-takeaway&quot;&gt;The Architectural Takeaway&lt;/h3&gt;
&lt;p&gt;We must stop hoping for performance and start architecting for it. By moving the decision boundary from the runtime heap to a statically-defined buffer, we remove entire classes of vulnerabilities.&lt;/p&gt;
&lt;p&gt;In the Sovereign Spine, the memory model is not just an implementation detail—it is a security guarantee.&lt;/p&gt;
&lt;h3 id=&quot;explore-the-implementation&quot;&gt;Explore the Implementation&lt;/h3&gt;
&lt;p&gt;The architectural patterns discussed here—including the session-scoped bump buffer — are part of the &lt;code&gt;sakshi-core&lt;/code&gt; module within the &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/webMethodMan/citadel-protocol&quot;&gt;Citadel Protocol Repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;View the code: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/webMethodMan/citadel-protocol/tree/main/sakshi-core&quot;&gt;citadel-protocol/sakshi-core&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21792/the-security-liability-of-memory-allocation-in-tees-a-design-decision-log-nostd-rust</guid>
<pubDate>Fri, 03 Jul 2026 00:54:14 +0000</pubDate>
</item>
<item>
<title>Building an uptime monitor in Rust: one binary, two databases, 130K checks/sec per core</title>
<link>https://coderlegion.com/21623/building-an-uptime-monitor-in-rust-one-binary-two-databases-130k-checks-sec-per-core</link>
<description>&lt;p&gt;I spent the last few months building &lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev&quot;&gt;Uptimepage&lt;/a&gt;, an open-source uptime monitor and status page written in Rust. This post is the build story: the decisions that shaped it, the parts I rewrote, and the numbers that came out the other side.&lt;/p&gt;
&lt;p&gt;The whole thing ships as one self-contained binary of about 23 MB, plus Postgres and ClickHouse. You can &lt;code&gt;docker compose up&lt;/code&gt; and self-host it, or use the hosted tier. Source is AGPL-3.0 on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/uptimepage/uptimepage&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id=&quot;why-one-binary&quot;&gt;Why one binary&lt;/h3&gt;
&lt;p&gt;A status page is a small surface with a lot of moving parts behind it: a scheduler, probe workers, an HTTP client, a time-series writer, an incident detector, an alerting fan-out, a web UI, a JSON API. The usual answer is a handful of services and a message bus between them.&lt;/p&gt;
&lt;p&gt;I went the other way. One process, one binary, the same image whether it runs the control plane or a remote probe. No queue to operate, no version skew between services, no &quot;which container is wedged&quot; at 3am. The cost is that you have to be careful about what shares a thread and what can stall what. Most of the engineering below is about keeping those boundaries clean inside a single process.&lt;/p&gt;
&lt;p&gt;Stack: Rust 1.95 (edition 2024), Tokio, Axum 0.8, Askama for compile-time HTML templates, HTMX 2 for partial swaps so there is no SPA framework to ship. The API stays the single source of truth because every UI mutation hits the same &lt;code&gt;/api/v1/*&lt;/code&gt; endpoint a script would.&lt;/p&gt;
&lt;h3 id=&quot;two-databases-on-purpose&quot;&gt;Two databases, on purpose&lt;/h3&gt;
&lt;p&gt;Monitors are low-cardinality relational data that gets mutated by API calls: targets, regions, channels, incidents, plans. That is Postgres. Check results are append-only, high-cardinality, and almost always queried by time range. That is ClickHouse.&lt;/p&gt;
&lt;p&gt;Trying to force one of those into the other is where uptime monitors usually fall over. Putting billions of check results in Postgres turns every dashboard query into a sequential scan. Putting your relational config in ClickHouse means fighting its update model forever. So: Postgres 18 for the world, ClickHouse 26.3 for the firehose. Both run their migrations at process startup, so there is no separate migrator to forget.&lt;/p&gt;
&lt;h3 id=&quot;the-http-client-i-did-not-want-to-write&quot;&gt;The HTTP client I did not want to write&lt;/h3&gt;
&lt;p&gt;The first version used a popular high-level HTTP client. It worked, but a monitor is a weird HTTP workload: you connect once per target per interval, you never reuse the connection, and you care about the timing of each phase more than the body.&lt;/p&gt;
&lt;p&gt;So I dropped down to &lt;code&gt;hyper&lt;/code&gt; and &lt;code&gt;hyper-util&lt;/code&gt; with &lt;code&gt;rustls&lt;/code&gt; and built a connector that times DNS resolution, TCP connect, and the TLS handshake as separate numbers, then runs the request over &lt;code&gt;hyper::client::conn&lt;/code&gt; and aborts the connection task the moment the body is read. Each result carries &lt;code&gt;dns_ms&lt;/code&gt;, &lt;code&gt;connect_ms&lt;/code&gt;, &lt;code&gt;tls_ms&lt;/code&gt;, and &lt;code&gt;ttfb_ms&lt;/code&gt; as distinct columns, which is what makes &quot;it got slow but it is the DNS, not your server&quot; a thing the dashboard can actually say.&lt;/p&gt;
&lt;p&gt;The rewrite paid for itself. On a single core the client sustains around 130K checks/sec at saturation, roughly 7.7 microseconds per check. That was a 44 to 56 percent throughput gain over the old path. A chunk of it was a &lt;code&gt;url::parse&lt;/code&gt; call hiding in the redirect policy that cost 7.5 percent on its own and just vanished. Two cores get to about 153K. Scaling goes sub-linear past four cores because of shared HTTP/2 connection state and the pool mutex, which is a fine problem to have for this workload.&lt;/p&gt;
&lt;p&gt;These numbers come from a laptop and a &lt;code&gt;loadtest&lt;/code&gt; binary, so I treat them as regression detection, not capacity planning. The headline in-process run pushed 252K requests/sec sustained at p99 393ms. The design goal is around 50K concurrent in-flight checks per node with under 50ms p99 of per-check overhead on top of the network.&lt;/p&gt;
&lt;h3 id=&quot;one-heap-not-a-timer-per-target&quot;&gt;One heap, not a timer per target&lt;/h3&gt;
&lt;p&gt;The naive scheduler spawns a timer task per monitor. That falls apart at fleet size: thousands of tasks, thousands of wakeups, memory that grows with the number of targets.&lt;/p&gt;
&lt;p&gt;Instead there is a single driver task that owns one &lt;code&gt;BinaryHeap&amp;lt;Reverse&amp;lt;Due&amp;gt;&amp;gt;&lt;/code&gt;, a min-heap keyed by the next due &lt;code&gt;Instant&lt;/code&gt; for the whole fleet. Memory stays flat in fleet size. Each target gets a deterministic jitter offset hashed from its UUID so a thousand monitors on a 60s interval do not all fire on the same tick. Generation and sequence counters mean a re-scheduled target supersedes its stale heap entry instead of double-firing. The registry refresh that pulls config from Postgres runs on its own task with exponential backoff, so a Postgres hiccup never stalls dispatch: the scheduler keeps running on what it last knew.&lt;/p&gt;
&lt;h3 id=&quot;failure-isolation-inside-one-process&quot;&gt;Failure isolation inside one process&lt;/h3&gt;
&lt;p&gt;Because everything is one process, one bad target cannot be allowed to take down the rest. Three patterns do most of that work. Per-host circuit breakers trip when a host keeps failing, so it fails fast with &lt;code&gt;circuit_open&lt;/code&gt; instead of tying up a worker on a timeout, then probes half-open after a cooldown. A per-tenant host throttle bulkhead caps how many checks can be in flight against one host at once; over the cap, a check is recorded as throttled and degraded rather than piling on, and it never pages. And singleflight on RDAP collapses domain-expiry checks for the same domain across many tenants into one upstream probe, with sticky last-good state so a flaky registrar does not flip the monitor red.&lt;/p&gt;
&lt;p&gt;The worker pool itself is a task-per-dispatch gated by a semaphore sized to a max-concurrency setting, with an SSRF guard filtering resolved IPs before any connect.&lt;/p&gt;
&lt;h3 id=&quot;modeling-time-series-in-clickhouse&quot;&gt;Modeling time series in ClickHouse&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;check_results&lt;/code&gt; table is a &lt;code&gt;MergeTree&lt;/code&gt; ordered by &lt;code&gt;(org_id, target_id, region, timestamp)&lt;/code&gt;. The &lt;code&gt;org_id&lt;/code&gt; leads the sort key so each tenant gets a sparse-index slice, but it is deliberately kept out of the partition key (partition is by day) so we do not end up with millions of partitions.&lt;/p&gt;
&lt;p&gt;The columns are where the storage savings live:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Timestamps are &lt;code&gt;DateTime('UTC')&lt;/code&gt; with &lt;code&gt;CODEC(DoubleDelta, ZSTD(1))&lt;/code&gt;. Check intervals are near-constant, so DoubleDelta crushes the gaps to almost nothing.&lt;/li&gt;
&lt;li&gt;Numeric phase columns (&lt;code&gt;duration_ms&lt;/code&gt;, &lt;code&gt;dns_ms&lt;/code&gt;, &lt;code&gt;connect_ms&lt;/code&gt;, &lt;code&gt;tls_ms&lt;/code&gt;, &lt;code&gt;ttfb_ms&lt;/code&gt;, response code and size) use &lt;code&gt;CODEC(T64, ZSTD(1))&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;region&lt;/code&gt; and &lt;code&gt;agent_id&lt;/code&gt; are &lt;code&gt;LowCardinality(String)&lt;/code&gt;, status is an &lt;code&gt;Enum8&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Retention is per row. There is a &lt;code&gt;ttl_days&lt;/code&gt; column with &lt;code&gt;TTL timestamp + toIntervalDay(ttl_days)&lt;/code&gt;, stamped from the org's plan at write time. A plan that buys longer history needs zero schema change: new rows just carry a bigger number.&lt;/p&gt;
&lt;p&gt;On top of the raw table sit two &lt;code&gt;AggregatingMergeTree&lt;/code&gt; materialized views: a per-minute rollup kept 30 days and an hourly rollup kept 13 months, both holding &lt;code&gt;quantilesState&lt;/code&gt; for p50/p95/p99 and per-status counts. Reads route by range: anything inside 30 days hits the minute rollup, older ranges hit the hour rollup, and raw reads are capped at a 90-day span. A dashboard asking for &quot;last 24h p99 latency&quot; never touches a raw row.&lt;/p&gt;
&lt;h3 id=&quot;regional-probes-without-a-second-brain&quot;&gt;Regional probes without a second brain&lt;/h3&gt;
&lt;p&gt;You can run probes in multiple regions, but I did not want each region to be its own little system. So an agent is the same binary in agent mode, running as a stateless probe with no database, no web, no alerting. Adding a region adds execution capacity, never a second control plane.&lt;/p&gt;
&lt;p&gt;An agent pulls its region's config from the control plane with ETag/304 handling, serves its last-known config if the control plane blips, and pauses if its token is revoked. It ships results back in batches that reuse one UUID across retries so a lost ack cannot double-count. Region and agent identity are derived server-side from the bearer token, never sent in the payload, so a probe cannot claim to be somewhere it is not. A separate long-poll loop handles interactive &quot;check now&quot; so a button press in the UI runs on a real remote probe within milliseconds.&lt;/p&gt;
&lt;p&gt;Region is the partition dimension end to end, so every read can slice by region: per-region latency series, per-region incident scope, &quot;down in Singapore, up in Helsinki.&quot;&lt;/p&gt;
&lt;h3 id=&quot;incidents-as-a-follower-not-a-gatekeeper&quot;&gt;Incidents as a follower, not a gatekeeper&lt;/h3&gt;
&lt;p&gt;The incident detector is a background task that follows the &lt;code&gt;check_results&lt;/code&gt; stream and writes into the Postgres &lt;code&gt;incidents&lt;/code&gt; table. The rule I held to: it never touches the hot write path, never gates check execution, and never produces alerts directly.&lt;/p&gt;
&lt;p&gt;The detection itself is boring on purpose: two or more consecutive unhealthy results with no open incident opens one, two or more consecutive healthy results closes it. A small flap threshold absorbs single-result blips. The cross-tenant walk is keyset-paginated so memory stays bounded as tenants grow, and a unique index on open incidents resolves the race when two ticks try to open the same one: only the winner pages.&lt;/p&gt;
&lt;p&gt;Opening or resolving fires a non-blocking signal to the escalation engine, which does repeat-until-acknowledged paging on a per-monitor cadence across about fourteen transports (Slack, generic signed webhooks, Telegram, PagerDuty, ntfy, Pushover, Discord, email, and more), with sharded per-incident locks so a reconcile and an inbound signal can never double-page the same episode. Channel secrets are sealed at rest with AES-GCM and never echoed back.&lt;/p&gt;
&lt;h3 id=&quot;automation-as-a-first-class-surface&quot;&gt;Automation as a first-class surface&lt;/h3&gt;
&lt;p&gt;Because the API is the single source of truth, the rest came almost for free:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A self-describing OpenAPI 3.1 spec with Swagger UI.&lt;/li&gt;
&lt;li&gt;An official &lt;a rel=&quot;nofollow&quot; href=&quot;https://registry.terraform.io/providers/uptimepage/uptimepage&quot;&gt;Terraform provider&lt;/a&gt; so you can manage monitors and notification channels as code.&lt;/li&gt;
&lt;li&gt;An &lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev&quot;&gt;MCP server&lt;/a&gt; so an LLM client can query your monitors and incidents over OAuth 2.1, scope-gated and audited, with per-action confirmation on the few write tools.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;where-it-is&quot;&gt;Where it is&lt;/h3&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev&quot;&gt;Uptimepage&lt;/a&gt; is live, free to start with no card, and AGPL-3.0 open source. The core is not paywalled: checks, status pages, subscribers, the API, and every alert channel are in the free tier. It monitors itself and serves its own status badges from the running binary, which is the most honest dogfood I could think of.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Site and hosted tier: &lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev&quot;&gt;https://uptimepage.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/uptimepage/uptimepage&quot;&gt;https://github.com/uptimepage/uptimepage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;More build notes: &lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev/blog&quot;&gt;https://uptimepage.dev/blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy to answer anything about the Rust internals in the comments.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21623/building-an-uptime-monitor-in-rust-one-binary-two-databases-130k-checks-sec-per-core</guid>
<pubDate>Tue, 30 Jun 2026 17:20:57 +0000</pubDate>
</item>
<item>
<title>uptimepage</title>
<link>https://coderlegion.com/21622/uptimepage</link>
<description>&lt;p&gt;Uptimepage is an open-source uptime monitor and status page, built as one self-contained Rust binary backed by Postgres and ClickHouse. Run it with &lt;code&gt;docker compose up&lt;/code&gt;, or use the free hosted tier.&lt;/p&gt;
&lt;p&gt;I built it because I wanted a monitor I could run as a single binary and drive entirely as code, without operating a fleet of services to do it.&lt;/p&gt;
&lt;h3 id=&quot;what-it-does&quot;&gt;What it does&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Checks: HTTP, TCP, DNS, TLS-certificate expiry, and domain expiry, from one region or many.&lt;/li&gt;
&lt;li&gt;Public status page: HTML, JSON, and RSS, per-component opt-in, incidents, maintenance windows, email and webhook subscribers.&lt;/li&gt;
&lt;li&gt;Alerting across about fourteen channels: Slack, generic signed webhooks, Telegram, WhatsApp, SMS, PagerDuty, ntfy, Pushover, Discord, Google Chat, MS Teams, email. Fire once, recover, and repeat until acknowledged.&lt;/li&gt;
&lt;li&gt;Incidents with a separate internal state and public phase, so you can work an incident before you publish anything.&lt;/li&gt;
&lt;li&gt;Multi-region probes: each agent is the same binary in a stateless probe mode. Run your own anywhere with an org-scoped token.&lt;/li&gt;
&lt;li&gt;Automation: REST API with an OpenAPI spec, a Terraform provider, and an MCP server an LLM client can query over OAuth.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;a-few-engineering-bits&quot;&gt;A few engineering bits&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;A custom &lt;code&gt;hyper&lt;/code&gt; and &lt;code&gt;rustls&lt;/code&gt; client instead of a high-level one, so each result carries separate DNS, connect, TLS, and TTFB timings. About 130K checks a second on one core in the load test.&lt;/li&gt;
&lt;li&gt;A single min-heap scheduler for the whole fleet rather than a timer task per monitor, so memory stays flat in fleet size.&lt;/li&gt;
&lt;li&gt;ClickHouse with per-row TTL and minute and hour rollups, so dashboards never scan raw rows.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Full write-up of how it is built: &lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev/blog/building-an-uptime-monitor-in-rust&quot;&gt;https://uptimepage.dev/blog/building-an-uptime-monitor-in-rust&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;free-and-open&quot;&gt;Free and open&lt;/h3&gt;
&lt;p&gt;The core is not paywalled. Checks, status pages, subscribers, the API, and every alert channel are in the free tier, with no card to start. It is AGPL-3.0, and it monitors itself.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Site and hosted tier: &lt;a rel=&quot;nofollow&quot; href=&quot;https://uptimepage.dev&quot;&gt;https://uptimepage.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/uptimepage/uptimepage&quot;&gt;https://github.com/uptimepage/uptimepage&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy to answer questions about the internals or the self-host setup in the comments.&lt;/p&gt;
</description>
<category>Launches</category>
<guid isPermaLink="true">https://coderlegion.com/21622/uptimepage</guid>
<pubDate>Tue, 30 Jun 2026 17:20:37 +0000</pubDate>
</item>
<item>
<title>Building a KVM Virtual Machine in Rust: Running a binary</title>
<link>https://coderlegion.com/21481/building-a-kvm-virtual-machine-in-rust-running-a-binary</link>
<description>&lt;h3 id=&quot;recap-1&quot;&gt;Recap&lt;/h3&gt;
&lt;p&gt;This is a continuation of &lt;a rel=&quot;nofollow&quot; href=&quot;../kvm-rust-part2&quot;&gt;Part 2&lt;/a&gt; of KVM in Rust series,&lt;br&gt;
where we successfully set up a memory region for our guest VM. Now we will see&lt;br&gt;
how to proceed further and run a very simple binary in our hypervisor.&lt;/p&gt;
&lt;h3 id=&quot;loading-the-binary&quot;&gt;Loading the binary&lt;/h3&gt;
&lt;p&gt;Now, we have only set up a memory region, but we still haven't loaded any binary&lt;br&gt;
or code to actually run. For this, I have written a small &quot;Hello world&quot; x86&lt;br&gt;
assembly file: &lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/StjepanPoljak/kvm-rust/blob/kvm-part3-code/samples/hello-world.asm&quot;&gt;https://github.com/StjepanPoljak/kvm-rust/blob/kvm-part3-code/samples/hello-world.asm&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Notice that the &lt;code&gt;org 0x1000&lt;/code&gt; header is quite arbitrary and it's actually a good&lt;br&gt;
lead of what we also need to do (set the instruction pointer). Let's compile the&lt;br&gt;
assembly file with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;nasm ./samples/hello-world.asm
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, we want to load our binary into the memory region at address &lt;code&gt;0x1000&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let mut file = File::open(&quot;./samples/hello-world&quot;)?;
let mut code = Vec::new();
file.read_to_end(&amp;amp;mut code)?;

unsafe {
std::ptr::copy_nonoverlapping(code.as_ptr(),
  (mem_ptr as *mut u8).add(0x1000),
  code.len()) };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here we are using the more optimal &lt;code&gt;copy_nonoverlapping&lt;/code&gt; because we can be quite&lt;br&gt;
certain that the memory where &lt;code&gt;code&lt;/code&gt; is stored is not overlapping with our&lt;br&gt;
memory region.&lt;/p&gt;
&lt;h3 id=&quot;creating-a-vcpu&quot;&gt;Creating a vCPU&lt;/h3&gt;
&lt;p&gt;As we saw in &lt;a rel=&quot;nofollow&quot; href=&quot;../kvm-rust-part1&quot;&gt;Part 1&lt;/a&gt;, we have also discovered that QEMU also&lt;br&gt;
calls &lt;code&gt;KVM_SET_VCPU&lt;/code&gt; to obtain a vCPU file descriptor on which it will call&lt;br&gt;
&lt;code&gt;KVM_RUN&lt;/code&gt;. So, all that we have to do to set up a vCPU is to take a look at the&lt;br&gt;
&lt;code&gt;ioctl&lt;/code&gt; and recreate it in Rust:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;// 140904 ioctl(9&amp;lt;anon_inode:kvm-vm&amp;gt;, 0xae41 /* KVM_CREATE_VCPU */, 0) = 10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;
let vcpu_fd = unsafe { libc::ioctl(self.fd, KVM_CREATE_VCPU, 0usize) };
if vcpu_fd &amp;lt; 0 {
return Err(io::Error::last_os_error());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;investigating-vcpu-operations&quot;&gt;Investigating vCPU operations&lt;/h4&gt;
&lt;p&gt;It would be tempting to simply call &lt;code&gt;KVM_RUN&lt;/code&gt; on this file descriptor, however,&lt;br&gt;
we don't really know how vCPU is set up, where the instruction pointer is and&lt;br&gt;
even if the registers are in the valid state. If we look at the &lt;code&gt;ioctl&lt;/code&gt; calls&lt;br&gt;
referring to &lt;code&gt;kvm-vcpu:0&lt;/code&gt; in our log (and filtering out the spamming &lt;code&gt;KVM_RUN&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;KVM_SMI&lt;/code&gt;), we can see a lot of lines like these being repeated:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ grep 'kvm-vcpu:0' kvm.log | grep -v 'RUN\|SMI'

                ### omitted a lot of repetitive strace output ###

140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xc080aebe /* KVM_GET_NESTED_STATE */, 0x7768f8002010) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xc028ae92 /* KVM_TPR_ACCESS_REPORTING */, 0x77690198f090) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4140aecd /* KVM_SET_SREGS2 */, 0x77690198f2f0) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4090ae82 /* KVM_SET_REGS */, {rax=0x20, ..., rsp=0x6d88, rbp=0, ..., rip=0x18, rflags=0x246}) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x5000aea5 /* KVM_SET_XSAVE */, 0x7768f8001000) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4188aea7 /* KVM_SET_XCRS */, 0x77690198f2c0) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4008ae89 /* KVM_SET_MSRS */, 0x7768f80040a0) = 59
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4040aea0 /* KVM_SET_VCPU_EVENTS */, 0x77690198f500) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4008ae89 /* KVM_SET_MSRS */, 0x7768f80040a0) = 1
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4080aea2 /* KVM_SET_DEBUGREGS */, 0x77690198f500) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xc008ae88 /* KVM_GET_MSRS */, 0x77690198f5a0) = 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So we see that QEMU is setting vCPU registers via &lt;code&gt;KVM_SET_REGS&lt;/code&gt; and in this log&lt;br&gt;
we can see their exact values. It's also calling &lt;code&gt;KVM_SET_SREGS2&lt;/code&gt; which is&lt;br&gt;
setting system registers; unfortunately, we cannot see the values used here.&lt;br&gt;
Also, we do not need to follow the whole logic here, we just want to see what&lt;br&gt;
QEMU is doing with registers before calling &lt;code&gt;KVM_RUN&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ awk '/KVM_RUN/ {print; exit} {print}' kvm.log | grep '[GS]ET_[S]\?REGS\|KVM_RUN'
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4140aecd /* KVM_SET_SREGS2 */, 0x77690198f310) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4090ae82 /* KVM_SET_REGS */, {rax=0, ..., rsp=0, rbp=0, ..., rip=0xfff0, rflags=0x2}) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x8090ae81 /* KVM_GET_REGS */, {rax=0, ..., rsp=0, rbp=0, ..., rip=0xfff0, rflags=0x2}) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x8140aecc /* KVM_GET_SREGS2 */, 0x77690198f310) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4140aecd /* KVM_SET_SREGS2 */, 0x77690198f310) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4090ae82 /* KVM_SET_REGS */, {rax=0, ..., rsp=0, rbp=0, ..., rip=0xfff0, rflags=0x2}) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xae80 /* KVM_RUN */, 0) = -1 EINTR (Interrupted system call)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since QEMU first calls &lt;code&gt;SET_SREGS2&lt;/code&gt; and &lt;code&gt;SET_REGS&lt;/code&gt; we should actually&lt;br&gt;
investigate what the default (if any) values are in our own implementation.&lt;/p&gt;
&lt;h4 id=&quot;implementing-register-get-set-operations-in-rust&quot;&gt;Implementing register get/set operations in Rust&lt;/h4&gt;
&lt;p&gt;The procedure for this is as with all other KVM &lt;code&gt;ioctl&lt;/code&gt; calls we implemented.&lt;br&gt;
We find out how to construct their numbers (consult the previous article here)&lt;br&gt;
and reimplement these constants in Rust:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;const KVM_GET_SREGS2 : u64 = _IOR::&amp;lt;kvm_sregs2&amp;gt;(KVMIO, 0xcc);
const KVM_SET_SREGS2 : u64 = _IOW::&amp;lt;kvm_sregs2&amp;gt;(KVMIO, 0xcd);
const KVM_GET_REGS : u64 = _IOR::&amp;lt;kvm_regs&amp;gt;(KVMIO, 0x81);
const KVM_SET_REGS : u64 = _IOW::&amp;lt;kvm_regs&amp;gt;(KVMIO, 0x82);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also need to add &lt;code&gt;kvm_sregs2&lt;/code&gt; and &lt;code&gt;kvm_regs&lt;/code&gt; in our &lt;code&gt;build.rs&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;    bindgen::Builder::default()
        .header(&quot;/usr/include/linux/kvm.h&quot;)
        .allowlist_type(&quot;kvm_sregs2&quot;)
        .allowlist_type(&quot;kvm_userspace_memory_region&quot;)
        .allowlist_type(&quot;kvm_regs&quot;)
        .generate_comments(false)
        .generate()?
        .write_to_file(out_path.join(&quot;kvm-bindings.rs&quot;))?;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, &lt;code&gt;ioctl&lt;/code&gt; calls are as follows (I will only give example for &lt;code&gt;get_sregs2&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;get_regs&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let mut sregs2 = unsafe { std::mem::zeroed() };
let ret = unsafe { libc::ioctl(self.fd, KVM_GET_SREGS2, &amp;amp;mut sregs2) };

let mut regs = unsafe { std::mem::zeroed() };
let ret = unsafe { libc::ioctl(self.fd, KVM_GET_REGS, &amp;amp;mut regs) };
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;peeking-at-registers&quot;&gt;Peeking at registers&lt;/h4&gt;
&lt;p&gt;After obtaining &lt;code&gt;sregs2&lt;/code&gt; and &lt;code&gt;regs&lt;/code&gt; we can print them out and see what they are&lt;br&gt;
set to by default (to accomplish this we consult the &lt;code&gt;struct&lt;/code&gt; definition in our&lt;br&gt;
&lt;code&gt;kvm-bindings.rs&lt;/code&gt; (or in the Linux headers) and print out all the values of the&lt;br&gt;
fields we find). For &lt;code&gt;regs&lt;/code&gt; we see they are pre-set to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;RAX=0x0 RBX=0x0 RCX=0x0 RDX=0x600
RSI=0x0 RDI=0x0 RSP=0x0 RBP=0x0
R8=0x0  R9=0x0  R10=0x0 R11=0x0
R12=0x0 R13=0x0 R14=0x0 R15=0x0
RIP=0xfff0      RFLAGS=0x2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Taking a look at our trace from &lt;code&gt;kvm.log&lt;/code&gt; we can see that the ones QEMU sets are&lt;br&gt;
exactly the same (except for &lt;code&gt;RDX&lt;/code&gt; but we can get away with this one).&lt;/p&gt;
&lt;h4 id=&quot;peeking-at-system-registers&quot;&gt;Peeking at system registers&lt;/h4&gt;
&lt;p&gt;Now, for &lt;code&gt;sregs2&lt;/code&gt; it gets a bit more complicated. Our defaults are as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CS      base=0xffff0000 selector=0xf000 limit=0xffff    type=0xb        present=0x1
        dpl=0x0         db=0x0          s=0x1   l=0x0   g=0x0           avl=0x0

DS      base=0x0        selector=0x0    limit=0xffff    type=0x3        present=0x1
        dpl=0x0         db=0x0          s=0x1   l=0x0   g=0x0           avl=0x0

ES      base=0x0        selector=0x0    limit=0xffff    type=0x3        present=0x1
        dpl=0x0         db=0x0          s=0x1   l=0x0   g=0x0           avl=0x0

FS      base=0x0        selector=0x0    limit=0xffff    type=0x3        present=0x1
        dpl=0x0         db=0x0          s=0x1   l=0x0   g=0x0           avl=0x0

GS      base=0x0        selector=0x0    limit=0xffff    type=0x3        present=0x1
        dpl=0x0         db=0x0          s=0x1   l=0x0   g=0x0           avl=0x0

SS      base=0x0        selector=0x0    limit=0xffff    type=0x3        present=0x1
        dpl=0x0         db=0x0          s=0x1   l=0x0   g=0x0           avl=0x0

TR      base=0x0        selector=0x0    limit=0xffff    type=0xb        present=0x1
        dpl=0x0         db=0x0          s=0x0   l=0x0   g=0x0           avl=0x0

LDT     base=0x0        selector=0x0    limit=0xffff    type=0x2        present=0x1
        dpl=0x0         db=0x0          s=0x0   l=0x0   g=0x0           avl=0x0

GDT     base=0x0        limit=0xffff

IDT     base=0x0        limit=0xffff

CR0=0x60000010          CR2=0x0         CR3=0x0         CR4=0x0         CR8=0x0

EFER=0x0                APIC_BASE=0xfee00900            FLAGS=0x0

PDPTRS[0]=0x0           PDPTRS[1]=0x0           PDPTRS[2]=0x0           PDPTRS[3]=0x0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Problem is that we don't see any values in our &lt;code&gt;strace&lt;/code&gt; log, only the address of&lt;br&gt;
the variable. So we need to be a bit creative here if we want to find out what &lt;br&gt;
QEMU sets these values to. We could use &lt;code&gt;ptrace&lt;/code&gt; (in fact &lt;code&gt;strace&lt;/code&gt; is built upon&lt;br&gt;
&lt;code&gt;ptrace&lt;/code&gt; API), but it may be a bit too much. Same for &lt;code&gt;uprobes&lt;/code&gt; and &lt;code&gt;eBPF&lt;/code&gt;. We&lt;br&gt;
do have GDB, though, and it's just perfect as a one-off thing here. All we have&lt;br&gt;
to do is run QEMU under GDB and then execute:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;(gdb) break ioctl if $rsi == 0x4140aecd
(gdb) run
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that &lt;code&gt;0x4140aecd&lt;/code&gt; is the exact value that we extracted from &lt;code&gt;SET_SREGS2&lt;/code&gt;&lt;br&gt;
&lt;code&gt;ioctl&lt;/code&gt; call (in x86 ABI, &lt;code&gt;RSI&lt;/code&gt; is holding the value of second argument):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0x4140aecd /* KVM_SET_SREGS2 */, 0x77690198f310) = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once GDB breaks, we can dump memory. However, we don't know the exact size of&lt;br&gt;
&lt;code&gt;struct kvm_sregs2&lt;/code&gt;; we could guess or manually inspect, but quickest way to get&lt;br&gt;
it is to actually have a small C program:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-c&quot;&gt;#include &amp;lt;linux/kvm.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;

int main() {
  printf(&quot;%lu&quot;, sizeof(struct kvm_sregs2));
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The program will return value of &lt;code&gt;320&lt;/code&gt; (at least on my system) and we can then&lt;br&gt;
use this in GDB:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;(gdb) dump memory dump.bin $rdx $rdx+320
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that &lt;code&gt;RDX&lt;/code&gt; is holding the third argument, i.e. the address we actually saw&lt;br&gt;
in our &lt;code&gt;strace&lt;/code&gt; log. Now, we can reuse our function for printing &lt;code&gt;sregs2&lt;/code&gt; in&lt;br&gt;
Rust. We simply hack our main function to load the file and reinterpret its data&lt;br&gt;
as &lt;code&gt;sregs2&lt;/code&gt; and then print it. We got:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CSbase=0xffff0000selector=0xf000limit=0xfffftype=0xbpresent=0x1
dpl=0x0db=0x0s=0x1l=0x0g=0x0avl=0x0

DSbase=0x0selector=0x0limit=0xfffftype=0x3present=0x1
dpl=0x0db=0x0s=0x1l=0x0g=0x0avl=0x0

ESbase=0x0selector=0x0limit=0xfffftype=0x3present=0x1
dpl=0x0db=0x0s=0x1l=0x0g=0x0avl=0x0

FSbase=0x0selector=0x0limit=0xfffftype=0x3present=0x1
dpl=0x0db=0x0s=0x1l=0x0g=0x0avl=0x0

GSbase=0x0selector=0x0limit=0xfffftype=0x3present=0x1
dpl=0x0db=0x0s=0x1l=0x0g=0x0avl=0x0

SSbase=0x0selector=0x0limit=0xfffftype=0x3present=0x1
dpl=0x0db=0x0s=0x1l=0x0g=0x0avl=0x0

TRbase=0x0selector=0x0limit=0xfffftype=0xbpresent=0x1
dpl=0x0db=0x0s=0x0l=0x0g=0x0avl=0x0

LDTbase=0x0selector=0x0limit=0xfffftype=0x2present=0x1
dpl=0x0db=0x0s=0x0l=0x0g=0x0avl=0x0

GDTbase=0x0limit=0xffff

IDTbase=0x0limit=0xffff

CR0=0x60000010CR2=0x0CR3=0x0CR4=0x0CR8=0x0

EFER=0x0APIC_BASE=0xfee00900FLAGS=0x0

PDPTRS[0]=0x0PDPTRS[1]=0x4b275f5fce32f200PDPTRS[2]=0x0PDPTRS[3]=0x3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see that this more or less corresponds to the defaults we observed in our&lt;br&gt;
KVM implementation. If we want to change our registers (which we will), then we&lt;br&gt;
will always first get the ones that are current in the vCPU, change the ones we&lt;br&gt;
need and then set them using &lt;code&gt;KVM_SET_REGS&lt;/code&gt; (or &lt;code&gt;KVM_SET_SREGS2&lt;/code&gt;). So one of&lt;br&gt;
first things to do is to set &lt;code&gt;CS&lt;/code&gt; to flat mapping:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let mut sregs2 = vcpu.get_sregs2()?;
sregs2.cs.base = 0;
sregs2.cs.selector = 0;
vcpu.set_sregs2(sregs2)?;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Recall that our assembly file was assembled with &lt;code&gt;org 0x1000&lt;/code&gt;, so setting RIP to&lt;br&gt;
&lt;code&gt;0x1000&lt;/code&gt; causes execution to begin at the start of the loaded binary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let mut regs = vcpu.get_regs()?;
regs.rip = 0x1000;
vcpu.set_regs(regs)?;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Furthermore, we now have a very good process of finding out what QEMU does with&lt;br&gt;
registers.&lt;/p&gt;
&lt;h3 id=&quot;running-the-binary-1&quot;&gt;Running the binary&lt;/h3&gt;
&lt;p&gt;Now this is where &lt;code&gt;strace&lt;/code&gt; stopped being useful and my understanding of the KVM&lt;br&gt;
run loop became more useful. Another positive thing is that this part of QEMU&lt;br&gt;
source code is quite readable and can be found in function &lt;a rel=&quot;nofollow&quot; href=&quot;https://elixir.bootlin.com/qemu/v11.0.1/source/accel/kvm/kvm-accel-ops.c&quot;&gt;kvm_vcpu_thread_fn&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The more x86-specific code with exit reasons can be found in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://elixir.bootlin.com/qemu/v11.0.1/source/target/i386/kvm/kvm.c&quot;&gt;kvm_arch_handle_exit&lt;/a&gt; function.&lt;/p&gt;
&lt;h4 id=&quot;setting-kvm-run-shared-memory-region&quot;&gt;Setting kvm_run shared memory region&lt;/h4&gt;
&lt;p&gt;To wrap things up, first we need to mmap the shared kvm_run structure that KVM&lt;br&gt;
uses to communicate VM-exit information and other runtime state between the&lt;br&gt;
kernel and userspace:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;// 140904 ioctl(3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;, 0xae04 /* KVM_GET_VCPU_MMAP_SIZE */, 0) = 12288
let kvm_run_size = unsafe {
libc::ioctl(kvm_fd, KVM_GET_VCPU_MMAP_SIZE, 0usize) };
let kvm_run_mem = unsafe {
libc::mmap(ptr::null_mut(), kvm_run_size as usize,
   libc::PROT_READ|libc::PROT_WRITE, libc::MAP_SHARED,
   vcpu_fd, 0) };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, when we call &lt;code&gt;KVM_RUN&lt;/code&gt; we simply match the exit reasons and act&lt;br&gt;
accordingly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let run = vcpu.kvm_run_mem as *mut kvm_run;

loop {
    let ret = unsafe { libc::ioctl(vcpu.fd, KVM_RUN, 0usize) };
    if ret &amp;lt; 0 {
        return Err(io::Error::last_os_error());
    }

    let exit_reason = unsafe { (*run).exit_reason };

    match exit_reason {
        KVM_EXIT_MMIO =&amp;gt; { /* omitted */ }
        KVM_EXIT_HLT =&amp;gt; {
            println!(&quot;Guest halted.&quot;);
            break; }
        KVM_EXIT_SHUTDOWN =&amp;gt; {
            println!(&quot;Guest shutdown.&quot;);
            break; }
        KVM_EXIT_INTERNAL_ERROR =&amp;gt; {
            return Err(io::Error::other(&quot;KVM internal error.&quot;)); }
        _ =&amp;gt; {
            println!(&quot;EXIT REASON = {}&quot;, exit_reason);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;our-first-output&quot;&gt;Our first output&lt;/h4&gt;
&lt;p&gt;Being that our assembly file uses VGA MMIO to output the &quot;Hello world!&quot; string,&lt;br&gt;
I have only implemented &lt;code&gt;KVM_EXIT_MMIO&lt;/code&gt;. Then we can finally see the output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ cargo run ./samples/hello-world
     ## ommitted sregs2 output and warnings ##
RAX=0x0 RBX=0x0 RCX=0x0 RDX=0x600
RSI=0x0 RDI=0x0 RSP=0x0 RBP=0x0
R8=0x0  R9=0x0  R10=0x0 R11=0x0
R12=0x0 R13=0x0 R14=0x0 R15=0x0
RIP=0x1000      RFLAGS=0x2
Hello from KVM!
Guest halted.
RAX=0xb800      RBX=0x0 RCX=0x0 RDX=0x600
RSI=0x102d      RDI=0x22        RSP=0x0 RBP=0x0
R8=0x0  R9=0x0  R10=0x0 R11=0x0
R12=0x0 R13=0x0 R14=0x0 R15=0x0
RIP=0x101b      RFLAGS=0x46
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;conclusion-2&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This concludes the three-part series on figuring out KVM via &lt;code&gt;strace&lt;/code&gt; and&lt;br&gt;
reimplementing it in Rust. A lot of next steps actually come down to x86&lt;br&gt;
architecture and bootloader specifics, so we may leave it here for now. The full&lt;br&gt;
code capable of running simple binaries can be found on GitHub:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/StjepanPoljak/kvm-rust/tree/kvm-part3-code&quot;&gt;https://github.com/StjepanPoljak/kvm-rust/tree/kvm-part3-code&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21481/building-a-kvm-virtual-machine-in-rust-running-a-binary</guid>
<pubDate>Sun, 28 Jun 2026 05:24:29 +0000</pubDate>
</item>
<item>
<title>KairoDB-Human-Readable Databases</title>
<link>https://coderlegion.com/21219/kairodb-human-readable-databases</link>
<description>&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=11662298101406878027&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;I’ve been working on a small experimental project called &lt;strong&gt;KairoDB&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It’s not trying to replace anything big or compete with existing databases.&lt;br&gt;
It’s more like a playground where I test ideas around data storage, structure, and performance.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-kairodb-&quot;&gt;What is KairoDB?&lt;/h3&gt;
&lt;p&gt;KairoDB is a lightweight database system built for experimentation and learning.&lt;/p&gt;
&lt;p&gt;The goal is simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Understand how databases actually think internally&lt;/li&gt;
&lt;li&gt;Experiment with storage and retrieval logic&lt;/li&gt;
&lt;li&gt;Build something minimal but functional&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;core-idea&quot;&gt;Core Idea&lt;/h3&gt;
&lt;p&gt;Instead of overengineering from day one, I focused on a simple pipeline:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;
Input → Parser → Storage Layer → Query Engine → Output

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nothing fancy. Just clarity first.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-i-built-it&quot;&gt;Why I built it&lt;/h3&gt;
&lt;p&gt;Most people learn databases from theory.&lt;/p&gt;
&lt;p&gt;I wanted to go one step deeper and actually build parts of it myself.&lt;/p&gt;
&lt;p&gt;Not because it's &quot;better&quot;, but because it makes concepts stick.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;current-features&quot;&gt;Current Features&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;
* Basic data insertion
* Simple query system
* File-based persistence
* Lightweight architecture


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tpn0lzmz7wno8390v4wt.png)

&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;things-i-learned&quot;&gt;Things I learned&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;How messy real-world data handling can be&lt;/li&gt;
&lt;li&gt;Why query optimization matters more than people think&lt;/li&gt;
&lt;li&gt;That simplicity is harder than complexity&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;project-links&quot;&gt;Project Links&lt;/h3&gt;
&lt;p&gt;GitHub:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/lnfiniware/Kairo&quot;&gt;https://github.com/lnfiniware/Kairo&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Live / Project:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://kairo.infiniware.bid&quot;&gt;https://kairo.infiniware.bid&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-note&quot;&gt;Final Note&lt;/h3&gt;
&lt;p&gt;This project is still evolving.&lt;br&gt;
I’m not trying to make it perfect — just better every time I touch it.&lt;/p&gt;
&lt;p&gt;Feedback is welcome.&lt;br&gt;
Even the painful kind.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/21219/kairodb-human-readable-databases</guid>
<pubDate>Tue, 23 Jun 2026 17:28:42 +0000</pubDate>
</item>
<item>
<title>Building a KVM Virtual Machine in Rust: Memory Setup</title>
<link>https://coderlegion.com/20611/building-a-kvm-virtual-machine-in-rust-memory-setup</link>
<description>&lt;h3 id=&quot;recap-2&quot;&gt;Recap&lt;/h3&gt;
&lt;p&gt;This is a continuation of my &lt;a rel=&quot;nofollow&quot; href=&quot;../kvm-rust-part1&quot;&gt;previous article&lt;/a&gt; which dealt&lt;br&gt;
with reverse-engineering QEMU with &lt;code&gt;strace&lt;/code&gt; to learn how KVM works. Now it's&lt;br&gt;
time to try and follow the steps we got from the &lt;code&gt;strace&lt;/code&gt; logs to build our own&lt;br&gt;
KVM-based virtual machine in Rust.&lt;/p&gt;
&lt;h3 id=&quot;kvm-headers&quot;&gt;KVM Headers&lt;/h3&gt;
&lt;p&gt;I haven't actually used existing KVM libraries written specifically for Rust but&lt;br&gt;
opted to use the &lt;code&gt;libc&lt;/code&gt; crate which provides the required &lt;code&gt;ioctl&lt;/code&gt; bindings and&lt;br&gt;
helper macros. The main reason is that I want a complete understanding of what&lt;br&gt;
is happening under the hood. Now for each of these KVM &lt;code&gt;ioctl&lt;/code&gt; calls we can use&lt;br&gt;
the Linux headers for reference. For example, to find out how to construct&lt;br&gt;
&lt;code&gt;ioctl&lt;/code&gt; number for &lt;code&gt;KVM_CREATE_VM&lt;/code&gt; we simply can do:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ grep -Rn 'KVM_CREATE_VM' /usr/include/linux/
/usr/include/linux/kvm.h:855:/* machine type bits, to be used as argument to KVM_CREATE_VM */
/usr/include/linux/kvm.h:882:#define KVM_CREATE_VM             _IO(KVMIO,   0x01) /* returns a VM fd */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Luckily in &lt;code&gt;libc&lt;/code&gt; Rust crate we have macros for &lt;code&gt;_IO&lt;/code&gt; (and the like), but we&lt;br&gt;
still need &lt;code&gt;KVMIO&lt;/code&gt; macro:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ grep -Rn 'define KVMIO' /usr/include/linux/
/usr/include/linux/kvm.h:853:#define KVMIO 0xAE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can now construct the &lt;code&gt;ioctl&lt;/code&gt; number for &lt;code&gt;KVM_CREATE_VM&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;use libc::{_IOW, _IO, _IOR};

const KVMIO : u32 = 0xae;
const KVM_CREATE_VM : u64 = _IO(KVMIO, 0x01);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that exact integer type depends on the platform and &lt;code&gt;libc&lt;/code&gt; definitions.&lt;br&gt;
Then, in the main we can do:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;fn main() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {
    let file = OpenOptions::new()
        .read(true)
        .write(true)
        .open(&quot;/dev/kvm&quot;)
        .expect(&quot;failed to open /dev/kvm&quot;);

    let fd = file.as_raw_fd();
    let vm_fd = unsafe { libc::ioctl(fd, KVM_CREATE_VM, 0usize) };
Ok(())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is the procedure we will follow for each relevant &lt;code&gt;ioctl&lt;/code&gt; call. In fact,&lt;br&gt;
we can &quot;reverse-engineer&quot; our own program and then compare it with the original,&lt;br&gt;
to make sure we are doing the right thing:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ strace cargo run

     ### omitted irrelevant strace output ###

openat(AT_FDCWD, &quot;/dev/kvm&quot;, O_RDWR|O_CLOEXEC) = 3
ioctl(3, KVM_CREATE_VM, 0)              = 4
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;important-note&quot;&gt;Important note&lt;/h4&gt;
&lt;p&gt;In production code we would immediately check for a negative return value and&lt;br&gt;
convert errno into a Rust error. To keep the example focused, I am omitting&lt;br&gt;
proper error handling in this article.&lt;/p&gt;
&lt;h3 id=&quot;setting-memory-region&quot;&gt;Setting memory region&lt;/h3&gt;
&lt;p&gt;Now, to recall, the next step is setting up the memory region used by both the&lt;br&gt;
KVM guest and the host. This region will be the memory of our virtual machine, a&lt;br&gt;
place where we will load our binary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;140900 mmap(NULL, 1075838976, 0 /* PROT_NONE */, 0x22 /* MAP_PRIVATE|MAP_ANONYMOUS */, -1, 0) = 0x7768b3e00000
140900 mmap(0x7768b3e00000, 1073741824, 0x3 /* PROT_READ|PROT_WRITE */, 0x32 /* MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS */, -1, 0) = 0x7768b3e00000
140900 ioctl(9&amp;lt;anon_inode:kvm-vm&amp;gt;, 0x4020ae46 /* KVM_SET_USER_MEMORY_REGION */, {slot=0, flags=0, guest_phys_addr=0, memory_size=1073741824, userspace_addr=0x7768b3e00000}) = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;recreating-mmap-call&quot;&gt;Recreating mmap call&lt;/h4&gt;
&lt;p&gt;So, first thing we need to do is follow the same logic for &lt;code&gt;mmap&lt;/code&gt; which is also&lt;br&gt;
available in the &lt;code&gt;libc&lt;/code&gt; Rust crate. After creating the virtual machine, we could&lt;br&gt;
simply recreate our own &lt;code&gt;mmap&lt;/code&gt; calls based on the &lt;code&gt;strace&lt;/code&gt; output. However,&lt;br&gt;
notice that QEMU first reserves a larger address range with &lt;code&gt;PROT_NONE&lt;/code&gt; and then&lt;br&gt;
maps only the portion it actually intends to use. For our prototype we do not&lt;br&gt;
actually need to mimic this exact reservation pattern.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let mem_size: u64 = 256 * 1024;
let mem = unsafe {
libc::mmap(ptr::null_mut(),
   mem_size as usize,
   libc::PROT_READ|libc::PROT_WRITE,
   libc::MAP_PRIVATE|libc::MAP_ANONYMOUS,
   -1,
   0)
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this experiment we are only allocating 256 kilobytes because we are not yet&lt;br&gt;
booting a full operating system and therefore need very little guest memory.&lt;br&gt;
Also note that, as with &lt;code&gt;ioctl&lt;/code&gt;, production code should check whether mmap()&lt;br&gt;
returned &lt;code&gt;MAP_FAILED&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;recreating-ioctl-call&quot;&gt;Recreating ioctl call&lt;/h4&gt;
&lt;p&gt;First we need to see the definition of the &lt;code&gt;KVM_SET_USER_MEMORY_REGION&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ grep -Rn 'define KVM_SET_USER' /usr/include/linux/ -A1
/usr/include/linux/kvm.h:1433:#define KVM_SET_USER_MEMORY_REGION _IOW(KVMIO, 0x46, \
/usr/include/linux/kvm.h-1434-                                  struct kvm_userspace_memory_region)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We see that for this one, we need &lt;code&gt;struct kvm_userspace_memory_region&lt;/code&gt;. The&lt;br&gt;
values we need are visible in the &lt;code&gt;strace&lt;/code&gt; output, but copying struct definition&lt;br&gt;
to our Rust program is really not advisable. Luckily, Rust has &lt;code&gt;bindgen&lt;/code&gt; which&lt;br&gt;
we can use to get this KVM struct (and others) from Linux headers. For this&lt;br&gt;
purpose we have a separate &lt;code&gt;build.rs&lt;/code&gt; file which will contain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;use bindgen;
use std::path::PathBuf;
use std::env;

fn main() -&amp;gt; Result&amp;lt;(), Box&amp;lt;dyn std::error::Error&amp;gt;&amp;gt; {
    let out_path = PathBuf::from(env::var(&quot;OUT_DIR&quot;)?);

    bindgen::Builder::default()
.header(&quot;/usr/include/linux/kvm.h&quot;)
.allowlist_type(&quot;kvm_userspace_memory_region&quot;)
.generate_comments(false)
.generate()?
.write_to_file(out_path.join(&quot;kvm-bindings.rs&quot;))?;

    Ok(())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now in the &lt;code&gt;main.rs&lt;/code&gt; we can import these bindings with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;include!(concat!(env!(&quot;OUT_DIR&quot;), &quot;/kvm-bindings.rs&quot;));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then, after &lt;code&gt;mmap()&lt;/code&gt; calls, we set the memory region, also imitating what&lt;br&gt;
QEMU is doing in the &lt;code&gt;strace&lt;/code&gt; output:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rs&quot;&gt;let region = kvm_userspace_memory_region {
slot : 0,
flags : 0,
guest_phys_addr : 0x0,
memory_size : mem_size,
userspace_addr : mem as u64
};

let _ret = unsafe { libc::ioctl(vm_fd, KVM_SET_USER_MEMORY_REGION, &amp;amp;region) };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We register this region starting at guest physical address &lt;code&gt;0x0&lt;/code&gt;, meaning the&lt;br&gt;
first byte of our allocated host memory will appear as physical address 0 inside&lt;br&gt;
the guest. Also note that the &lt;code&gt;KVM_SET_USER_MEMORY_REGION&lt;/code&gt; call does not copy&lt;br&gt;
memory, but rather tells KVM that guest physical address will be backed by a&lt;br&gt;
specific userspace memory region.&lt;/p&gt;
&lt;h3 id=&quot;running-the-code&quot;&gt;Running the code&lt;/h3&gt;
&lt;p&gt;Next thing to do is to run it. We still haven't added any checks after &lt;code&gt;mmap&lt;/code&gt;&lt;br&gt;
and &lt;code&gt;ioctl&lt;/code&gt; calls, but for this prototype we can again simply use &lt;code&gt;strace&lt;/code&gt; our&lt;br&gt;
own code:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;$ strace -yy -X verbose -e trace=ioctl,mmap,openat,read,write cargo run

                ### omitted irrelevant strace output ###

openat(-100 /* AT_FDCWD */&amp;lt;/home/stjepan/Develop/KVM/rust&amp;gt;, &quot;/dev/kvm&quot;, 0x80002 /* O_RDWR|O_CLOEXEC */) = 3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;
ioctl(3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;, 0xae01 /* KVM_CREATE_VM */, 0) = 4&amp;lt;anon_inode:kvm-vm&amp;gt;
mmap(NULL, 262144, 0x3 /* PROT_READ|PROT_WRITE */, 0x22 /* MAP_PRIVATE|MAP_ANONYMOUS */, -1, 0) = 0x7d63fdfa2000
ioctl(4&amp;lt;anon_inode:kvm-vm&amp;gt;, 0x4020ae46 /* KVM_SET_USER_MEMORY_REGION */, {slot=0, flags=0, guest_phys_addr=0, memory_size=262144, userspace_addr=0x7d63fdfa2000}) = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see our output is fine and no errors were reported. Note that a full&lt;br&gt;
working example with proper checking and Rust idiomatic approaches can be found&lt;br&gt;
on my GitHub page:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/StjepanPoljak/kvm-rust/tree/kvm-part2-code&quot;&gt;https://github.com/StjepanPoljak/kvm-rust/tree/kvm-part2-code&lt;/a&gt;&lt;/p&gt;
&lt;h4 id=&quot;next-steps&quot;&gt;Next steps&lt;/h4&gt;
&lt;p&gt;At this point we have a VM object and guest memory, but nothing is actually executing yet. In the next part we will create a vCPU, initialize its state, load a small binary into guest memory and enter the first &lt;code&gt;KVM_RUN&lt;/code&gt; loop.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20611/building-a-kvm-virtual-machine-in-rust-memory-setup</guid>
<pubDate>Mon, 15 Jun 2026 05:20:13 +0000</pubDate>
</item>
<item>
<title>Learning KVM by Reverse-Engineering QEMU with strace</title>
<link>https://coderlegion.com/20315/learning-kvm-by-reverse-engineering-qemu-with-strace</link>
<description>&lt;h3 id=&quot;motivation-1&quot;&gt;Motivation&lt;/h3&gt;
&lt;p&gt;I work with virtual machines in QEMU/KVM environment (a lot). In order to debug,&lt;br&gt;
optimize and customize the VMs requires an in-depth knowledge of both QEMU and&lt;br&gt;
KVM, the Linux kernel virtualization subsystem that exposes hardware&lt;br&gt;
virtualization features such as Intel VT-x and AMD-V to userspace applications&lt;br&gt;
like QEMU. Not only that, but I work on a lot of hobby projects requiring quick&lt;br&gt;
bare-metal boot-ups and debugging workflows, and, to be honest, a lot of times&lt;br&gt;
QEMU is an overkill for these sorts of tasks.&lt;/p&gt;
&lt;h3 id=&quot;kvm-vs-tcg&quot;&gt;KVM vs TCG&lt;/h3&gt;
&lt;p&gt;Also when it comes to QEMU, it's worth noting that we always have an option of&lt;br&gt;
using TCG, which has a completely different purpose than KVM. TCG is short for&lt;br&gt;
Tiny Code Generator, which works by translating guest instructions into host&lt;br&gt;
instructions at runtime. This is quite slow compared to running code without&lt;br&gt;
translation overhead. So, if we want to test our bare-metal code, we may want to&lt;br&gt;
test it on our own real CPU at native speed. This is where KVM comes in. Unlike&lt;br&gt;
TCG, KVM does not emulate the CPU itself. Instead, it allows guest code to&lt;br&gt;
execute directly on the host processor while the Linux kernel manages&lt;br&gt;
transitions between guest and host execution.&lt;/p&gt;
&lt;h3 id=&quot;how-kvm-works-in-linux&quot;&gt;How KVM works in Linux&lt;/h3&gt;
&lt;p&gt;So, I do already know some basics. KVM driver exposes a driver interface in&lt;br&gt;
Linux root filesystem, &lt;code&gt;/dev/kvm&lt;/code&gt;. Communicating with the driver is done via&lt;br&gt;
&lt;code&gt;ioctl()&lt;/code&gt; system call on a file descriptor. What we need to find out is how QEMU&lt;br&gt;
communicates with Linux kernel and try and follow the QEMU logic without&lt;br&gt;
reading QEMU source code and KVM API (both can be a bit more intimidating than&lt;br&gt;
just seeing how it works under the hood).&lt;/p&gt;
&lt;h3 id=&quot;reverse-engineering-kvm&quot;&gt;Reverse-engineering KVM&lt;/h3&gt;
&lt;p&gt;Now we can take some lightweight Debian Linux image and load it into the QEMU,&lt;br&gt;
with KVM enabled:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;QEMU_IMAGE=./debian-12-nocloud-amd64.qcow2

qemu-system-x86_64\
-m 1024\
-drive file=&quot;${QEMU_IMAGE}&quot;,if=virtio,cache=none\
-serial stdio\
-enable-kvm\
-cpu host\
-nodefaults\
-nographic
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is a pretty straightforward way to run QEMU with minimal setup. The most&lt;br&gt;
relevant options for us are &lt;code&gt;-enable-kvm&lt;/code&gt; and &lt;code&gt;-cpu host&lt;/code&gt;, which will enable&lt;br&gt;
KVM and use host CPU instead of emulating some specific CPU.&lt;/p&gt;
&lt;h4 id=&quot;tracing-qemu-kvm-with-strace&quot;&gt;Tracing QEMU/KVM with strace&lt;/h4&gt;
&lt;p&gt;Now, we want to see what QEMU is really doing by utilizing &lt;code&gt;strace&lt;/code&gt;. We can put this command in a &lt;code&gt;start-qemu.sh&lt;/code&gt; script and call it with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sh&quot;&gt;strace -yy -f -X verbose\
   -e trace=ioctl,openat,read,write,mmap\
   -o kvm.log\
   ./start-qemu.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This command will trace all &lt;code&gt;ioctl&lt;/code&gt;, &lt;code&gt;openat&lt;/code&gt;, &lt;code&gt;read&lt;/code&gt;, &lt;code&gt;write&lt;/code&gt; and &lt;code&gt;mmap&lt;/code&gt; system&lt;br&gt;
calls. Although I mentioned only &lt;code&gt;ioctl&lt;/code&gt; calls so far, I always like to include&lt;br&gt;
some other common system calls that could be used. As far as we know &lt;code&gt;/dev/kvm&lt;/code&gt;&lt;br&gt;
is the interface to KVM driver and QEMU will probably use &lt;code&gt;openat&lt;/code&gt; on it.&lt;br&gt;
Similarly, we also want to see what QEMU is doing with memory and what it's&lt;br&gt;
reading and writing in general.&lt;/p&gt;
&lt;p&gt;Note: Information on &lt;code&gt;strace&lt;/code&gt; arguments as above can be found in &lt;code&gt;strace --help&lt;/code&gt;&lt;br&gt;
or &lt;code&gt;man strace&lt;/code&gt;, but essentially, the &lt;code&gt;-yy&lt;/code&gt; tells strace to print all available&lt;br&gt;
information when decoding file descriptors, &lt;code&gt;-f&lt;/code&gt; follows forks (we need this one&lt;br&gt;
as we're wrapping it in scripts and QEMU might also do similar stuff). The&lt;br&gt;
&lt;code&gt;-X verbose&lt;/code&gt; will print names of constants and flags (very important when&lt;br&gt;
analyzing &lt;code&gt;ioctl&lt;/code&gt; calls);&lt;/p&gt;
&lt;h4 id=&quot;interpreting-the-logs&quot;&gt;Interpreting the logs&lt;/h4&gt;
&lt;p&gt;Now, we start the above command and, as soon as system boots, we can kill it&lt;br&gt;
with &lt;code&gt;CTRL+C&lt;/code&gt;. This will be quite sufficient to see how QEMU/KVM works without&lt;br&gt;
spamming our logs with redundant information. When we read the &lt;code&gt;kvm.log&lt;/code&gt; file,&lt;br&gt;
we will see a lot of traces that are not really interesting. However, we already&lt;br&gt;
have some knowledge: we know QEMU should be opening &lt;code&gt;/dev/kvm&lt;/code&gt; so a quick&lt;br&gt;
search for &lt;code&gt;kvm&lt;/code&gt; reveals exactly what we need:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;140900 openat(-100 /* AT_FDCWD */&amp;lt;/home/stjepan/Develop/KVM&amp;gt;, &quot;/dev/kvm&quot;, 0x80002 /* O_RDWR|O_CLOEXEC */) = 3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;
140900 ioctl(3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;, 0xae00 /* KVM_GET_API_VERSION */, 0) = 12
140900 ioctl(3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;, 0xae03 /* KVM_CHECK_EXTENSION */, 0x88 /* KVM_CAP_IMMEDIATE_EXIT */) = 1
140900 ioctl(3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;, 0xae03 /* KVM_CHECK_EXTENSION */, 0xa /* KVM_CAP_NR_MEMSLOTS */) = 32764
140900 ioctl(3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;, 0xae03 /* KVM_CHECK_EXTENSION */, 0x76 /* KVM_CAP_MULTI_ADDRESS_SPACE */) = 2
140900 ioctl(3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;, 0xae01 /* KVM_CREATE_VM */, 0) = 9&amp;lt;anon_inode:kvm-vm&amp;gt;
140900 ioctl(9&amp;lt;anon_inode:kvm-vm&amp;gt;, 0xae03 /* KVM_CHECK_EXTENSION */, 0x9 /* KVM_CAP_NR_VCPUS */) = 4
140900 ioctl(3&amp;lt;/dev/kvm&amp;lt;char 10:232&amp;gt;&amp;gt;, 0xae03 /* KVM_CHECK_EXTENSION */, 0x42 /* KVM_CAP_MAX_VCPUS */) = 4096
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see that QEMU is opening &lt;code&gt;/dev/kvm&lt;/code&gt; and that it's checking API version&lt;br&gt;
and various extensions. We may skip these checks and focus on the calls that&lt;br&gt;
look most important; one of these here is &lt;code&gt;KVM_CREATE_VM&lt;/code&gt; which also returns a&lt;br&gt;
file descriptor &lt;code&gt;9&amp;lt;anon_inode:kvm-vm&amp;gt;&lt;/code&gt; which we can use as a further reference.&lt;/p&gt;
&lt;h5 id=&quot;setting-up-memory-regions&quot;&gt;Setting up memory regions&lt;/h5&gt;
&lt;p&gt;We know QEMU must eventually load firmware and guest memory into the VM. Looking&lt;br&gt;
for file operations after &lt;code&gt;KVM_CREATE_VM&lt;/code&gt;, we quickly encounter SeaBIOS being&lt;br&gt;
loaded:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;140900 openat(-100 /* AT_FDCWD */&amp;lt;/home/stjepan/Develop/KVM&amp;gt;, &quot;/usr/share/seabios/bios-256k.bin&quot;, 0 /* O_RDONLY */) = 12&amp;lt;/usr/share/seabios/bios-256k.bin&amp;gt;
140900 mmap(NULL, 2359296, 0 /* PROT_NONE */, 0x22 /* MAP_PRIVATE|MAP_ANONYMOUS */, -1, 0) = 0x776900dc1000
140900 mmap(0x776900e00000, 262144, 0x3 /* PROT_READ|PROT_WRITE */, 0x32 /* MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS */, -1, 0) = 0x776900e00000
140900 openat(-100 /* AT_FDCWD */&amp;lt;/home/stjepan/Develop/KVM&amp;gt;, &quot;/usr/share/seabios/bios-256k.bin&quot;, 0 /* O_RDONLY */) = 12&amp;lt;/usr/share/seabios/bios-256k.bin&amp;gt;
140900 mmap(NULL, 266240, 0x3 /* PROT_READ|PROT_WRITE */, 0x22 /* MAP_PRIVATE|MAP_ANONYMOUS */, -1, 0) = 0x776900fc0000
140900 read(12&amp;lt;/usr/share/seabios/bios-256k.bin&amp;gt;, &quot;\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0&quot;..., 262144) = 262144
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can see QEMU opening and reading SeaBIOS binary and reserving memory along&lt;br&gt;
with it. We can use the return of the &lt;code&gt;read&lt;/code&gt; system call (the size of the&lt;br&gt;
SeaBIOS binary) and see what KVM is doing with it. Searching through the log&lt;br&gt;
for this size gives us further information on what KVM is doing with SeaBIOS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;140900 ioctl(9&amp;lt;anon_inode:kvm-vm&amp;gt;, 0x4020ae46 /* KVM_SET_USER_MEMORY_REGION */, {slot=3, flags=0x2 /* KVM_MEM_READONLY */, guest_phys_addr=0xfffc0000, memory_size=262144, userspace_addr=0x776900e00000}) = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So we see that it's now setting this as the memory region for KVM at guest&lt;br&gt;
physical address &lt;code&gt;0xfffc0000&lt;/code&gt; from userspace address that was actually&lt;br&gt;
obtained by &lt;code&gt;mmap&lt;/code&gt; in one of the traces above. In other words, KVM does not&lt;br&gt;
allocate guest RAM itself; userspace applications such as QEMU remain&lt;br&gt;
responsible for managing the backing memory.&lt;/p&gt;
&lt;h5 id=&quot;creating-vcpu-and-running&quot;&gt;Creating vCPU and running&lt;/h5&gt;
&lt;p&gt;Now, it gets very busy in the logs, but most of the stuff we see is still just&lt;br&gt;
checking for extensions and capabilities. However, if we take a look at the&lt;br&gt;
tail of the log, we will see a lot of these &lt;code&gt;ioctl&lt;/code&gt; calls:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xae80 /* KVM_RUN */, 0) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xaeb7 /* KVM_SMI */, 0) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xae80 /* KVM_RUN */, 0) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xae80 /* KVM_RUN */, 0) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xae80 /* KVM_RUN */, 0) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xae80 /* KVM_RUN */, 0) = 0
140904 ioctl(10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;, 0xaeb7 /* KVM_SMI */, 0) = 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now both &lt;code&gt;KVM_RUN&lt;/code&gt; and &lt;code&gt;KVM_SMI&lt;/code&gt; are operating on a &lt;code&gt;kvm-vcpu&lt;/code&gt; file descriptor,&lt;br&gt;
something we haven't yet seen. So if we search the logs for it, we can actually&lt;br&gt;
see where it's created:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;140904 ioctl(9&amp;lt;anon_inode:kvm-vm&amp;gt;, 0xae41 /* KVM_CREATE_VCPU */, 0) = 10&amp;lt;anon_inode:kvm-vcpu:0&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;conclusion-3&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Now we have a more complete picture of how QEMU is setting up KVM. First,&lt;br&gt;
&lt;code&gt;/dev/kvm&lt;/code&gt; is opened to obtain a file descriptor representing the KVM subsystem.&lt;br&gt;
From it we create a new virtual machine and get &lt;code&gt;kvm-vm&lt;/code&gt; file descriptor. On&lt;br&gt;
this file descriptor we are setting up memory regions and later use it to create&lt;br&gt;
a vCPU, on which we can call &lt;code&gt;KVM_RUN&lt;/code&gt;. The following diagram explains it&lt;br&gt;
better:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/dev/kvm   kvm fd
    |
    +--&amp;gt; KVM_CREATE_VM   kvm-vm fd
            |
            +--&amp;gt; KVM_SET_USER_MEMORY_REGION
            |
            +--&amp;gt; KVM_CREATE_VCPU   kvm-vcpu fd
                            |
                            +--&amp;gt; KVM_RUN (loop)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As we can see from the logs, &lt;code&gt;KVM_RUN&lt;/code&gt; appears repeatedly during guest&lt;br&gt;
execution, while occasional &lt;code&gt;KVM_SMI&lt;/code&gt; calls inject System Management Interrupts&lt;br&gt;
into the guest. This repeated interaction between userspace and KVM is what&lt;br&gt;
ultimately drives virtual CPU execution.&lt;/p&gt;
&lt;p&gt;Next time we will recreate this exact behavior in Rust and also see about just&lt;br&gt;
a few missing pieces to get our first virtual machine running in KVM.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20315/learning-kvm-by-reverse-engineering-qemu-with-strace</guid>
<pubDate>Thu, 11 Jun 2026 15:56:48 +0000</pubDate>
</item>
<item>
<title>Why I Still Choose Go After All These Years</title>
<link>https://coderlegion.com/20174/why-i-still-choose-go-after-all-these-years</link>
<description>&lt;p&gt;&lt;strong&gt;Hello everyone.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I'd like to discuss a topic that has sparked endless debates among developers for years:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What programming language should you choose for a serious project?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Throughout my career, I've worked with C, Assembly, Go, and spent a considerable amount of time exploring modern languages such as Rust and C++. The more experience I gain, the more convinced I become that &lt;strong&gt;there is no perfect programming language&lt;/strong&gt;. Every language has its own strengths and weaknesses.&lt;/p&gt;
&lt;p&gt;Despite that, I deliberately chose &lt;strong&gt;Go&lt;/strong&gt; as the foundation of my primary project: &lt;strong&gt;ForgeZero&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;ForgeZero is a cross-platform build orchestration tool written in Go, with carefully optimized performance-critical components implemented in Plan 9 Assembly to minimize runtime overhead and eliminate unnecessary allocations.&lt;/p&gt;
&lt;p&gt;At first glance, it might seem logical that after working with C and Assembly, the next step should be Rust or modern C++, since they are widely considered the future of systems programming.&lt;/p&gt;
&lt;h3 id=&quot;rust&quot;&gt;Rust&lt;/h3&gt;
&lt;p&gt;Rust solves an impressive number of memory safety problems.&lt;/p&gt;
&lt;p&gt;Its ownership model and borrow checker force developers to think about lifetimes and resource management before the code even compiles.&lt;/p&gt;
&lt;p&gt;However, safety comes at a cost.&lt;/p&gt;
&lt;p&gt;There are moments when it feels like the language dictates your architecture rather than the other way around. In large projects, satisfying the borrow checker can sometimes consume more time than solving the original problem itself.&lt;/p&gt;
&lt;p&gt;Even with Rust's strict guarantees, bugs still exist. No language can eliminate every possible programming mistake.&lt;/p&gt;
&lt;p&gt;Personally, I also find large Rust codebases relatively difficult to read. The amount of syntax and abstractions often makes understanding unfamiliar code slower than it should be.&lt;/p&gt;
&lt;h3 id=&quot;c-&quot;&gt;C++&lt;/h3&gt;
&lt;p&gt;C++ is an incredibly powerful language.&lt;/p&gt;
&lt;p&gt;Unfortunately, that power comes with enormous complexity.&lt;/p&gt;
&lt;p&gt;Templates, metaprogramming, concepts, multiple paradigms, countless coding styles, and dozens of ways to solve the same problem can make maintaining large codebases challenging.&lt;/p&gt;
&lt;p&gt;Sometimes reading someone else's C++ project feels less like software engineering and more like an archaeological expedition.&lt;/p&gt;
&lt;h3 id=&quot;c&quot;&gt;C&lt;/h3&gt;
&lt;p&gt;C remains one of the greatest programming languages ever created.&lt;/p&gt;
&lt;p&gt;It provides maximum control over the system, minimal abstractions, and extremely predictable performance, making it an outstanding choice for low-level development.&lt;/p&gt;
&lt;p&gt;The downside, however, is responsibility.&lt;/p&gt;
&lt;p&gt;Every pointer, every allocation, and every memory access becomes your responsibility. Bugs such as use-after-free, buffer overflows, or segmentation faults can be devastating.&lt;/p&gt;
&lt;p&gt;And perhaps even more importantly, building large software systems in C requires an enormous investment of engineering time.&lt;/p&gt;
&lt;p&gt;Had ForgeZero been written entirely in C, both development and long-term maintenance would have been significantly more expensive.&lt;/p&gt;
&lt;h3 id=&quot;why-go-&quot;&gt;Why Go?&lt;/h3&gt;
&lt;p&gt;Go occupies a very unique position.&lt;/p&gt;
&lt;p&gt;It is simple enough that new contributors can understand a codebase within days, while still being powerful enough to build serious production-grade software.&lt;/p&gt;
&lt;p&gt;Goroutines, channels, built-in concurrency, and a minimalistic syntax allow developers to focus on solving problems instead of fighting the language.&lt;/p&gt;
&lt;p&gt;Of course, Go has its own limitations.&lt;/p&gt;
&lt;p&gt;The runtime, garbage collector, and automatic memory management make it a poor choice for operating system kernels, drivers, or extremely low-level software.&lt;/p&gt;
&lt;p&gt;Yet the common belief that Go cannot deliver high performance is simply incorrect.&lt;/p&gt;
&lt;p&gt;With careful architecture, proper escape analysis, object pooling, and manual optimization of performance-critical paths, it is entirely possible to achieve nearly zero allocations where they actually matter.&lt;/p&gt;
&lt;p&gt;That is exactly the direction ForgeZero takes.&lt;/p&gt;
&lt;p&gt;For me, Go represents an excellent balance between development speed, maintainability, readability, and performance.&lt;/p&gt;
&lt;p&gt;It is not perfect.&lt;/p&gt;
&lt;p&gt;But it allows me to build complex tooling faster than most alternatives without sacrificing quality.&lt;/p&gt;
&lt;p&gt;That is why, after all these years, I still choose Go.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;am-i-betraying-c-&quot;&gt;Am I Betraying C?&lt;/h2&gt;
&lt;p&gt;Absolutely not.&lt;/p&gt;
&lt;p&gt;C shaped me as a software engineer, and I still consider it one of the greatest programming languages ever designed.&lt;/p&gt;
&lt;p&gt;In fact, one of ForgeZero's goals is to &lt;strong&gt;make C development easier&lt;/strong&gt;, improving build workflows and project management for both newcomers and experienced engineers.&lt;/p&gt;
&lt;p&gt;Go may be my primary language today, but that does not mean abandoning C.&lt;/p&gt;
&lt;p&gt;A huge portion of modern software infrastructure still depends on it.&lt;/p&gt;
&lt;p&gt;Operating system kernels, drivers, networking libraries, embedded software, and countless system components have been built and refined in C for decades.&lt;/p&gt;
&lt;p&gt;Many developers now discuss Rust's introduction into the Linux kernel and the future of systems programming.&lt;/p&gt;
&lt;p&gt;But realistically, no major company or open-source project is going to rewrite millions of lines of stable legacy C code overnight simply because a newer language exists.&lt;/p&gt;
&lt;p&gt;Large migrations take years—sometimes decades—and happen gradually.&lt;/p&gt;
&lt;p&gt;For that reason alone, C will remain relevant for a very long time.&lt;/p&gt;
&lt;p&gt;And personally, I still use it whenever it is the right tool for the job.&lt;/p&gt;
&lt;p&gt;Recently, while training a small neural network on my home machine, I encountered an interesting problem.&lt;/p&gt;
&lt;p&gt;The hardware is far from modern: an eight-core CPU from 2014 paired with a GTX 1060 6GB (Pascal architecture). Although training was performed on the GPU through PyTorch, the CPU was still under significant thermal stress.&lt;/p&gt;
&lt;p&gt;Instead of building a monitoring solution in Python or deploying heavyweight background services, I wrote a small daemon in C that continuously monitored CPU temperature and automatically terminated non-essential processes once critical thresholds were reached.&lt;/p&gt;
&lt;p&gt;Could I have written the same utility in Go?&lt;/p&gt;
&lt;p&gt;Absolutely.&lt;/p&gt;
&lt;p&gt;But for this particular low-level task, C was simply the better tool.&lt;/p&gt;
&lt;p&gt;No runtime, minimal dependencies, tiny resource usage, and complete control over the operating system.&lt;/p&gt;
&lt;p&gt;That is exactly why I never claim that Go should replace C.&lt;/p&gt;
&lt;p&gt;Quite the opposite.&lt;/p&gt;
&lt;p&gt;I believe that &lt;strong&gt;Go + C + Assembly&lt;/strong&gt; is an incredibly powerful combination.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Go&lt;/strong&gt; excels at infrastructure, CLI applications, automation, backend services, and distributed systems.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;C&lt;/strong&gt; remains unmatched for systems programming and direct interaction with operating system internals.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Assembly&lt;/strong&gt; is invaluable when every CPU cycle matters and maximum optimization is required.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A good engineer should choose the right tool for the problem—not the trendiest language on social media.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;my-advice-for-beginners&quot;&gt;My Advice for Beginners&lt;/h2&gt;
&lt;p&gt;If I had to start my programming journey again, I would begin with &lt;strong&gt;C&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Not because it is the &quot;best&quot; language, but because it forces you to understand how computers actually work.&lt;/p&gt;
&lt;p&gt;How memory is organized.&lt;/p&gt;
&lt;p&gt;How the stack and the heap operate.&lt;/p&gt;
&lt;p&gt;How pointers behave.&lt;/p&gt;
&lt;p&gt;How a compiler transforms source code into assembly instructions and eventually into machine code executed by the processor.&lt;/p&gt;
&lt;p&gt;Once those fundamentals become clear, learning Go, Rust, C++, Java, or almost any other language becomes dramatically easier.&lt;/p&gt;
&lt;p&gt;You stop memorizing syntax and start understanding software engineering.&lt;/p&gt;
&lt;p&gt;So my advice is simple:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don't be afraid of low-level programming.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Even if your future career revolves around web services or cloud infrastructure, understanding what happens beneath the abstractions will make you a far stronger engineer.&lt;/p&gt;
&lt;p&gt;Programming languages come and go.&lt;/p&gt;
&lt;p&gt;Fundamental computer science knowledge stays with you forever.&lt;/p&gt;
</description>
<category>Discussions</category>
<guid isPermaLink="true">https://coderlegion.com/20174/why-i-still-choose-go-after-all-these-years</guid>
<pubDate>Wed, 10 Jun 2026 13:38:44 +0000</pubDate>
</item>
<item>
<title>Rust Concepts: dyn Trait, Custom Iterators, Deref/Drop &amp; Axum REST API (Part 5)</title>
<link>https://coderlegion.com/19038/rust-concepts-dyn-trait-custom-iterators-deref-drop-axum-rest-api-part-5</link>
<description>&lt;hr&gt;
&lt;p&gt;title: Rust Concepts: dyn Trait, Custom Iterators, Deref/Drop &amp;amp; Axum REST API (Part 5)&lt;br&gt;
published: true&lt;br&gt;
tags: rust, webdev, intermediate, systems&lt;br&gt;
cover_image: &lt;a rel=&quot;nofollow&quot; href=&quot;https://images.unsplash.com/photo-1504639725590-34d0984388bd?w=1000&quot;&gt;https://images.unsplash.com/photo-1504639725590-34d0984388bd?w=1000&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;series-core-rust-concepts&quot;&gt;series: Core Rust Concepts&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;This is &lt;strong&gt;Part 5&lt;/strong&gt; of the Core Rust Concepts series.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Part 1&lt;/a&gt; — Ownership, Borrowing, Lifetimes, Traits, Result/Option, Pattern Matching&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Part 2&lt;/a&gt; — Closures, Iterators, Generics, Enums, Smart Pointers, Async/Await&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Part 3&lt;/a&gt; — Macros, Modules, Testing, Unsafe Rust, FFI&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Part 4&lt;/a&gt; — Threads, Channels, Send/Sync, Mutex, Atomics, clap&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;table-of-contents&quot;&gt;Table of Contents&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#25-trait-objects-and-dynamic-dispatch&quot;&gt;Trait Objects and Dynamic Dispatch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#26-the-deref-trait&quot;&gt;The Deref Trait&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#27-the-drop-trait&quot;&gt;The Drop Trait&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#28-custom-iterators&quot;&gt;Custom Iterators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#29-building-a-rest-api-with-axum&quot;&gt;Building a REST API with Axum&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;25-trait-objects-and-dynamic-dispatch&quot;&gt;25. Trait Objects and Dynamic Dispatch&lt;/h3&gt;
&lt;p&gt;In Part 2 we used &lt;code&gt;impl Trait&lt;/code&gt; — that's &lt;strong&gt;static dispatch&lt;/strong&gt;: the compiler knows the exact type at compile time and monomorphizes it. Sometimes you don't know the type until runtime. That's where &lt;strong&gt;trait objects&lt;/strong&gt; (&lt;code&gt;dyn Trait&lt;/code&gt;) come in.&lt;/p&gt;
&lt;h4 id=&quot;static-vs-dynamic-dispatch&quot;&gt;Static vs Dynamic dispatch&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;trait Greet {
    fn hello(&amp;amp;self) -&amp;gt; String;
}

struct English;
struct Spanish;

impl Greet for English {
    fn hello(&amp;amp;self) -&amp;gt; String { String::from(&quot;Hello!&quot;) }
}

impl Greet for Spanish {
    fn hello(&amp;amp;self) -&amp;gt; String { String::from(&quot;¡Hola!&quot;) }
}

// Static dispatch — type known at compile time, zero overhead
fn greet_static(g: &amp;amp;impl Greet) {
    println!(&quot;{}&quot;, g.hello());
}

// Dynamic dispatch — type known at runtime via vtable
fn greet_dynamic(g: &amp;amp;dyn Greet) {
    println!(&quot;{}&quot;, g.hello());
}

fn main() {
    let e = English;
    let s = Spanish;

    greet_static(&amp;amp;e);
    greet_dynamic(&amp;amp;s);

    // Only dyn Trait allows mixing types in a collection
    let greeters: Vec&amp;lt;Box&amp;lt;dyn Greet&amp;gt;&amp;gt; = vec![
        Box::new(English),
        Box::new(Spanish),
        Box::new(English),
    ];

    for g in &amp;amp;greeters {
        println!(&quot;{}&quot;, g.hello());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;when-to-use-dyn-trait-vs-impl-trait&quot;&gt;When to use &lt;code&gt;dyn Trait&lt;/code&gt; vs &lt;code&gt;impl Trait&lt;/code&gt;&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;&lt;code&gt;impl Trait&lt;/code&gt; &lt;/th&gt;&lt;th&gt;&lt;code&gt;dyn Trait&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Dispatch &lt;/td&gt;&lt;td&gt;Static (compile time) &lt;/td&gt;&lt;td&gt;Dynamic (runtime vtable)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Performance &lt;/td&gt;&lt;td&gt;Zero overhead &lt;/td&gt;&lt;td&gt;Small indirection cost&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Heterogeneous collections &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Return from function &lt;/td&gt;&lt;td&gt;✅ (one concrete type) &lt;/td&gt;&lt;td&gt;✅ (any type at runtime)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Sized &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;❌ (must be behind &lt;code&gt;&amp;amp;&lt;/code&gt; or &lt;code&gt;Box&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;object-safe-traits&quot;&gt;Object-safe traits&lt;/h4&gt;
&lt;p&gt;Not every trait can be made into a trait object. A trait is &lt;strong&gt;object-safe&lt;/strong&gt; if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It has no methods that return &lt;code&gt;Self&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;It has no generic methods&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;// ✅ Object-safe
trait Drawable {
    fn draw(&amp;amp;self);
    fn area(&amp;amp;self) -&amp;gt; f64;
}

// ❌ NOT object-safe — returns Self
trait Clone2 {
    fn clone2(&amp;amp;self) -&amp;gt; Self; // can't use as dyn Clone2
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;real-world-pattern-plugin-system&quot;&gt;Real-world pattern — plugin system&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;trait Plugin {
    fn name(&amp;amp;self) -&amp;gt; &amp;amp;str;
    fn run(&amp;amp;self, input: &amp;amp;str) -&amp;gt; String;
}

struct UppercasePlugin;
struct ReversePlugin;

impl Plugin for UppercasePlugin {
    fn name(&amp;amp;self) -&amp;gt; &amp;amp;str { &quot;uppercase&quot; }
    fn run(&amp;amp;self, input: &amp;amp;str) -&amp;gt; String { input.to_uppercase() }
}

impl Plugin for ReversePlugin {
    fn name(&amp;amp;self) -&amp;gt; &amp;amp;str { &quot;reverse&quot; }
    fn run(&amp;amp;self, input: &amp;amp;str) -&amp;gt; String { input.chars().rev().collect() }
}

struct Pipeline {
    plugins: Vec&amp;lt;Box&amp;lt;dyn Plugin&amp;gt;&amp;gt;,
}

impl Pipeline {
    fn new() -&amp;gt; Self { Self { plugins: vec![] } }

    fn add(mut self, p: Box&amp;lt;dyn Plugin&amp;gt;) -&amp;gt; Self {
        self.plugins.push(p);
        self
    }

    fn run(&amp;amp;self, input: &amp;amp;str) -&amp;gt; String {
        self.plugins.iter().fold(input.to_string(), |acc, p| p.run(&amp;amp;acc))
    }
}

fn main() {
    let pipeline = Pipeline::new()
        .add(Box::new(UppercasePlugin))
        .add(Box::new(ReversePlugin));

    println!(&quot;{}&quot;, pipeline.run(&quot;hello&quot;)); // &quot;OLLEH&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;26-the-deref-trait&quot;&gt;26. The Deref Trait&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Deref&lt;/code&gt; controls what happens when you use the &lt;code&gt;*&lt;/code&gt; operator on a type. It's also what enables &lt;strong&gt;deref coercions&lt;/strong&gt; — Rust automatically calls &lt;code&gt;deref()&lt;/code&gt; to convert types so you don't have to do it manually.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use std::ops::Deref;

// A simple smart pointer
struct MyBox&amp;lt;T&amp;gt;(T);

impl&amp;lt;T&amp;gt; MyBox&amp;lt;T&amp;gt; {
    fn new(x: T) -&amp;gt; MyBox&amp;lt;T&amp;gt; { MyBox(x) }
}

impl&amp;lt;T&amp;gt; Deref for MyBox&amp;lt;T&amp;gt; {
    type Target = T;

    fn deref(&amp;amp;self) -&amp;gt; &amp;amp;Self::Target {
        &amp;amp;self.0 // return a reference to the inner value
    }
}

fn hello(name: &amp;amp;str) {
    println!(&quot;Hello, {name}!&quot;);
}

fn main() {
    let x = MyBox::new(String::from(&quot;Rustacean&quot;));

    // These are all equivalent due to deref coercions:
    hello(&amp;amp;x);           // MyBox&amp;lt;String&amp;gt; → &amp;amp;String → &amp;amp;str  (auto!)
    hello(&amp;amp;(*x)[..]);    // manual — what Rust does behind the scenes
    hello(x.deref());    // explicit deref
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;deref-coercion-chain&quot;&gt;Deref coercion chain&lt;/h4&gt;
&lt;p&gt;Rust automatically chains &lt;code&gt;Deref&lt;/code&gt; calls as many times as needed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;MyBox&amp;lt;String&amp;gt;  →  String  →  str
    &amp;amp;MyBox&amp;lt;String&amp;gt;  →  &amp;amp;String  →  &amp;amp;str
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn main() {
    let boxed = Box::new(String::from(&quot;hello&quot;));

    // Box&amp;lt;String&amp;gt; derefs to String, then to str
    println!(&quot;{}&quot;, boxed.to_uppercase()); // HELLO
    println!(&quot;{}&quot;, boxed.len());          // 5

    let s: &amp;amp;str = &amp;amp;boxed; // Box&amp;lt;String&amp;gt; → &amp;amp;String → &amp;amp;str — all automatic
    println!(&quot;{s}&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;derefmut&quot;&gt;DerefMut&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;DerefMut&lt;/code&gt; does the same for mutable contexts:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use std::ops::{Deref, DerefMut};

struct Wrapper(Vec&amp;lt;i32&amp;gt;);

impl Deref for Wrapper {
    type Target = Vec&amp;lt;i32&amp;gt;;
    fn deref(&amp;amp;self) -&amp;gt; &amp;amp;Vec&amp;lt;i32&amp;gt; { &amp;amp;self.0 }
}

impl DerefMut for Wrapper {
    fn deref_mut(&amp;amp;mut self) -&amp;gt; &amp;amp;mut Vec&amp;lt;i32&amp;gt; { &amp;amp;mut self.0 }
}

fn main() {
    let mut w = Wrapper(vec![1, 2, 3]);
    w.push(4);                     // DerefMut lets you call Vec methods
    println!(&quot;{:?}&quot;, w.as_slice()); // Deref: [1, 2, 3, 4]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;27-the-drop-trait&quot;&gt;27. The Drop Trait&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Drop&lt;/code&gt; lets you run custom cleanup code when a value goes out of scope — like a destructor in C++. Rust calls &lt;code&gt;drop()&lt;/code&gt; automatically; you never call it yourself (use &lt;code&gt;std::mem::drop()&lt;/code&gt; to drop early).&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;struct Resource {
    name: String,
}

impl Resource {
    fn new(name: &amp;amp;str) -&amp;gt; Self {
        println!(&quot;acquiring: {name}&quot;);
        Resource { name: name.to_string() }
    }
}

impl Drop for Resource {
    fn drop(&amp;amp;mut self) {
        println!(&quot;releasing: {}&quot;, self.name);
    }
}

fn main() {
    let _a = Resource::new(&quot;database connection&quot;);
    let _b = Resource::new(&quot;file handle&quot;);
    println!(&quot;resources in use...&quot;);
    // Drop order is LIFO: _b dropped first, then _a
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;acquiring: database connection
acquiring: file handle
resources in use...
releasing: file handle
releasing: database connection
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;dropping-early&quot;&gt;Dropping early&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn main() {
    let conn = Resource::new(&quot;db&quot;);
    println!(&quot;doing work...&quot;);

    drop(conn); // explicit early drop — calls Drop::drop()
    println!(&quot;conn already released&quot;);

    // conn.name; ← compile error: value was dropped
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;drop-in-practice-raii-pattern&quot;&gt;Drop in practice — RAII pattern&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use std::sync::{Arc, Mutex};

struct DbPool {
    connections: Arc&amp;lt;Mutex&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;&amp;gt;,
}

struct DbConnection {
    id: String,
    pool: Arc&amp;lt;Mutex&amp;lt;Vec&amp;lt;String&amp;gt;&amp;gt;&amp;gt;,
}

impl Drop for DbConnection {
    fn drop(&amp;amp;mut self) {
        // Return the connection back to the pool automatically
        self.pool.lock().unwrap().push(self.id.clone());
        println!(&quot;connection '{}' returned to pool&quot;, self.id);
    }
}

impl DbPool {
    fn new() -&amp;gt; Self {
        let conns = vec![&quot;conn-1&quot;.into(), &quot;conn-2&quot;.into(), &quot;conn-3&quot;.into()];
        Self { connections: Arc::new(Mutex::new(conns)) }
    }

    fn get(&amp;amp;self) -&amp;gt; Option&amp;lt;DbConnection&amp;gt; {
        let id = self.connections.lock().unwrap().pop()?;
        println!(&quot;checked out: {id}&quot;);
        Some(DbConnection { id, pool: Arc::clone(&amp;amp;self.connections) })
    }
}

fn main() {
    let pool = DbPool::new();
    {
        let _c1 = pool.get(); // &quot;conn-3&quot;
        let _c2 = pool.get(); // &quot;conn-2&quot;
    } // both returned to pool here automatically
    println!(&quot;pool size: {}&quot;, pool.connections.lock().unwrap().len()); // 3
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt; This is the &lt;strong&gt;RAII&lt;/strong&gt; (Resource Acquisition Is Initialization) pattern — the same one Rust's &lt;code&gt;MutexGuard&lt;/code&gt;, file handles, and socket wrappers use internally.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;28-custom-iterators&quot;&gt;28. Custom Iterators&lt;/h3&gt;
&lt;p&gt;You can make any type iterable by implementing the &lt;code&gt;Iterator&lt;/code&gt; trait, which requires only one method: &lt;code&gt;next()&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;struct Counter {
    count: u32,
    max: u32,
}

impl Counter {
    fn new(max: u32) -&amp;gt; Self { Self { count: 0, max } }
}

impl Iterator for Counter {
    type Item = u32;

    fn next(&amp;amp;mut self) -&amp;gt; Option&amp;lt;Self::Item&amp;gt; {
        if self.count &amp;lt; self.max {
            self.count += 1;
            Some(self.count)
        } else {
            None // signals the iterator is exhausted
        }
    }
}

fn main() {
    let counter = Counter::new(5);

    // You get ALL iterator methods for free!
    let sum: u32 = counter.sum();
    println!(&quot;sum: {sum}&quot;); // 15

    // Chain with other iterators
    let result: Vec&amp;lt;u32&amp;gt; = Counter::new(5)
        .zip(Counter::new(5).skip(1))   // pair consecutive values
        .map(|(a, b)| a * b)            // multiply each pair
        .filter(|x| x % 3 == 0)        // keep multiples of 3
        .collect();

    println!(&quot;{:?}&quot;, result); // [6, 12]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;a-real-world-custom-iterator-paginated-results&quot;&gt;A real-world custom iterator — paginated results&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;struct Paginator {
    current_page: u32,
    total_pages: u32,
    page_size: u32,
}

impl Paginator {
    fn new(total_items: u32, page_size: u32) -&amp;gt; Self {
        Self {
            current_page: 0,
            total_pages: (total_items + page_size - 1) / page_size,
            page_size,
        }
    }
}

#[derive(Debug)]
struct Page {
    number: u32,
    offset: u32,
    limit: u32,
}

impl Iterator for Paginator {
    type Item = Page;

    fn next(&amp;amp;mut self) -&amp;gt; Option&amp;lt;Page&amp;gt; {
        if self.current_page &amp;lt; self.total_pages {
            self.current_page += 1;
            Some(Page {
                number: self.current_page,
                offset: (self.current_page - 1) * self.page_size,
                limit: self.page_size,
            })
        } else {
            None
        }
    }
}

fn main() {
    let pages = Paginator::new(95, 20);

    for page in pages {
        println!(&quot;Page {}: OFFSET {} LIMIT {}&quot;, page.number, page.offset, page.limit);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Output:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Page 1: OFFSET 0 LIMIT 20
Page 2: OFFSET 20 LIMIT 20
Page 3: OFFSET 40 LIMIT 20
Page 4: OFFSET 60 LIMIT 20
Page 5: OFFSET 80 LIMIT 20
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;intoiterator-making-structs-work-in-for-loops&quot;&gt;IntoIterator — making structs work in for loops&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;struct Stack&amp;lt;T&amp;gt; {
    items: Vec&amp;lt;T&amp;gt;,
}

impl&amp;lt;T&amp;gt; Stack&amp;lt;T&amp;gt; {
    fn new() -&amp;gt; Self { Self { items: vec![] } }
    fn push(&amp;amp;mut self, item: T) { self.items.push(item); }
}

impl&amp;lt;T&amp;gt; IntoIterator for Stack&amp;lt;T&amp;gt; {
    type Item = T;
    type IntoIter = std::vec::IntoIter&amp;lt;T&amp;gt;;

    fn into_iter(self) -&amp;gt; Self::IntoIter {
        self.items.into_iter()
    }
}

fn main() {
    let mut stack = Stack::new();
    stack.push(1);
    stack.push(2);
    stack.push(3);

    for item in stack { // works because of IntoIterator
        println!(&quot;{item}&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;29-building-a-rest-api-with-axum&quot;&gt;29. Building a REST API with Axum&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;axum&lt;/code&gt; is the most popular async web framework in Rust, built on top of &lt;code&gt;tokio&lt;/code&gt; and &lt;code&gt;hyper&lt;/code&gt;. It uses Rust's type system to make routing and request extraction ergonomic and safe.&lt;/p&gt;
&lt;h4 id=&quot;setup&quot;&gt;Setup&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;# Cargo.toml
[dependencies]
axum      = &quot;0.7&quot;
tokio     = { version = &quot;1&quot;, features = [&quot;full&quot;] }
serde     = { version = &quot;1&quot;, features = [&quot;derive&quot;] }
serde_json = &quot;1&quot;
uuid      = { version = &quot;1&quot;, features = [&quot;v4&quot;] }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;full-crud-api-todo-list&quot;&gt;Full CRUD API — Todo List&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use axum::{
    extract::{Path, State},
    http::StatusCode,
    response::Json,
    routing::{delete, get, post, put},
    Router,
};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
use uuid::Uuid;

// --- Models ---

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Todo {
    id: String,
    title: String,
    completed: bool,
}

#[derive(Deserialize)]
struct CreateTodo {
    title: String,
}

#[derive(Deserialize)]
struct UpdateTodo {
    title: Option&amp;lt;String&amp;gt;,
    completed: Option&amp;lt;bool&amp;gt;,
}

// --- Shared state ---

type Db = Arc&amp;lt;Mutex&amp;lt;Vec&amp;lt;Todo&amp;gt;&amp;gt;&amp;gt;;

// --- Handlers ---

async fn list_todos(State(db): State&amp;lt;Db&amp;gt;) -&amp;gt; Json&amp;lt;Vec&amp;lt;Todo&amp;gt;&amp;gt; {
    let todos = db.lock().unwrap();
    Json(todos.clone())
}

async fn create_todo(
    State(db): State&amp;lt;Db&amp;gt;,
    Json(payload): Json&amp;lt;CreateTodo&amp;gt;,
) -&amp;gt; (StatusCode, Json&amp;lt;Todo&amp;gt;) {
    let todo = Todo {
        id: Uuid::new_v4().to_string(),
        title: payload.title,
        completed: false,
    };
    db.lock().unwrap().push(todo.clone());
    (StatusCode::CREATED, Json(todo))
}

async fn get_todo(
    State(db): State&amp;lt;Db&amp;gt;,
    Path(id): Path&amp;lt;String&amp;gt;,
) -&amp;gt; Result&amp;lt;Json&amp;lt;Todo&amp;gt;, StatusCode&amp;gt; {
    let todos = db.lock().unwrap();
    todos
        .iter()
        .find(|t| t.id == id)
        .cloned()
        .map(Json)
        .ok_or(StatusCode::NOT_FOUND)
}

async fn update_todo(
    State(db): State&amp;lt;Db&amp;gt;,
    Path(id): Path&amp;lt;String&amp;gt;,
    Json(payload): Json&amp;lt;UpdateTodo&amp;gt;,
) -&amp;gt; Result&amp;lt;Json&amp;lt;Todo&amp;gt;, StatusCode&amp;gt; {
    let mut todos = db.lock().unwrap();
    let todo = todos.iter_mut().find(|t| t.id == id).ok_or(StatusCode::NOT_FOUND)?;

    if let Some(title) = payload.title { todo.title = title; }
    if let Some(completed) = payload.completed { todo.completed = completed; }

    Ok(Json(todo.clone()))
}

async fn delete_todo(
    State(db): State&amp;lt;Db&amp;gt;,
    Path(id): Path&amp;lt;String&amp;gt;,
) -&amp;gt; StatusCode {
    let mut todos = db.lock().unwrap();
    let before = todos.len();
    todos.retain(|t| t.id != id);

    if todos.len() &amp;lt; before { StatusCode::NO_CONTENT }
    else { StatusCode::NOT_FOUND }
}

// --- Router ---

fn app(db: Db) -&amp;gt; Router {
    Router::new()
        .route(&quot;/todos&quot;,      get(list_todos).post(create_todo))
        .route(&quot;/todos/:id&quot;,  get(get_todo).put(update_todo).delete(delete_todo))
        .with_state(db)
}

// --- Main ---

#[tokio::main]
async fn main() {
    let db: Db = Arc::new(Mutex::new(vec![]));

    let listener = tokio::net::TcpListener::bind(&quot;0.0.0.0:3000&quot;).await.unwrap();
    println!(&quot;listening on http://localhost:3000&quot;);

    axum::serve(listener, app(db)).await.unwrap();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;test-it-with-curl&quot;&gt;Test it with curl&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Create todos
curl -s -X POST http://localhost:3000/todos \
  -H &quot;Content-Type: application/json&quot; \
  -d '{&quot;title&quot;: &quot;Learn Rust&quot;}' | jq

curl -s -X POST http://localhost:3000/todos \
  -H &quot;Content-Type: application/json&quot; \
  -d '{&quot;title&quot;: &quot;Build an API&quot;}' | jq

# List all
curl -s http://localhost:3000/todos | jq

# Get one (replace ID)
curl -s http://localhost:3000/todos/&amp;lt;id&amp;gt; | jq

# Update
curl -s -X PUT http://localhost:3000/todos/&amp;lt;id&amp;gt; \
  -H &quot;Content-Type: application/json&quot; \
  -d '{&quot;completed&quot;: true}' | jq

# Delete
curl -s -X DELETE http://localhost:3000/todos/&amp;lt;id&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;adding-middleware-request-logging&quot;&gt;Adding middleware — request logging&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use axum::middleware::{self, Next};
use axum::extract::Request;
use axum::response::Response;

async fn log_request(req: Request, next: Next) -&amp;gt; Response {
    let method = req.method().clone();
    let uri    = req.uri().clone();
    let res    = next.run(req).await;
    println!(&quot;{} {} → {}&quot;, method, uri, res.status());
    res
}

fn app(db: Db) -&amp;gt; Router {
    Router::new()
        .route(&quot;/todos&quot;,     get(list_todos).post(create_todo))
        .route(&quot;/todos/:id&quot;, get(get_todo).put(update_todo).delete(delete_todo))
        .layer(middleware::from_fn(log_request)) // add logging to all routes
        .with_state(db)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;wrapping-up&quot;&gt;Wrapping Up&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Concept &lt;/th&gt;&lt;th&gt;What it gives you&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;dyn Trait&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Runtime polymorphism, heterogeneous collections&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Deref&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Transparent smart pointer ergonomics, auto-coercions&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Drop&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;RAII — deterministic cleanup, no GC needed&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Custom iterators&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Any type can plug into Rust's iterator ecosystem&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Axum&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Type-safe, async REST APIs on top of tokio&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-full-series&quot;&gt;The full series&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Part &lt;/th&gt;&lt;th&gt;Topics&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Part 1&lt;/a&gt; &lt;/td&gt;&lt;td&gt;Ownership, Borrowing, Lifetimes, Traits, Result/Option, Pattern Matching&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Part 2&lt;/a&gt; &lt;/td&gt;&lt;td&gt;Closures, Iterators, Generics, Enums, Smart Pointers, Async/Await&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Part 3&lt;/a&gt; &lt;/td&gt;&lt;td&gt;Macros, Modules, Cargo, Testing, Unsafe, FFI&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Part 4&lt;/a&gt; &lt;/td&gt;&lt;td&gt;Threads, Channels, Send/Sync, Mutex, Atomics, clap&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Part 5&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;dyn Trait, Deref, Drop, Custom Iterators, Axum REST API&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h4 id=&quot;what-s-in-part-6-&quot;&gt;What's in Part 6?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Workspaces and multi-crate projects&lt;/li&gt;
&lt;li&gt;&lt;code&gt;serde&lt;/code&gt; — serialization deep dive&lt;/li&gt;
&lt;li&gt;Benchmarking with &lt;code&gt;criterion&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Error handling patterns with &lt;code&gt;thiserror&lt;/code&gt; and &lt;code&gt;anyhow&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Writing a real-world CLI + library crate&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Found this useful? Drop a ❤️ and follow for Part 6!&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19038/rust-concepts-dyn-trait-custom-iterators-deref-drop-axum-rest-api-part-5</guid>
<pubDate>Wed, 27 May 2026 07:37:07 +0000</pubDate>
</item>
<item>
<title>Rust Concurrency: Threads, Channels, Mutex &amp; Sync (Part 4)</title>
<link>https://coderlegion.com/19037/rust-concurrency-threads-channels-mutex-sync-part-4</link>
<description>&lt;hr&gt;
&lt;p&gt;title: Rust Concurrency: Threads, Channels, Mutex &amp;amp; Sync (Part 4)&lt;br&gt;
published: true&lt;br&gt;
tags: rust, concurrency, intermediate, systems&lt;br&gt;
cover_image: &lt;a rel=&quot;nofollow&quot; href=&quot;https://images.unsplash.com/photo-1558494949-ef010cbdcc31?w=1000&quot;&gt;https://images.unsplash.com/photo-1558494949-ef010cbdcc31?w=1000&lt;/a&gt;&lt;/p&gt;
&lt;h3 id=&quot;series-core-rust-concepts-1&quot;&gt;series: Core Rust Concepts&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;This is &lt;strong&gt;Part 4&lt;/strong&gt; of the Core Rust Concepts series.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Part 1&lt;/a&gt; — Ownership, Borrowing, Lifetimes, Traits, Result/Option, Pattern Matching&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Part 2&lt;/a&gt; — Closures, Iterators, Generics, Enums, Smart Pointers, Async/Await&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Part 3&lt;/a&gt; — Macros, Modules, Testing, Unsafe Rust, FFI&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;table-of-contents-1&quot;&gt;Table of Contents&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#19-threads&quot;&gt;Threads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#20-message-passing-with-channels&quot;&gt;Message Passing with Channels&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#21-send-and-sync-traits&quot;&gt;Send and Sync Traits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#22-mutex-and-rwlock&quot;&gt;Mutex and RwLock&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#23-atomics&quot;&gt;Atomics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#24-building-a-cli-tool-with-clap&quot;&gt;Building a CLI Tool with clap&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;19-threads&quot;&gt;19. Threads&lt;/h3&gt;
&lt;p&gt;Rust's standard library provides OS threads via &lt;code&gt;std::thread&lt;/code&gt;. The ownership and type system prevents data races at compile time — if your code compiles, it's free of data races. That's a guarantee no other systems language makes.&lt;/p&gt;
&lt;h4 id=&quot;spawning-a-thread&quot;&gt;Spawning a thread&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use std::thread;
use std::time::Duration;

fn main() {
    let handle = thread::spawn(|| {
        for i in 1..=5 {
            println!(&quot;thread: {i}&quot;);
            thread::sleep(Duration::from_millis(50));
        }
    });

    for i in 1..=3 {
        println!(&quot;main:   {i}&quot;);
        thread::sleep(Duration::from_millis(80));
    }

    handle.join().unwrap(); // wait for the thread to finish
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;moving-data-into-a-thread&quot;&gt;Moving data into a thread&lt;/h4&gt;
&lt;p&gt;Use &lt;code&gt;move&lt;/code&gt; to transfer ownership of data into the thread closure:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use std::thread;

fn main() {
    let data = vec![1, 2, 3, 4, 5];

    let handle = thread::spawn(move || {
        // data is moved into this thread
        let sum: i32 = data.iter().sum();
        println!(&quot;sum: {sum}&quot;);
    });

    // println!(&quot;{:?}&quot;, data); ← compile error: data was moved

    handle.join().unwrap();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;spawning-many-threads&quot;&gt;Spawning many threads&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use std::thread;

fn main() {
    let handles: Vec&amp;lt;_&amp;gt; = (0..8)
        .map(|i| {
            thread::spawn(move || {
                println!(&quot;worker {i} done&quot;);
                i * i
            })
        })
        .collect();

    let results: Vec&amp;lt;_&amp;gt; = handles
        .into_iter()
        .map(|h| h.join().unwrap())
        .collect();

    println!(&quot;squares: {:?}&quot;, results);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt; &lt;code&gt;thread::spawn&lt;/code&gt; returns a &lt;code&gt;JoinHandle&amp;lt;T&amp;gt;&lt;/code&gt; where &lt;code&gt;T&lt;/code&gt; is the return type of the closure. Always &lt;code&gt;.join()&lt;/code&gt; your handles — a panicking thread silently fails otherwise.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;20-message-passing-with-channels&quot;&gt;20. Message Passing with Channels&lt;/h3&gt;
&lt;p&gt;Rust's standard library includes &lt;strong&gt;multiple-producer, single-consumer (mpsc)&lt;/strong&gt; channels. The philosophy: &lt;em&gt;&quot;do not communicate by sharing memory; share memory by communicating.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;h4 id=&quot;basic-channel&quot;&gt;Basic channel&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel();

    thread::spawn(move || {
        let msgs = vec![&quot;hello&quot;, &quot;from&quot;, &quot;the&quot;, &quot;thread&quot;];
        for msg in msgs {
            tx.send(msg).unwrap();
        }
    });

    for received in rx {
        println!(&quot;got: {received}&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;multiple-producers&quot;&gt;Multiple producers&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use std::sync::mpsc;
use std::thread;

fn main() {
    let (tx, rx) = mpsc::channel::&amp;lt;String&amp;gt;();

    for id in 0..4 {
        let tx_clone = tx.clone(); // each thread gets its own sender
        thread::spawn(move || {
            tx_clone.send(format!(&quot;msg from worker {id}&quot;)).unwrap();
        });
    }

    drop(tx); // drop the original so rx knows when all senders are gone

    for msg in rx {
        println!(&quot;{msg}&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;synchronous-channel-bounded-&quot;&gt;Synchronous channel (bounded)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use std::sync::mpsc;
use std::thread;

fn main() {
    // Buffer of 2 — sender blocks when full
    let (tx, rx) = mpsc::sync_channel::&amp;lt;i32&amp;gt;(2);

    thread::spawn(move || {
        for i in 0..5 {
            println!(&quot;sending {i}&quot;);
            tx.send(i).unwrap(); // blocks at i=2 until receiver reads
        }
    });

    thread::sleep(std::time::Duration::from_millis(200));

    for val in rx {
        println!(&quot;received {val}&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt; For more advanced channel patterns (broadcast, watch, oneshot), use the &lt;code&gt;tokio::sync&lt;/code&gt; or &lt;code&gt;crossbeam-channel&lt;/code&gt; crates.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;21-send-and-sync-traits&quot;&gt;21. Send and Sync Traits&lt;/h3&gt;
&lt;p&gt;These two marker traits are the foundation of Rust's fearless concurrency. You rarely implement them manually — the compiler derives them automatically.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Trait &lt;/th&gt;&lt;th&gt;Meaning&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Send&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Safe to &lt;strong&gt;transfer ownership&lt;/strong&gt; to another thread&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Sync&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Safe to &lt;strong&gt;share a reference&lt;/strong&gt; across threads (&lt;code&gt;&amp;amp;T&lt;/code&gt; is &lt;code&gt;Send&lt;/code&gt;)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use std::thread;
use std::rc::Rc;
use std::sync::Arc;

fn main() {
    // Rc&amp;lt;T&amp;gt; is NOT Send — can't move across threads
    // let rc = Rc::new(42);
    // thread::spawn(move || println!(&quot;{}&quot;, rc)); ← compile error!

    // Arc&amp;lt;T&amp;gt; IS Send — atomic reference counting
    let arc = Arc::new(42);
    let arc_clone = Arc::clone(&amp;amp;arc);

    thread::spawn(move || {
        println!(&quot;thread sees: {}&quot;, arc_clone);
    }).join().unwrap();

    println!(&quot;main sees: {}&quot;, arc);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Types and their Send/Sync status:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Type &lt;/th&gt;&lt;th&gt;Send &lt;/th&gt;&lt;th&gt;Sync&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;i32&lt;/code&gt;, &lt;code&gt;f64&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt; &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Vec&amp;lt;T&amp;gt;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Rc&amp;lt;T&amp;gt;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Arc&amp;lt;T&amp;gt;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;RefCell&amp;lt;T&amp;gt;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Mutex&amp;lt;T&amp;gt;&lt;/code&gt; &lt;/td&gt;&lt;td&gt;✅ &lt;/td&gt;&lt;td&gt;✅&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;*mut T&lt;/code&gt; (raw pointer) &lt;/td&gt;&lt;td&gt;❌ &lt;/td&gt;&lt;td&gt;❌&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;&lt;p&gt; If you try to share a non-&lt;code&gt;Sync&lt;/code&gt; type across threads, the compiler refuses to compile. No runtime surprises.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;22-mutex-and-rwlock&quot;&gt;22. Mutex and RwLock&lt;/h3&gt;
&lt;p&gt;When multiple threads need to &lt;strong&gt;mutate shared data&lt;/strong&gt;, use a &lt;code&gt;Mutex&lt;/code&gt; (mutual exclusion lock). Wrap it in &lt;code&gt;Arc&lt;/code&gt; to share ownership across threads.&lt;/p&gt;
&lt;h4 id=&quot;mutex&quot;&gt;Mutex&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));
    let mut handles = vec![];

    for _ in 0..10 {
        let counter = Arc::clone(&amp;amp;counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap(); // blocks until lock acquired
            *num += 1;
        }); // lock is released here when `num` drops
        handles.push(handle);
    }

    for h in handles { h.join().unwrap(); }

    println!(&quot;result: {}&quot;, *counter.lock().unwrap()); // 10
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;rwlock-many-readers-one-writer&quot;&gt;RwLock — many readers, one writer&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use std::sync::{Arc, RwLock};
use std::thread;

fn main() {
    let data = Arc::new(RwLock::new(vec![1, 2, 3]));

    // Spawn 4 reader threads
    let readers: Vec&amp;lt;_&amp;gt; = (0..4).map(|i| {
        let data = Arc::clone(&amp;amp;data);
        thread::spawn(move || {
            let r = data.read().unwrap(); // multiple readers allowed
            println!(&quot;reader {i}: {:?}&quot;, *r);
        })
    }).collect();

    // One writer thread
    let data_w = Arc::clone(&amp;amp;data);
    let writer = thread::spawn(move || {
        let mut w = data_w.write().unwrap(); // exclusive access
        w.push(4);
        println!(&quot;writer pushed 4&quot;);
    });

    for r in readers { r.join().unwrap(); }
    writer.join().unwrap();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Mutex vs RwLock:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;&lt;code&gt;Mutex&amp;lt;T&amp;gt;&lt;/code&gt; &lt;/th&gt;&lt;th&gt;&lt;code&gt;RwLock&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Readers &lt;/td&gt;&lt;td&gt;One at a time &lt;/td&gt;&lt;td&gt;Many simultaneously&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Writers &lt;/td&gt;&lt;td&gt;One at a time &lt;/td&gt;&lt;td&gt;One at a time (exclusive)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Best for &lt;/td&gt;&lt;td&gt;Write-heavy workloads &lt;/td&gt;&lt;td&gt;Read-heavy workloads&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Overhead &lt;/td&gt;&lt;td&gt;Lower &lt;/td&gt;&lt;td&gt;Higher&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 id=&quot;avoiding-deadlocks&quot;&gt;Avoiding deadlocks&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use std::sync::{Arc, Mutex};

fn main() {
    let a = Arc::new(Mutex::new(1));
    let b = Arc::new(Mutex::new(2));

    // ✅ Always acquire locks in the same order
    {
        let _lock_a = a.lock().unwrap();
        let _lock_b = b.lock().unwrap();
        println!(&quot;safe&quot;);
    }

    // ❌ Acquiring in different orders across threads = deadlock risk
    // thread 1: lock a → lock b
    // thread 2: lock b → lock a  ← deadlock!
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;23-atomics&quot;&gt;23. Atomics&lt;/h3&gt;
&lt;p&gt;For simple counters and flags shared between threads, &lt;code&gt;std::sync::atomic&lt;/code&gt; types are faster than a &lt;code&gt;Mutex&lt;/code&gt; — they use hardware atomic instructions with no locking.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;

fn main() {
    let counter = Arc::new(AtomicUsize::new(0));
    let mut handles = vec![];

    for _ in 0..8 {
        let counter = Arc::clone(&amp;amp;counter);
        handles.push(thread::spawn(move || {
            for _ in 0..1000 {
                counter.fetch_add(1, Ordering::Relaxed);
            }
        }));
    }

    for h in handles { h.join().unwrap(); }
    println!(&quot;total: {}&quot;, counter.load(Ordering::SeqCst)); // 8000
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;memory-ordering-cheat-sheet&quot;&gt;Memory ordering cheat sheet&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Ordering &lt;/th&gt;&lt;th&gt;Use case&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Relaxed&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Counters — no ordering guarantees needed&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Acquire&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Load that synchronizes with a &lt;code&gt;Release&lt;/code&gt; store&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;Release&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Store that publishes data to other threads&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;AcqRel&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Read-modify-write (e.g. &lt;code&gt;fetch_add&lt;/code&gt; with sync)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;SeqCst&lt;/code&gt; &lt;/td&gt;&lt;td&gt;Strongest — total order across all threads&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;

fn main() {
    let ready = Arc::new(AtomicBool::new(false));
    let ready_clone = Arc::clone(&amp;amp;ready);

    let worker = thread::spawn(move || {
        thread::sleep(std::time::Duration::from_millis(100));
        ready_clone.store(true, Ordering::Release);
        println!(&quot;worker: done&quot;);
    });

    // Spin-wait (not ideal in prod — use a Condvar or channel instead)
    while !ready.load(Ordering::Acquire) {
        thread::yield_now();
    }
    println!(&quot;main: worker finished&quot;);
    worker.join().unwrap();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt; Available atomic types: &lt;code&gt;AtomicBool&lt;/code&gt;, &lt;code&gt;AtomicI8/16/32/64&lt;/code&gt;, &lt;code&gt;AtomicU8/16/32/64&lt;/code&gt;, &lt;code&gt;AtomicUsize&lt;/code&gt;, &lt;code&gt;AtomicIsize&lt;/code&gt;, &lt;code&gt;AtomicPtr&amp;lt;T&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;24-building-a-cli-tool-with-clap&quot;&gt;24. Building a CLI Tool with clap&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;clap&lt;/code&gt; is the standard Rust library for building command-line interfaces. It gives you argument parsing, help text, and subcommands with minimal boilerplate.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-toml&quot;&gt;# Cargo.toml
[dependencies]
clap = { version = &quot;4&quot;, features = [&quot;derive&quot;] }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;basic-cli&quot;&gt;Basic CLI&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use clap::Parser;

/// A simple file word counter
#[derive(Parser, Debug)]
#[command(name = &quot;wordcount&quot;)]
#[command(about = &quot;Count words in a string or file&quot;, long_about = None)]
struct Cli {
    /// The text to count words in
    #[arg(short, long)]
    text: Option&amp;lt;String&amp;gt;,

    /// Show character count too
    #[arg(short, long, default_value_t = false)]
    chars: bool,

    /// Verbosity level
    #[arg(short, long, action = clap::ArgAction::Count)]
    verbose: u8,
}

fn main() {
    let cli = Cli::parse();

    let input = cli.text.unwrap_or_else(|| String::from(&quot;hello world&quot;));
    let words = input.split_whitespace().count();

    if cli.verbose &amp;gt; 0 {
        println!(&quot;Input: {:?}&quot;, input);
    }

    println!(&quot;Words: {words}&quot;);

    if cli.chars {
        println!(&quot;Chars: {}&quot;, input.chars().count());
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Running it:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ cargo run -- --text &quot;the quick brown fox&quot; --chars -v
Input: &quot;the quick brown fox&quot;
Words: 4
Chars: 19

$ cargo run -- --help
Count words in a string or file

Usage: wordcount [OPTIONS]

Options:
  -t, --text &amp;lt;TEXT&amp;gt;   The text to count words in
  -c, --chars         Show character count too
  -v, --verbose...    Verbosity level
  -h, --help          Print help
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;subcommands&quot;&gt;Subcommands&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;use clap::{Parser, Subcommand};

#[derive(Parser)]
#[command(name = &quot;mytool&quot;, about = &quot;A multi-command CLI&quot;)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand)]
enum Commands {
    /// Add two numbers
    Add { a: f64, b: f64 },
    /// Multiply two numbers
    Mul { a: f64, b: f64 },
    /// Greet someone
    Greet {
        name: String,
        #[arg(short, long, default_value = &quot;Hello&quot;)]
        greeting: String,
    },
}

fn main() {
    let cli = Cli::parse();

    match cli.command {
        Commands::Add { a, b } =&amp;gt; println!(&quot;{} + {} = {}&quot;, a, b, a + b),
        Commands::Mul { a, b } =&amp;gt; println!(&quot;{} × {} = {}&quot;, a, b, a * b),
        Commands::Greet { name, greeting } =&amp;gt; println!(&quot;{greeting}, {name}!&quot;),
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Running subcommands:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;$ cargo run -- add 3.5 2.5
3.5 + 2.5 = 6

$ cargo run -- greet Alice --greeting &quot;Hey&quot;
Hey, Alice!

$ cargo run -- mul 6 7
6 × 7 = 42
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;&lt;p&gt; For a full production CLI, combine &lt;code&gt;clap&lt;/code&gt; with &lt;code&gt;anyhow&lt;/code&gt; for error handling, &lt;code&gt;indicatif&lt;/code&gt; for progress bars, and &lt;code&gt;colored&lt;/code&gt; or &lt;code&gt;owo-colors&lt;/code&gt; for terminal color output.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;wrapping-up-1&quot;&gt;Wrapping Up&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Concept &lt;/th&gt;&lt;th&gt;What it gives you&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Threads&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;OS-level parallelism, compile-time data race prevention&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Channels&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Safe message passing between threads (mpsc)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Send &amp;amp; Sync&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Compiler-enforced thread-safety contracts&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Mutex &amp;amp; RwLock&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Shared mutable state without data races&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Atomics&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Lock-free counters and flags&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;clap&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;Production-grade CLI argument parsing&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h4 id=&quot;the-full-series-so-far&quot;&gt;The full series so far&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Part &lt;/th&gt;&lt;th&gt;Topics&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Part 1&lt;/a&gt; &lt;/td&gt;&lt;td&gt;Ownership, Borrowing, Lifetimes, Traits, Result/Option, Pattern Matching&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Part 2&lt;/a&gt; &lt;/td&gt;&lt;td&gt;Closures, Iterators, Generics, Enums, Smart Pointers, Async/Await&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;#&quot;&gt;Part 3&lt;/a&gt; &lt;/td&gt;&lt;td&gt;Macros, Modules, Cargo, Testing, Unsafe, FFI&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Part 4&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;&lt;strong&gt;Threads, Channels, Send/Sync, Mutex, Atomics, clap&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h4 id=&quot;what-s-in-part-5-&quot;&gt;What's in Part 5?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Trait objects and dynamic dispatch (&lt;code&gt;dyn Trait&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Deref&lt;/code&gt; and &lt;code&gt;Drop&lt;/code&gt; traits&lt;/li&gt;
&lt;li&gt;Custom iterators&lt;/li&gt;
&lt;li&gt;Building a real REST API with &lt;code&gt;axum&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Found this helpful? Drop a ❤️ and follow for Part 5!&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/19037/rust-concurrency-threads-channels-mutex-sync-part-4</guid>
<pubDate>Wed, 27 May 2026 07:36:28 +0000</pubDate>
</item>
<item>
<title>Sorting Wars: Benchmarking Selection, Insertion, Gnome e Bubble Sort em 5 Linguagens</title>
<link>https://coderlegion.com/18692/sorting-wars-benchmarking-selection-insertion-gnome-e-bubble-sort-em-5-linguagens</link>
<description>&lt;p&gt;Algoritmos quadráticos são lentos — isso todo mundo sabe. Mas &lt;em&gt;o quão&lt;/em&gt; lentos? E a linguagem escolhida importa mais do que o algoritmo em si? Foi exatamente isso que nossa turma de Algoritmos e Estruturas de Dados I do &lt;strong&gt;CEFET-MG&lt;/strong&gt; decidiu testar empiricamente.&lt;/p&gt;
&lt;p&gt;Implementamos &lt;strong&gt;Selection Sort, Insertion Sort, Gnome Sort e Bubble Sort&lt;/strong&gt; em &lt;strong&gt;C, C++, Rust, JavaScript e Python&lt;/strong&gt;, rodamos benchmarks com até 1 milhão de elementos e coletamos os dados. Esse post resume o que encontramos — incluindo alguns resultados que nos surpreenderam bastante.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;por-que-estudar-algoritmos-o-n-em-2025-&quot;&gt;Por que estudar algoritmos O(n²) em 2025?&lt;/h3&gt;
&lt;p&gt;Antes de mergulhar nos números, vale responder a pergunta óbvia. Três razões principais:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Didática&lt;/strong&gt; — a lógica é direta, ideal para aprender análise de complexidade e invariantes de laço.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Entradas pequenas&lt;/strong&gt; — para &lt;code&gt;N ≤ ~50&lt;/code&gt;, o overhead reduzido pode torná-los mais rápidos que algoritmos sofisticados.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adaptatividade&lt;/strong&gt; — Insertion Sort e Bubble Sort com parada antecipada chegam a &lt;strong&gt;O(n)&lt;/strong&gt; em entradas quase ordenadas. É por isso que o &lt;strong&gt;TimSort&lt;/strong&gt; (usado em Python e Java) usa Insertion Sort internamente para subconjuntos pequenos.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;os-algoritmos&quot;&gt;Os Algoritmos&lt;/h3&gt;
&lt;h4 id=&quot;selection-sort-o-inflex-vel&quot;&gt;Selection Sort — O inflexível&lt;/h4&gt;
&lt;p&gt;Seleciona o mínimo do subarray não ordenado e o move para a posição correta com uma única troca.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;PARA i DE 0 ATE N - 2 FACA
  MIN_IDX &amp;lt;- i
  PARA j DE i + 1 ATE N - 1 FACA
    SE VETOR[j] &amp;lt; VETOR[MIN_IDX] ENTAO
      MIN_IDX &amp;lt;- j
  SE MIN_IDX != i ENTAO
    TROCA VETOR[i] COM VETOR[MIN_IDX]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Característica marcante:&lt;/strong&gt; sempre realiza exatamente &lt;code&gt;n(n-1)/2&lt;/code&gt; comparações, independente da entrada. Não existe melhor caso em comparações — é &lt;strong&gt;Θ(n²) em todos os cenários&lt;/strong&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Caso              &lt;/th&gt;&lt;th&gt;Comparações &lt;/th&gt;&lt;th&gt;Complexidade&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Melhor (ordenado) &lt;/td&gt;&lt;td&gt;n(n−1)/2    &lt;/td&gt;&lt;td&gt;O(n²)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Médio (aleatório) &lt;/td&gt;&lt;td&gt;n(n−1)/2    &lt;/td&gt;&lt;td&gt;O(n²)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Pior (invertido)  &lt;/td&gt;&lt;td&gt;n(n−1)/2    &lt;/td&gt;&lt;td&gt;O(n²)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;&lt;p&gt;✅ In-place · ❌ Não estável · ❌ Não adaptativo&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h4 id=&quot;insertion-sort-o-adaptativo&quot;&gt;Insertion Sort — O adaptativo&lt;/h4&gt;
&lt;p&gt;Mantém um subarray ordenado à esquerda e insere cada novo elemento na posição correta via &lt;strong&gt;shifting&lt;/strong&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;PARA i DE 1 ATE N - 1 FACA
  CHAVE &amp;lt;- VETOR[i]
  j &amp;lt;- i - 1
  ENQUANTO j &amp;gt;= 0 E VETOR[j] &amp;gt; CHAVE FACA
    VETOR[j + 1] &amp;lt;- VETOR[j]
    j &amp;lt;- j - 1
  VETOR[j + 1] &amp;lt;- CHAVE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Em entradas já ordenadas, o laço interno nunca executa: complexidade &lt;strong&gt;O(n)&lt;/strong&gt; no melhor caso. Isso ficou claro nos nossos dados — até 10⁶ elementos, o cenário crescente terminou em microssegundos.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Caso              &lt;/th&gt;&lt;th&gt;Comparações &lt;/th&gt;&lt;th&gt;Complexidade&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Melhor (ordenado) &lt;/td&gt;&lt;td&gt;n − 1       &lt;/td&gt;&lt;td&gt;O(n)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Médio (aleatório) &lt;/td&gt;&lt;td&gt;n(n−1)/4    &lt;/td&gt;&lt;td&gt;O(n²)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Pior (invertido)  &lt;/td&gt;&lt;td&gt;n(n−1)/2    &lt;/td&gt;&lt;td&gt;O(n²)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;&lt;p&gt;✅ In-place · ✅ Estável · ✅ Adaptativo&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h4 id=&quot;gnome-sort-o-exc-ntrico&quot;&gt;Gnome Sort — O excêntrico&lt;/h4&gt;
&lt;p&gt;Opera como um &quot;gnomo organizando vasos&quot;: avança comparando adjacentes, e ao encontrar desordem, troca e recua uma posição.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;INDICE &amp;lt;- 0
ENQUANTO INDICE &amp;lt; N FACA
  SE INDICE = 0 OU VETOR[INDICE] &amp;gt;= VETOR[INDICE - 1] ENTAO
    INDICE &amp;lt;- INDICE + 1
  SENAO
    TROCA VETOR[INDICE] COM VETOR[INDICE - 1]
    INDICE &amp;lt;- INDICE - 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Funcionalmente equivalente a um Insertion Sort por trocas — mas com movimento local em vez de shifting. Também adaptativo: O(n) no melhor caso.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Caso              &lt;/th&gt;&lt;th&gt;Comparações &lt;/th&gt;&lt;th&gt;Complexidade&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Melhor (ordenado) &lt;/td&gt;&lt;td&gt; n − 1      &lt;/td&gt;&lt;td&gt;O(n)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Médio (aleatório) &lt;/td&gt;&lt;td&gt;(A DEPENDER)&lt;/td&gt;&lt;td&gt;O(n²)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Pior (invertido)  &lt;/td&gt;&lt;td&gt;n(n−1)/2    &lt;/td&gt;&lt;td&gt;O(n²)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;&lt;p&gt;✅ In-place · ✅ Estável · ✅ Adaptativo&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h4 id=&quot;bubble-sort-o-cl-ssico-com-parada-antecipada-&quot;&gt;Bubble Sort — O clássico (com parada antecipada)&lt;/h4&gt;
&lt;p&gt;Realiza passagens comparando pares adjacentes. A variável &lt;code&gt;swapped&lt;/code&gt; permite encerramento antecipado quando o vetor já está ordenado.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;PARA i DE 0 ATE N - 1 FACA
  SWAPPED &amp;lt;- FALSO
  PARA j DE 0 ATE N - i - 2 FACA
    SE VETOR[j] &amp;gt; VETOR[j + 1] ENTAO
      TROCA VETOR[j] COM VETOR[j+1]
      SWAPPED &amp;lt;- VERDADEIRO
  SE SWAPPED = FALSO ENTAO
    RETORNE  // Encerramento antecipado
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Caso              &lt;/th&gt;&lt;th&gt;Comparações &lt;/th&gt;&lt;th&gt;Complexidade&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Melhor (ordenado) &lt;/td&gt;&lt;td&gt;n − 1       &lt;/td&gt;&lt;td&gt;O(n)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Médio (aleatório) &lt;/td&gt;&lt;td&gt;n(n−1)/4    &lt;/td&gt;&lt;td&gt;O(n²)&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Pior (invertido)  &lt;/td&gt;&lt;td&gt;n(n−1)/2    &lt;/td&gt;&lt;td&gt;O(n²)&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;&lt;p&gt;✅ In-place · ✅ Estável · ✅ Adaptativo (com parada antecipada)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;metodologia&quot;&gt;Metodologia&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tamanhos de entrada:&lt;/strong&gt; N ∈ { 10², 10³, 10⁴, 10⁵, 10⁶ }&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cenários:&lt;/strong&gt; crescente (melhor caso), aleatório (caso médio), decrescente (pior caso)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repetições:&lt;/strong&gt; 5 execuções; média das 3 centrais (descartando menor e maior)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Otimizações testadas:&lt;/strong&gt; &lt;code&gt;-O0&lt;/code&gt; e &lt;code&gt;-O3&lt;/code&gt; para C/C++/Rust&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Medição:&lt;/strong&gt; apenas tempo de ordenação (I/O descartado)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cada linguagem usou sua estrutura de dados nativa:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Linguagem  &lt;/th&gt;&lt;th&gt;Estrutura              &lt;/th&gt;&lt;th&gt;Tipo&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;C          &lt;/td&gt;&lt;td&gt;&lt;code&gt;double[]&lt;/code&gt; com &lt;code&gt;malloc&lt;/code&gt;&lt;/td&gt;&lt;td&gt;&lt;code&gt;double&lt;/code&gt; 64-bit&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;C++        &lt;/td&gt;&lt;td&gt;&lt;code&gt;std::vector&amp;lt;double&amp;gt;&lt;/code&gt;  &lt;/td&gt;&lt;td&gt;&lt;code&gt;double&lt;/code&gt; 64-bit&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Rust       &lt;/td&gt;&lt;td&gt;&lt;code&gt;Vec&amp;lt;f64&amp;gt;&lt;/code&gt;             &lt;/td&gt;&lt;td&gt;&lt;code&gt;f64&lt;/code&gt; 64-bit&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;JavaScript &lt;/td&gt;&lt;td&gt;&lt;code&gt;Array&lt;/code&gt; (V8 float64)   &lt;/td&gt;&lt;td&gt;&lt;code&gt;Number&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Python     &lt;/td&gt;&lt;td&gt;&lt;code&gt;list&lt;/code&gt; (refs a PyObject)&lt;/td&gt;&lt;td&gt;&lt;code&gt;float&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;resultados-selection-sort&quot;&gt;Resultados — Selection Sort&lt;/h3&gt;
&lt;p&gt;O Selection Sort foi o mais previsível: tempos quase idênticos nos três cenários, confirmando a não-adaptatividade.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;N = 10⁵ — tempo em segundos (-O3 / otimização máxima)&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Linguagem        &lt;/th&gt;&lt;th&gt;Crescente (s) &lt;/th&gt;&lt;th&gt;Aleatório (s) &lt;/th&gt;&lt;th&gt;Decrescente (s)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;C (-O3)          &lt;/td&gt;&lt;td&gt;2.0500        &lt;/td&gt;&lt;td&gt;3.5188        &lt;/td&gt;&lt;td&gt;1.9618&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;C++ (-O3)        &lt;/td&gt;&lt;td&gt;2.1247        &lt;/td&gt;&lt;td&gt;3.5188        &lt;/td&gt;&lt;td&gt;1.9089&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Rust (-O3)       &lt;/td&gt;&lt;td&gt;2.3265        &lt;/td&gt;&lt;td&gt;4.2560        &lt;/td&gt;&lt;td&gt;2.1320&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;JavaScript (V8)  &lt;/td&gt;&lt;td&gt;3.5835        &lt;/td&gt;&lt;td&gt;6.6564        &lt;/td&gt;&lt;td&gt;3.5851&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Python           &lt;/td&gt;&lt;td&gt;93.164        &lt;/td&gt;&lt;td&gt;164.890       &lt;/td&gt;&lt;td&gt;98.378&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;&lt;p&gt;Python para N = 10⁶ foi descartado — tempo estimado superior a &lt;strong&gt;10 horas&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Hierarquia:&lt;/strong&gt; &lt;code&gt;C -O3 ≈ C++ -O3 &amp;lt; Rust &amp;lt; JavaScript ≪ Python&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;resultados-insertion-sort&quot;&gt;Resultados — Insertion Sort&lt;/h3&gt;
&lt;p&gt;Aqui a adaptatividade brilhou. No cenário crescente com N = 10⁶, todas as linguagens terminaram em &lt;strong&gt;menos de 12 ms&lt;/strong&gt;. Já no cenário decrescente, o pior caso quadrático foi brutal:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;N = 10⁵ — tempo em segundos (-O3 / otimização máxima)&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Linguagem        &lt;/th&gt;&lt;th&gt;Crescente (s) &lt;/th&gt;&lt;th&gt;Aleatório (s) &lt;/th&gt;&lt;th&gt;Decrescente (s)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;C (-O3)          &lt;/td&gt;&lt;td&gt;0.000055      &lt;/td&gt;&lt;td&gt;0.615683      &lt;/td&gt;&lt;td&gt;1.188966&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;C++ (-O3)        &lt;/td&gt;&lt;td&gt;0.0000634     &lt;/td&gt;&lt;td&gt;0.587483      &lt;/td&gt;&lt;td&gt;1.183000&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Rust (-O3)       &lt;/td&gt;&lt;td&gt;0.000079      &lt;/td&gt;&lt;td&gt;0.897895      &lt;/td&gt;&lt;td&gt;1.774292&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;JavaScript (V8)  &lt;/td&gt;&lt;td&gt;0.000809      &lt;/td&gt;&lt;td&gt;1.525250      &lt;/td&gt;&lt;td&gt;3.033301&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Python           &lt;/td&gt;&lt;td&gt;0.005321      &lt;/td&gt;&lt;td&gt;136.295       &lt;/td&gt;&lt;td&gt;226.560&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;&lt;p&gt;Para N = 10⁶ no pior caso sem otimização, C++ chegou a &lt;strong&gt;3670 s&lt;/strong&gt; (~1 hora). Com &lt;code&gt;-O3&lt;/code&gt;, caiu para &lt;strong&gt;119 s&lt;/strong&gt; — speedup de &lt;strong&gt;~30×&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h3 id=&quot;resultados-gnome-sort&quot;&gt;Resultados — Gnome Sort&lt;/h3&gt;
&lt;p&gt;O Gnome Sort trouxe a maior surpresa da série: &lt;strong&gt;Rust superou C e C++ no caso médio&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;N = 10⁵ — tempo em segundos (-O3 / otimização máxima)&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Linguagem        &lt;/th&gt;&lt;th&gt;Crescente (s) &lt;/th&gt;&lt;th&gt;Aleatório (s) &lt;/th&gt;&lt;th&gt;Decrescente (s)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;C (-O3)          &lt;/td&gt;&lt;td&gt;0.000179      &lt;/td&gt;&lt;td&gt;15.19068      &lt;/td&gt;&lt;td&gt;28.90668&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;C++ (-O3)        &lt;/td&gt;&lt;td&gt;0.000159      &lt;/td&gt;&lt;td&gt;13.96807      &lt;/td&gt;&lt;td&gt;28.25787&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Rust (-O3)       &lt;/td&gt;&lt;td&gt;0.000049      &lt;/td&gt;&lt;td&gt;&lt;strong&gt;3.94810&lt;/strong&gt;   &lt;/td&gt;&lt;td&gt;8.27646&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;JavaScript (V8)  &lt;/td&gt;&lt;td&gt;0.001584      &lt;/td&gt;&lt;td&gt;13.90908      &lt;/td&gt;&lt;td&gt;27.45163&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Python           &lt;/td&gt;&lt;td&gt;0.019030      &lt;/td&gt;&lt;td&gt;21.28324      &lt;/td&gt;&lt;td&gt;257.00624&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Por quê? O padrão de acesso do Gnome Sort — muitos acessos locais e trocas adjacentes — se beneficia especialmente das otimizações do compilador LLVM (backend do Rust), que gera código de swap em registrador sem acesso intermediário à memória.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;resultados-bubble-sort&quot;&gt;Resultados — Bubble Sort&lt;/h3&gt;
&lt;p&gt;O Bubble Sort entregou o resultado mais inesperado de todo o projeto:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;N = 10⁵ — tempo em segundos (-O3 / otimização máxima)&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Linguagem        &lt;/th&gt;&lt;th&gt;Crescente (s) &lt;/th&gt;&lt;th&gt;Aleatório (s) &lt;/th&gt;&lt;th&gt;Decrescente (s)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;C (-O3)          &lt;/td&gt;&lt;td&gt;0.000042      &lt;/td&gt;&lt;td&gt;20.489569     &lt;/td&gt;&lt;td&gt;23.568626&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;C++ (-O3)        &lt;/td&gt;&lt;td&gt;0.000045      &lt;/td&gt;&lt;td&gt;21.165759     &lt;/td&gt;&lt;td&gt;23.919268&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Rust (-O3)       &lt;/td&gt;&lt;td&gt;0.000051      &lt;/td&gt;&lt;td&gt;13.513529     &lt;/td&gt;&lt;td&gt;9.231813&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;JavaScript (V8)  &lt;/td&gt;&lt;td&gt;0.001753      &lt;/td&gt;&lt;td&gt;15.012168     &lt;/td&gt;&lt;td&gt;&lt;strong&gt;6.905683&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Python           &lt;/td&gt;&lt;td&gt;0.003504      &lt;/td&gt;&lt;td&gt;312.509064    &lt;/td&gt;&lt;td&gt;403.412438&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;JavaScript bateu C e C++ no pior caso.&lt;/strong&gt; O motor V8 identificou o padrão repetitivo de trocas adjacentes e gerou código de máquina altamente especializado via JIT. É um caso raro onde a compilação dinâmica supera a compilação estática.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;principais-aprendizados&quot;&gt;Principais Aprendizados&lt;/h3&gt;
&lt;h4 id=&quot;1-otimiza-o-de-compilador-importa-muito-mais-do-que-parece&quot;&gt;1. Otimização de compilador importa muito mais do que parece&lt;/h4&gt;
&lt;p&gt;O impacto de &lt;code&gt;-O3&lt;/code&gt; vs &lt;code&gt;-O0&lt;/code&gt; para Rust no Bubble Sort chegou a &lt;strong&gt;23×&lt;/strong&gt; para N = 10⁴. Em modo debug, o Rust mantém verificações de &lt;em&gt;bounds checking&lt;/em&gt; em todo acesso ao array — correto para desenvolvimento, devastador para performance.&lt;/p&gt;
&lt;h4 id=&quot;2-adaptatividade-real-e-mensur-vel&quot;&gt;2. Adaptatividade é real e mensurável&lt;/h4&gt;
&lt;p&gt;Insertion Sort com N = 10⁶ no cenário crescente: &lt;strong&gt;&amp;lt; 1 ms&lt;/strong&gt;. No cenário decrescente: &lt;strong&gt;até 3670 s&lt;/strong&gt; (sem otimização). Isso é uma diferença de &lt;strong&gt;mais de 10 ordens de magnitude&lt;/strong&gt; no número de operações.&lt;/p&gt;
&lt;h4 id=&quot;3-python-tem-overhead-estrutural-n-o-s-de-interpretador&quot;&gt;3. Python tem overhead estrutural, não só de interpretador&lt;/h4&gt;
&lt;p&gt;A &lt;code&gt;list&lt;/code&gt; do Python armazena referências a objetos &lt;code&gt;PyObject&lt;/code&gt; — cada acesso e comparação envolve uma indireção de ponteiro. Isso multiplica o custo por operação mesmo para tipos simples como &lt;code&gt;int&lt;/code&gt; e &lt;code&gt;float&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;4-jit-pode-superar-c-digo-est-tico-em-padr-es-repetitivos&quot;&gt;4. JIT pode superar código estático em padrões repetitivos&lt;/h4&gt;
&lt;p&gt;O V8 observa o comportamento em runtime e especializa o código gerado. Para algoritmos com padrões de acesso altamente previsíveis (como Bubble Sort), isso pode ser mais eficiente que otimizações estáticas conservadoras de um compilador AOT.&lt;/p&gt;
&lt;h4 id=&quot;5-a-hierarquia-n-o-fixa&quot;&gt;5. A hierarquia não é fixa&lt;/h4&gt;
&lt;p&gt;A ordem geral foi &lt;code&gt;C ≈ C++ &amp;lt; Rust &amp;lt; JS &amp;lt; Python&lt;/code&gt;, mas exceções importantes existem — Rust venceu todos no Gnome Sort, JS venceu todos no Bubble Sort em pior caso. &lt;strong&gt;O melhor resultado depende do algoritmo, tamanho de entrada e padrão de acesso.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclus-o&quot;&gt;Conclusão&lt;/h3&gt;
&lt;p&gt;Algoritmos simples ensinam mais do que complexidade assintótica. Ensinam sobre a relação entre padrões de acesso à memória e otimizações de compilador, sobre como compilação JIT funciona na prática, e sobre por que constantes importam tanto quanto expoentes para tamanhos reais de entrada.&lt;/p&gt;
&lt;p&gt;Se você quiser explorar o código completo, estrutura de Makefiles, geradores de dataset e scripts de benchmark, o repositório está público no GitHub.&lt;/p&gt;
&lt;p&gt;{% embed &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Bernardo-Lebron/simple_sort_algorithms&quot;&gt;https://github.com/Bernardo-Lebron/simple_sort_algorithms&lt;/a&gt; %}&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Trabalho prático da disciplina de Algoritmos e Estruturas de Dados I — CEFET-MG Campus V, Divinópolis. Orientação: Prof. Michel Pires.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18692/sorting-wars-benchmarking-selection-insertion-gnome-e-bubble-sort-em-5-linguagens</guid>
<pubDate>Fri, 22 May 2026 14:58:16 +0000</pubDate>
</item>
<item>
<title>I'm Building a Multi-Target Compiler Backend from Scratch — No LLVM, No Crutches</title>
<link>https://coderlegion.com/18588/im-building-a-multi-target-compiler-backend-from-scratch-no-llvm-no-crutches</link>
<description>&lt;p&gt;Hi there, i'm Gideon. Roughly 1.5 years of writing C++ from the ground up — ray tracers, video codecs, and now a compiler. No frameworks. No LLVM. Just me, the hardware manuals, and a lot of wrong turns.&lt;/p&gt;
&lt;p&gt;This post starts a series where I document the build in real time. I'm currently in the parser stage. By the end, I want a compiler that emits x86-64 and SPIR-V from a C++-like language, with SIMD vectorization and security-hardened codegen baked in.&lt;/p&gt;
&lt;p&gt;What I'm Actually Building&lt;/p&gt;
&lt;p&gt;Not a programming language. A compiler backend toolkit — the part that turns intermediate representation into fast machine code across multiple targets.&lt;/p&gt;
&lt;p&gt;The pipeline:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Source → Parser → AST → SSMOL (HIR) → MREL (LIR) → x86-64 / SPIR-V / ARM64 / RISC-V / WASM
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MREL is my target-agnostic low-level IR. It knows about virtual registers, stack slots, and machine operations — but not physical register names. The backend handles that per-target.&lt;/p&gt;
&lt;p&gt;Why Not Just Use LLVM?&lt;/p&gt;
&lt;p&gt;LLVM is 4 million lines of code. It solves everyone's problem and no one's perfectly. I need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Fine-grained control over SIMD width selection per target&lt;/li&gt;
&lt;li&gt;Constant-time crypto primitive emission with secret register annotations&lt;/li&gt;
&lt;li&gt;Security obfuscation passes (control flow flattening, opaque predicates)&lt;/li&gt;
&lt;li&gt;A codebase I fully understand and can license&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Building from scratch is slower. But I own every decision.&lt;/p&gt;
&lt;p&gt;Where I Am Right Now&lt;/p&gt;
&lt;p&gt;Parser stage. Hand-written recursive descent. C++-like syntax with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Functions, structs, basic types&lt;/li&gt;
&lt;li&gt;Ownership semantics (borrowed from my Rust phase, simplified)&lt;/li&gt;
&lt;li&gt;Explicit SIMD types (&lt;code&gt;v128&lt;/code&gt;, &lt;code&gt;v256&lt;/code&gt;, &lt;code&gt;v512&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The parser emits an AST that gets lowered to SSMOL — my high-level IR that knows about types, ownership, and semantics.&lt;/p&gt;
&lt;p&gt;What's Next&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;SSMOL → MREL lowering (types to sizes, structs to offsets, control flow to basic blocks)&lt;/li&gt;
&lt;li&gt;MREL → x86-64 backend (register allocation, instruction selection, ELF emission)&lt;/li&gt;
&lt;li&gt;One working program: compile, link, run &lt;code&gt;main()&lt;/code&gt; that returns 42&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Then SPIR-V compute kernels. Then the rest.&lt;/p&gt;
&lt;p&gt;What I'll Write About&lt;/p&gt;
&lt;p&gt;Each stage, when I hit it. The problems that took me three days to solve. The specs I wrote to keep myself honest. The wrong assumptions that cost me a week.&lt;/p&gt;
&lt;p&gt;Not polished tutorials. Build logs from someone actually building.&lt;/p&gt;
&lt;p&gt;Follow This Series If&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You work in systems, compilers, or graphics&lt;/li&gt;
&lt;li&gt;You're curious what &quot;building from scratch&quot; actually looks like&lt;/li&gt;
&lt;li&gt;You want to see if I crash or ship&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My Specs (For the Curious)&lt;/p&gt;
&lt;p&gt;I write technical specifications to keep the design coherent across months of work. The MREL backend spec covers x86-64, ARM64, RISC-V, SPIR-V, and WebAssembly with calling conventions, opcode tables, and security passes. Link in bio.&lt;/p&gt;
&lt;p&gt;Substack: (&lt;a rel=&quot;nofollow&quot; href=&quot;https://ayndlr.substack.com&quot;&gt;https://ayndlr.substack.com&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Closing&lt;/p&gt;
&lt;p&gt;This is post 1 of however many it takes. Next post: parsing expressions with operator precedence and why I gave up on Pratt parsing.&lt;/p&gt;
&lt;p&gt;Follow for the crash or the ship. Either way, it's real.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18588/im-building-a-multi-target-compiler-backend-from-scratch-no-llvm-no-crutches</guid>
<pubDate>Thu, 21 May 2026 20:34:21 +0000</pubDate>
</item>
<item>
<title>Data Sanitization is a Design Flaw: How Penta-V Prevents Data Pollution at the Hardware-Software</title>
<link>https://coderlegion.com/18183/data-sanitization-is-design-flaw-how-penta-prevents-data-pollution-the-hardware-software</link>
<description>&lt;p&gt;Go to GitHub, search for &quot;data cleaning library,&quot; and you will find thousands of repositories. From &lt;code&gt;Pandas&lt;/code&gt; to &lt;code&gt;Pydantic&lt;/code&gt;, the entire software engineering industry has been conditioned to treat data sanitization as a &lt;strong&gt;reactive, post-facto chore&lt;/strong&gt;. You ingest data, you realize it’s full of missing values, &lt;code&gt;NaN&lt;/code&gt;s, or volatile floating-point noise (&lt;code&gt;Inf&lt;/code&gt;), and then you write heavy, resource-expensive loops to &quot;clean&quot; it.&lt;/p&gt;
&lt;p&gt;Here is the cold, hard truth that legacy architectures refuse to admit: &lt;strong&gt;If your system is spending CPU cycles cleaning data inside its execution core, your architecture has already failed.&lt;/strong&gt; In high-frequency pipelines and autonomous AI environments, traditional cleaning introduces massive latency spikes, destroys CPU cache locality, and leads to &lt;strong&gt;Logic Drift&lt;/strong&gt;—where the state of your system silently decays.&lt;/p&gt;
&lt;p&gt;When we engineered the &lt;strong&gt;Penta-V Kernel (v0.4.0)&lt;/strong&gt;, we didn't build another data-cleaning library. We built &lt;code&gt;src/processing/cleaner.rs&lt;/code&gt; (&lt;code&gt;PentaCleaner&lt;/code&gt;) to prove a different architectural paradigm: &lt;strong&gt;We don't clean data; we structurally prevent data pollution from existing.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-legacy-trap-why-runtime-cleaning-kills-high-frequency-systems&quot;&gt;The Legacy Trap: Why Runtime Cleaning Kills High-Frequency Systems&lt;/h3&gt;
&lt;p&gt;In standard hybrid pipelines (Python bridging into Rust via PyO3), data passed from autonomous AI agents is inherently probabilistic and volatile. &lt;/p&gt;
&lt;p&gt;When a naive system encounters corrupted data at runtime, it usually triggers one of two reactive paths:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The Allocation Nightmare:&lt;/strong&gt; It instantiates dynamic buffers on the heap to reconstruct tables (e.g., &lt;code&gt;df.fillna()&lt;/code&gt;), causing memory fragmentation and triggering garbage collection jitter.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Unsafe Race:&lt;/strong&gt; It forces raw parallel mutation via &lt;code&gt;unsafe&lt;/code&gt; pointers, risking data races and undefined behavior (UB) under sudden systemic stress.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Penta-V eliminates this by transforming data purification from an operational step into an &lt;strong&gt;Adaptive Geometric Constraint&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-zero-allocation-parallel-extraction-eliminating-memory-debt-&quot;&gt;1. Zero-Allocation Parallel Extraction (Eliminating Memory Debt)&lt;/h3&gt;
&lt;p&gt;Instead of allocating new memory frames to clean data, the &lt;code&gt;PentaCleaner&lt;/code&gt; leverages a highly optimized, lock-free parallel extraction pipeline driven by safe Rust abstractions (&lt;code&gt;Polars&lt;/code&gt; and &lt;code&gt;Rayon&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Look at how the core execution loop handles a contaminated DataFrame:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;// src/processing/cleaner.rs

pub fn geometric_sanitize(df: &amp;amp;mut DataFrame, state: &amp;amp;ProcessingState) -&amp;gt; PolarsResult&amp;lt;()&amp;gt; {
    let pressure = state.data_pressure;

    // SAFE &amp;amp; LOCK-FREE PARALLELISM:
    // We leverage low-level pointer duplication (shallow clones) to parallelize 
    // column purification across all available CPU cores without single-line heap allocations.
    let updated_columns: Vec&amp;lt;Series&amp;gt; = df
        .get_columns()
        .par_iter()
        .map(|series| {
            let mut clned = series.clone(); // Cheap shallow clone (O(1) pointer copy)
            Self::purify_column(&amp;amp;mut clned, pressure);
            clned
        })
        .collect();

    // Reconstruct the DataFrame in a single CPU cycle by consuming the safe vector
    *df = DataFrame::new(updated_columns)?;

    Ok(())
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;why-this-is-prevention-not-cleaning-&quot;&gt;Why this is prevention, not cleaning:&lt;/h4&gt;
&lt;p&gt;By enforcing compile-time data aliasing rules, the Rust compiler guarantees that no two threads can cause a data race. The allocation cost is &lt;strong&gt;exactly zero&lt;/strong&gt; because &lt;code&gt;series.clone()&lt;/code&gt; in Polars is merely a shallow reference duplication. The system doesn't wait for data to become a problem; it enforces a strict parallel layout where invalid structures cannot propagate.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-pressure-aware-structural-adaptation-the-anti-fragile-gate-&quot;&gt;2. Pressure-Aware Structural Adaptation (The Anti-Fragile Gate)&lt;/h3&gt;
&lt;p&gt;A static data cleaner is blind. It executes the exact same parsing script whether your server is idling at 5% or burning at 99% capacity. Under extreme load, this blindness causes memory queues to back up, leading to catastrophic system throttles.&lt;/p&gt;
&lt;p&gt;Penta-V introduces &lt;strong&gt;Pressure-Aware Sanitization&lt;/strong&gt;. The kernel dynamically mutates its purification strategy based on the thermodynamic state of the infrastructure (&lt;code&gt;data_pressure&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;fn purify_column(series: &amp;amp;mut Series, pressure: f64) {
    // Dynamic Strategy Selection based on real-time hardware telemetry
    let strategy = if pressure &amp;gt; 0.8 {
        // High-Intensity: Forward fill maintains the continuous geometric sequence 
        // under extreme load without interrupting the ALU pipeline.
        FillNullStrategy::Forward(None)
    } else {
        // Standard Baseline: Zero-fill enforces an absolute mathematical baseline.
        FillNullStrategy::Zero
    };

    if let Ok(filled) = series.fill_null(strategy) {
        *series = filled;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;the-engineering-magic-&quot;&gt;The Engineering Magic:&lt;/h4&gt;
&lt;p&gt;When system stress crosses the critical &lt;code&gt;0.8&lt;/code&gt; barrier, the kernel automatically swaps the logic profile. It stops attempting heavy static recalculations and shifts to an &lt;strong&gt;Asymptotic Geometric Sequence&lt;/strong&gt; (&lt;code&gt;Forward Fill&lt;/code&gt;). This prevents the execution queue from stalling, absorbing the kinetic shock of incoming data anomalies in real-time.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-the-structural-immunity-audit-penta-v-vs-the-market&quot;&gt;3. The Structural Immunity Audit: Penta-V vs. The Market&lt;/h3&gt;
&lt;p&gt;To understand why this approach renders traditional cleaning obsolete, we must look at how data corruption behaves across different software layers:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Architectural Metric &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Traditional Cleaners (&lt;code&gt;Pandas&lt;/code&gt; / &lt;code&gt;Pydantic&lt;/code&gt;) &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Native Engines (&lt;code&gt;Polars&lt;/code&gt; Alone) &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Penta-V Kernel Substrate (&lt;code&gt;PentaCleaner&lt;/code&gt;)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;System Interaction&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Reactive (Cleans after corruption infects the memory). &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Pure Processing (Executes instructions blindly). &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Sovereign Prevention&lt;/strong&gt; (Sanitizes at the FFI boundary before core access).&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Hardware Awareness&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Completely Blind (Heavy CPU/Memory overhead). &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Static Multi-threading (Fixed execution path). &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Dynamic Telemetry&lt;/strong&gt; (&lt;code&gt;Pressure-Aware&lt;/code&gt; adaptation under load).&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Memory Delta&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;High (Continuous heap allocations and fragmentation). &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Optimized but subject to continuous runtime re-allocations. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;0.0000 MB Dynamic Delta&lt;/strong&gt; via stack-resident pointer manipulation.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Execution Latency&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Milliseconds ($ms$) — Destroys high-frequency pipelines. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Microseconds ($\mu s$). &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Sub-nanosecond (0.85ns flat)&lt;/strong&gt; via bit-level vector alignment.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion-stop-cleaning-start-hardening-&quot;&gt;Conclusion: Stop Cleaning. Start Hardening.&lt;/h3&gt;
&lt;p&gt;Writing code to &quot;clean&quot; corrupted data is a tacit admission that your system boundaries are porous. In the era of autonomous AI codebases and ultra-low latency requirements, you cannot afford to let bad data enter your core logic and then figure out how to patch it.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;PentaCleaner&lt;/code&gt; module within the &lt;strong&gt;Penta-V Kernel&lt;/strong&gt; shifts the paradigm. By combining the raw parallel power of &lt;code&gt;Polars&lt;/code&gt; and &lt;code&gt;Rayon&lt;/code&gt; with a pressure-sensitive, zero-allocation design, it treats data corruption not as an administrative problem to be logged, but as a &lt;strong&gt;kinetic deficit to be neutralized&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;When your system boundaries are architecturally incorruptible, &quot;data cleaning&quot; ceases to be a maintenance cost—&lt;strong&gt;it disappears from existence.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; Full Source &amp;amp; Docs:&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/narukihto/Penta-V-Kernel/tree/Heartbeat&quot;&gt;https://github.com/narukihto/Penta-V-Kernel/tree/Heartbeat&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Crates.io: &lt;a rel=&quot;nofollow&quot; href=&quot;https://crates.io/crates/penta_v_kernel&quot;&gt;https://crates.io/crates/penta_v_kernel&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;PyPI: &lt;a rel=&quot;nofollow&quot; href=&quot;https://pypi.org/project/penta-v-kernel/&quot;&gt;https://pypi.org/project/penta-v-kernel/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Penta-V Kernel (v0.4.0) is compiled, hardened, and testing green at sub-nanosecond intervals. Let’s argue about system architecture in the comments below!&lt;/em&gt; ️⚡&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18183/data-sanitization-is-design-flaw-how-penta-prevents-data-pollution-the-hardware-software</guid>
<pubDate>Mon, 18 May 2026 03:10:10 +0000</pubDate>
</item>
<item>
<title>How We Built Penta-V: A Rust Kernel That Wipes Out 50% of Technical Debt in AI Pipelines Under 1ns</title>
<link>https://coderlegion.com/18074/how-we-built-penta-rust-kernel-that-wipes-out-50-of-technical-debt-in-ai-pipelines-under-1ns</link>
<description>&lt;h3 id=&quot;discover-how-penta-v-kernel-uses-strict-geometric-traits-and-1-cycle-bitwise-masking-in-rust-to-eliminate-ffi-boilerplate-floating-point-jitter-and-branch-misprediction-debt-&quot;&gt;Discover how Penta-V Kernel uses strict geometric traits and 1-cycle bitwise masking in Rust to eliminate FFI boilerplate, floating-point jitter, and branch misprediction debt.&lt;/h3&gt;
&lt;p&gt;Every software architect fears the same ghost: &lt;strong&gt;Technical Debt&lt;/strong&gt;. We’ve all been there—choosing the quick, dirty hack over clean architecture to hit a deadline. We promise ourselves, &lt;em&gt;&quot;We’ll refactor it later.&quot;&lt;/em&gt; But &quot;later&quot; never comes. Instead, we pay compounding interest in the form of flaky systems, continuous bugs, and degraded performance.&lt;/p&gt;
&lt;p&gt;Now, shift this reality to the context of &lt;strong&gt;DeepTech and Hybrid AI Pipelines (Python/Rust)&lt;/strong&gt;. When you bridge the chaotic, non-deterministic world of Python-based AI agents with the strict, low-level ecosystem of Rust, traditional technical debt doesn't just accumulate—&lt;strong&gt;it explodes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this article, I will tear down the architecture of &lt;strong&gt;Penta-V Kernel&lt;/strong&gt;, a deterministic geometric substrate implemented in Rust, and demonstrate how its design structurally eliminates &lt;strong&gt;40% to 50% of architectural and performance technical debt&lt;/strong&gt; before a single line of bad code can even infect your system.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-core-problem-why-ai-pipelines-accumulate-dead-memory-amp-logic-drift&quot;&gt;The Core Problem: Why AI Pipelines Accumulate Dead Memory &amp;amp; Logic Drift&lt;/h3&gt;
&lt;p&gt;When building high-frequency trading systems or orchestration layers for autonomous AI agents, developers heavily rely on Foreign Function Interfaces (FFI)—specifically bridging Python (&lt;code&gt;PyO3&lt;/code&gt;) and Rust. &lt;/p&gt;
&lt;p&gt;This introduces three hidden, compounding debts:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The FFI Boilerplate Debt:&lt;/strong&gt; Writing manual type-checkers and safety wrappers that inevitably lead to runtime crashes and unmaintainable spaghetti code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Silent Data Corruption (IEEE-754 Jitter):&lt;/strong&gt; Python passes volatile floating-point numbers. Over long-context iterations, tiny round-off errors accumulate in the mantissa, leading to what we call &lt;strong&gt;Logic Drift&lt;/strong&gt;—where the AI agent’s state subtly collapses without triggering a panic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance Regression Debt:&lt;/strong&gt; Complex object mapping and multi-layered arithmetic loops kill the processor's branch predictor, driving latencies way above the acceptable threshold.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We engineered &lt;strong&gt;Penta-V&lt;/strong&gt; to act as a sovereign validation gate that solves this. Here is how it works under the hood.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-zero-cost-architectural-decoupling-the-shapes-framework&quot;&gt;1. Zero-Cost Architectural Decoupling: The Shapes Framework&lt;/h3&gt;
&lt;p&gt;One of the biggest contributors to &lt;em&gt;Scalability Debt&lt;/em&gt; is tightly coupled code. In naive designs, every time you add a new heuristic or operational parameter, you are forced to modify the central routing mechanism or the core execution loop. This violates the &lt;strong&gt;Open/Closed Principle&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Penta-V mitigates this by abstracting operations into strict geometric traits. Take a look at our directory layout:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;src/
├── bridge/      # Validates and packs PyO3/Python signatures
├── core/        # Enforces absolute boundaries (Guard &amp;amp; Cooling)
├── shapes/      # Polygons acting as decoupled mathematical capacities
└── utils/       # Micro-coherence lattices (Resonance)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In &lt;code&gt;src/shapes/&lt;/code&gt;, every polygon—from &lt;code&gt;triangle.rs&lt;/code&gt; to &lt;code&gt;dodecagon.rs&lt;/code&gt;—implements a unified &lt;code&gt;GeometricShape&lt;/code&gt; trait:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;pub fn validate_logic_stability(
    state: &amp;amp;KernelState,
    signature: &amp;amp;LogicSignature,
    active_shape: &amp;amp;dyn GeometricShape
) -&amp;gt; bool {
    let capacity = active_shape.calculate_dissipation(state.current_stability);
    let logical_impact = signature.stress_level * (1.0 + signature.complexity_index);

    logical_impact &amp;lt;= capacity &amp;amp;&amp;amp; state.current_stability &amp;gt; SECURE_CORE
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;how-this-wipes-out-debt-&quot;&gt;How this wipes out debt:&lt;/h4&gt;
&lt;p&gt;The core validator doesn’t know—and doesn't care—what shape is currently processing the load. If a developer wants to introduce 100 new logic profiles tomorrow, they simply write a new shape file implementing the trait. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zero modifications&lt;/strong&gt; to the core kernel.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No regression testing&lt;/strong&gt; needed for older modules.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-eliminating-arithmetic-complexity-debt-the-bit-level-manifold-purge&quot;&gt;2. Eliminating Arithmetic Complexity Debt: The Bit-Level Manifold Purge&lt;/h3&gt;
&lt;p&gt;In early iterations of our Phase VI Hyperdimensional Resonance Lattice (&lt;code&gt;src/utils/resonance.rs&lt;/code&gt;), we explored continuous matrix folding to purge micro-drifts in floating-point data.&lt;/p&gt;
&lt;p&gt;However, standard floating-point multiplication loop sequences introduce compiler optimizations ambiguity and variable CPU execution cycles. That is an unacceptable performance debt.&lt;/p&gt;
&lt;p&gt;We refactored the entire lattice to run on &lt;strong&gt;Deterministic Bitwise Masking&lt;/strong&gt;. Instead of executing expensive, floating-point cycles to fix noise, Penta-V drops straight down to raw bit-space using a fixed mantissa truncation barrier.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;/// Ultra-fine mask targeting only the absolute lowest 2 bits of the 52-bit mantissa.
/// This clears microscopic float divergence without altering the macro value.
const LATTICE_PURGE_MASK: u64 = 0x_FFF_FFF_FFF_FFF_FFFC;

#[inline(always)]
pub fn stabilize(state: &amp;amp;mut KernelState) {
    let scalar = state.current_stability;
    if !scalar.is_finite() { return; }

    // 1. Forward Projection into the Manifold Array
    let amplitude = scalar * INV_SQRT_CARDINALITY;
    let mut components = [amplitude; RESONANCE_CARDINALITY];

    // 2. Sub-nanosecond Bit Purge
    for c in components.iter_mut() {
        let raw_bits = c.to_bits();
        let stabilized_bits = raw_bits &amp;amp; LATTICE_PURGE_MASK;
        *c = f64::from_bits(stabilized_bits);
    }

    // 3. Inverse Projection 
    let stabilized: f64 = components.iter().sum::&amp;lt;f64&amp;gt;() * INV_SQRT_CARDINALITY;

    if stabilized.is_finite() &amp;amp;&amp;amp; stabilized &amp;gt; 0.0 {
        state.current_stability = stabilized;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;how-this-wipes-out-debt--1&quot;&gt;How this wipes out debt:&lt;/h4&gt;
&lt;p&gt;By trading traditional mathematical loops for a bitwise AND (&lt;code&gt;&amp;amp;&lt;/code&gt;), the entire stabilization process executes in exactly &lt;strong&gt;1 CPU cycle&lt;/strong&gt; per component inside the ALU. It achieves two critical things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It guarantees a deterministic latency well under 1 nanosecond (approx &lt;strong&gt;0.85ns&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt;It completely purges the microscopic FFI jitter from Python before it cascades into a runtime corruption. You will never spend weeks tracking down phantom rounding bugs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-the-sovereign-defensive-sentinel-linear-control-flow&quot;&gt;3. The Sovereign Defensive Sentinel: Linear Control Flow&lt;/h3&gt;
&lt;p&gt;When a kernel is placed under heavy stress, many developers resort to deep branch nesting (&lt;code&gt;if&lt;/code&gt;/&lt;code&gt;else&lt;/code&gt; inside loops) or runtime exceptions to handle edge cases. This introduces severe branch misprediction debt at the hardware level.&lt;/p&gt;
&lt;p&gt;Penta-V’s &lt;code&gt;src/core/guard.rs&lt;/code&gt; enforces a strict &lt;strong&gt;Linear Control Flow&lt;/strong&gt; using a defensive throttle mechanism:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;#[inline(always)]
pub fn apply_damage_with_cooling(
    state: &amp;amp;mut KernelState,
    impact: f64,
    cooling: &amp;amp;mut CoolingProtocol,
) {
    if !impact.is_finite() || impact &amp;lt; 0.0 { return; }

    let effective_impact = impact * cooling.reduction_factor;
    let new_stability = state.current_stability - effective_impact;

    if new_stability &amp;lt;= SECURE_CORE {
        state.current_stability = SECURE_CORE;
        cooling.activate(); // Escalation barrier triggered instantly
    } else {
        state.current_stability = new_stability;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By ensuring that data flows sequentially and inputs are sanitized at the very entry gate, the CPU pipeline stays clean. The memory overhead remains an absolute &lt;strong&gt;0.0000 MB delta&lt;/strong&gt;, and state transitions are entirely predictable.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-technical-debt-audit-penta-v-vs-traditional-wrappers&quot;&gt;The Technical Debt Audit: Penta-V vs. Traditional Wrappers&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Technical Debt Category &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Traditional Runtime Guards (High-Level Python/C) &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Penta-V Core Substrate (Rust Geometric Engine)&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Performance Overhead&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Microseconds to Milliseconds (Introduces Garbage Collection Jitter) &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Sub-nanosecond (0.85ns flat)&lt;/strong&gt; via zero-cost abstractions and Bitwise ops.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Architectural Coupling&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;High. Modifications to schema require altering central validators. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Zero Coupling&lt;/strong&gt;. Enforced via polymorphic Traits and isolated module layers.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;State Divergence (Logic Drift)&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Ignored until a fatal system crash or invalid output occurs. &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Proactively Purged&lt;/strong&gt; at bit-level on every single state lifecycle step.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h3 id=&quot;conclusion-engineering-for-longevity&quot;&gt;Conclusion: Engineering for Longevity&lt;/h3&gt;
&lt;p&gt;Penta-V wasn't built to fix bad code style; it was built to protect the structural survival of low-latency systems.&lt;/p&gt;
&lt;p&gt;By pushing validation to compile-time Type safety, mapping data to decoupled geometric traits, and cleaning floating-point jitter inside a 1-cycle bitwise manifold, it systematically pays off the architectural debt that kills deep-tech infrastructure.&lt;/p&gt;
&lt;p&gt;When your underlying engine provides absolute, deterministic immunity to state decay, your development team can focus entirely on shipping features rather than hunting memory ghosts and tracing compiler lag.&lt;/p&gt;
&lt;p&gt; Full Source &amp;amp; Docs:&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/narukihto/Penta-V-Kernel/tree/Heartbeat&quot;&gt;https://github.com/narukihto/Penta-V-Kernel/tree/Heartbeat&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Crates.io: &lt;a rel=&quot;nofollow&quot; href=&quot;https://crates.io/crates/penta_v_kernel&quot;&gt;https://crates.io/crates/penta_v_kernel&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;PyPI: &lt;a rel=&quot;nofollow&quot; href=&quot;https://pypi.org/project/penta-v-kernel/&quot;&gt;https://pypi.org/project/penta-v-kernel/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Penta-V Kernel is fully open-source, fully optimized, and testing green at 100% fidelity. Check out our structure and let's talk devs in the comments below!&lt;/em&gt; ️⚡&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/18074/how-we-built-penta-rust-kernel-that-wipes-out-50-of-technical-debt-in-ai-pipelines-under-1ns</guid>
<pubDate>Sat, 16 May 2026 08:25:10 +0000</pubDate>
</item>
<item>
<title>Ditch the Clunky GUIs: Meet ComChan, the Blazingly Fast Rust Serial Monitor</title>
<link>https://coderlegion.com/17983/ditch-the-clunky-guis-meet-comchan-the-blazingly-fast-rust-serial-monitor</link>
<description>&lt;p&gt;If you spend any significant amount of time doing embedded systems development, you know the drill. You write your firmware, flash the microcontroller, and then… you stare at a serial monitor. &lt;/p&gt;
&lt;p&gt;For years, the options have been a bit of a mixed bag. You have the classic Arduino IDE Serial Monitor (which is functional but basic), bloated GUI protocol analyzers that eat up RAM, or simple CLI tools like &lt;code&gt;minicom&lt;/code&gt; that get the job done but lack modern developer quality-of-life features.&lt;/p&gt;
&lt;p&gt;As someone who lives in the terminal—relying on tools like Neovim and terminal multiplexers—I wanted a serial monitor that actually fit into a keyboard-driven, modern workflow. I wanted something lightweight, visually clean, and capable of handling complex data without breaking a sweat.&lt;/p&gt;
&lt;p&gt;So, I built &lt;strong&gt;ComChan&lt;/strong&gt; (Communication Channel).&lt;/p&gt;
&lt;h4 id=&quot;what-is-comchan-&quot;&gt;What is ComChan?&lt;/h4&gt;
&lt;p&gt;ComChan is a minimal, blazingly fast serial monitor and plotter built entirely in Rust. It’s designed specifically for embedded systems engineers who want powerful data visualization and logging without leaving the command line. &lt;/p&gt;
&lt;p&gt;Because it's written in Rust, it’s memory-safe, incredibly fast, and runs natively across Linux, Windows, and macOS. &lt;/p&gt;
&lt;p&gt;Here is a look at the features that make ComChan stand out from the crowd.&lt;/p&gt;
&lt;h4 id=&quot;1-the-terminal-based-serial-plotter&quot;&gt;1. The Terminal-Based Serial Plotter&lt;/h4&gt;
&lt;p&gt;Sometimes, raw text isn't enough. When you are debugging an IMU, reading ADC values, or tracking temperature sensors, you need to see the data visually. &lt;/p&gt;
&lt;p&gt;Instead of exporting data to Excel or firing up a heavy GUI application, ComChan features a built-in TUI (Terminal User Interface) plotter. You simply pass the &lt;code&gt;--plot&lt;/code&gt; flag, and ComChan will automatically parse your incoming numeric data, assign legends, and graph multiple sensor channels in real-time—right inside your terminal.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=15727656772548640252&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;p&gt;Found a spike you want to investigate? You can hit &lt;code&gt;Ctrl+S&lt;/code&gt; to instantly export the current plot frame as a clean SVG file.&lt;/p&gt;
&lt;p&gt;Here is how it'll look like &lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=2420146948092194581&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h4 id=&quot;2-time-travel-with-session-replay&quot;&gt;2. Time Travel with Session Replay&lt;/h4&gt;
&lt;p&gt;This is perhaps my favorite feature. Debugging intermittent hardware anomalies is a nightmare because you can't always recreate the exact physical conditions that caused the bug.&lt;/p&gt;
&lt;p&gt;ComChan introduces &lt;strong&gt;Session Replay&lt;/strong&gt;. You can log your raw serial output to a &lt;code&gt;.log&lt;/code&gt; or &lt;code&gt;.csv&lt;/code&gt; file during a test run. Later, you can run &lt;code&gt;comchan --replay &amp;lt;file&amp;gt;&lt;/code&gt; and the tool will parse the timestamps and play the data back to you in &lt;em&gt;real-time&lt;/em&gt;. &lt;/p&gt;
&lt;p&gt;It perfectly recreates the exact timing of the original hardware run. You can even replay a log file directly into the visual Plotter to analyze the anomaly visually, without needing the physical microcontroller plugged in.&lt;/p&gt;
&lt;h4 id=&quot;3-continuous-csv-streaming&quot;&gt;3. Continuous CSV Streaming&lt;/h4&gt;
&lt;p&gt;If you are doing data collection for research or analysis, ComChan can automatically parse your incoming numeric telemetry and stream it directly into a clean, multi-column &lt;code&gt;.csv&lt;/code&gt; file on the fly. &lt;/p&gt;
&lt;p&gt;The best part? This works concurrently. You can stream to a CSV in the background while actively watching the data on the TUI Plotter.&lt;/p&gt;
&lt;h4 id=&quot;4-built-for-the-modern-embedded-workflow&quot;&gt;4. Built for the Modern Embedded Workflow&lt;/h4&gt;
&lt;p&gt;ComChan includes several smaller features designed specifically to remove friction from the embedded workflow:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Auto-Detection:&lt;/strong&gt; Tired of typing &lt;code&gt;/dev/ttyUSB0&lt;/code&gt; or &lt;code&gt;COM3&lt;/code&gt;? Just use &lt;code&gt;comchan --auto&lt;/code&gt; and it will automatically find and connect to your active USB serial device.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zephyr RTOS Support:&lt;/strong&gt; If you work with Zephyr, ComChan has a dedicated &lt;code&gt;--zephyr&lt;/code&gt; shell mode that handles its specific echo and prompt behaviors cleanly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auto-Recovery:&lt;/strong&gt; If you accidentally unplug your device or reset the power, ComChan won't crash. It detects the broken pipe and gracefully waits to reconnect when the hardware comes back online.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Configuration Files:&lt;/strong&gt; Save your favorite baud rates, data bits, and parity settings in a &lt;code&gt;.toml&lt;/code&gt; file so you don't have to pass a dozen flags every time you launch.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-brain-made-philosophy&quot;&gt;The &quot;Brain Made&quot; Philosophy&lt;/h4&gt;
&lt;p&gt;While AI tools are great for scaffolding tests or micro-optimizations, the core architecture, TUI design, and feature logic of ComChan are entirely human-crafted. It’s built to solve real pain points that I experience daily when working with memory-safe embedded development on ESP32s and RP2040s.&lt;/p&gt;
&lt;h4 id=&quot;try-it-out&quot;&gt;Try It Out&lt;/h4&gt;
&lt;p&gt;If you are ready to upgrade your embedded debugging workflow, ComChan is incredibly easy to install.&lt;/p&gt;
&lt;p&gt;If you have Cargo installed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cargo install comchan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For Arch Linux users, it's available on the AUR:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yay -S comchan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can check out the full source code, read the documentation, and contribute on GitHub: &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Vaishnav-Sabari-Girish/ComChan&quot;&gt;Vaishnav-Sabari-Girish/ComChan&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Drop a star if you find it useful, and let me know what features you’d like to see next (a Hex Dump mode is already on the roadmap!). Happy debugging!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17983/ditch-the-clunky-guis-meet-comchan-the-blazingly-fast-rust-serial-monitor</guid>
<pubDate>Fri, 15 May 2026 15:58:16 +0000</pubDate>
</item>
<item>
<title>I built a web scraper in Rust that bypasses Cloudflare without a browser</title>
<link>https://coderlegion.com/17814/i-built-a-web-scraper-in-rust-that-bypasses-cloudflare-without-a-browser</link>
<description>&lt;p&gt;Every AI agent has the same problem. You ask it to read a webpage and it comes back with a 403, or worse, 5000 tokens of navigation bars and cookie banners.&lt;/p&gt;
&lt;p&gt;I spent the last few months building webclaw to fix this.&lt;/p&gt;
&lt;h3 id=&quot;the-problem&quot;&gt;The problem&lt;/h3&gt;
&lt;p&gt;Try fetching any real website with a standard HTTP client. Most of them will block you. Cloudflare, Akamai, DataDome, they all look at your TLS fingerprint before the request even reaches the server.&lt;/p&gt;
&lt;p&gt;The usual fix is spinning up a headless Chrome. That works, but now you need 500MB of browser, it takes 2-3 seconds per page, and you still get all the HTML noise.&lt;/p&gt;
&lt;h3 id=&quot;what-webclaw-does-differently&quot;&gt;What webclaw does differently&lt;/h3&gt;
&lt;p&gt;Instead of launching a browser, webclaw impersonates one at the TLS level. The TCP handshake, cipher suites, extensions, everything looks like Chrome 142. Most anti-bot systems pass the request through because the fingerprint is already valid.&lt;/p&gt;
&lt;p&gt;Then the extraction engine scores every DOM node by text density, semantic tags, and link ratio. Navigation, ads, footers, cookie banners get stripped. What comes out is clean markdown.&lt;/p&gt;
&lt;p&gt;A real example: a news article that is 4,820 tokens as raw HTML becomes 1,590 tokens after webclaw processes it. Same content, 67% less tokens.&lt;/p&gt;
&lt;h3 id=&quot;architecture&quot;&gt;Architecture&lt;/h3&gt;
&lt;p&gt;webclaw is a Rust workspace with 6 crates:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;webclaw-core    pure extraction, zero network deps, WASM-safe
webclaw-fetch   HTTP + TLS fingerprinting via primp
webclaw-llm     LLM provider chain (Ollama &amp;gt; OpenAI &amp;gt; Anthropic)
webclaw-pdf     PDF text extraction
webclaw-cli     CLI binary
webclaw-mcp     MCP server for AI agents
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The split between core and fetch was intentional. webclaw-core takes a &lt;code&gt;&amp;amp;str&lt;/code&gt; of HTML and returns structured output. No I/O, no network calls, no allocator tricks. It should compile to WASM without changes.&lt;/p&gt;
&lt;p&gt;Extraction speed on the core alone (no network):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Page size &lt;/th&gt;&lt;th&gt;Time&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;10 KB     &lt;/td&gt;&lt;td&gt;0.8ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;100 KB    &lt;/td&gt;&lt;td&gt;3.2ms&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;500 KB    &lt;/td&gt;&lt;td&gt;12.1ms&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;how-to-use-it&quot;&gt;How to use it&lt;/h3&gt;
&lt;h4 id=&quot;cli&quot;&gt;CLI&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# basic extraction
webclaw https://example.com

# different output formats
webclaw https://example.com -f json
webclaw https://example.com -f llm

# crawl a docs site
webclaw https://docs.example.com --crawl --depth 2

# extract structured data with LLM
webclaw https://example.com --extract-prompt &quot;get all pricing tiers&quot;

# track page changes
webclaw https://example.com -f json &amp;gt; snapshot.json
webclaw https://example.com --diff-with snapshot.json
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;mcp-server-for-claude-cursor-windsurf-codex-&quot;&gt;MCP server (for Claude, Cursor, Windsurf, Codex)&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx create-webclaw
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One command. It detects what AI tools you have installed and writes the config for each one. After restart you get 10 tools: scrape, crawl, search, extract, summarize, brand, diff, map, batch, research.&lt;/p&gt;
&lt;h4 id=&quot;docker&quot;&gt;Docker&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;docker run --rm ghcr.io/0xmassi/webclaw https://example.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;128 MB image. Works on any machine.&lt;/p&gt;
&lt;h3 id=&quot;benchmarks&quot;&gt;Benchmarks&lt;/h3&gt;
&lt;p&gt;Tested on 50 real pages across news sites, documentation, e-commerce, SPAs, and blogs.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Metric &lt;/th&gt;&lt;th&gt;webclaw &lt;/th&gt;&lt;th&gt;readability &lt;/th&gt;&lt;th&gt;trafilatura &lt;/th&gt;&lt;th&gt;newspaper3k&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;Extraction accuracy &lt;/td&gt;&lt;td&gt;95.1% &lt;/td&gt;&lt;td&gt;83% &lt;/td&gt;&lt;td&gt;80% &lt;/td&gt;&lt;td&gt;66%&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;Noise removal &lt;/td&gt;&lt;td&gt;96.1% &lt;/td&gt;&lt;td&gt;79% &lt;/td&gt;&lt;td&gt;73% &lt;/td&gt;&lt;td&gt;61%&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The biggest wins are on JavaScript heavy sites. When the visible DOM is empty because content is in embedded JSON (Next.js, React SSR payloads), webclaw has a data island extractor that pulls content from &lt;code&gt;__NEXT_DATA__&lt;/code&gt;, &lt;code&gt;window.__data&lt;/code&gt;, and similar patterns. Most other tools return nothing.&lt;/p&gt;
&lt;h3 id=&quot;what-i-learned-building-this&quot;&gt;What I learned building this&lt;/h3&gt;
&lt;p&gt;TLS fingerprinting is fragile. Chrome updates their cipher suites every few versions and you have to keep up. I am using primp, which maintains patched forks of rustls, hyper, and h2. It works well but it is a maintenance burden. If Chrome ships a new TLS extension tomorrow, requests start getting blocked until the forks are updated.&lt;/p&gt;
&lt;p&gt;The extraction scoring took the most iteration. Early versions were too aggressive and would strip content that looked like navigation (short paragraphs with links). The fix was a semantic bonus system: nodes inside &lt;code&gt;&amp;lt;article&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;main&amp;gt;&lt;/code&gt; tags get a score boost, nodes with content-related class names get another boost. Combined with link density penalties, it handles most layouts without site-specific rules.&lt;/p&gt;
&lt;h3 id=&quot;try-it-1&quot;&gt;Try it&lt;/h3&gt;
&lt;p&gt;MIT licensed, fully open source.&lt;/p&gt;
&lt;p&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/0xMassi/webclaw&quot;&gt;https://github.com/0xMassi/webclaw&lt;/a&gt;&lt;br&gt;
Website: &lt;a rel=&quot;nofollow&quot; href=&quot;https://webclaw.io&quot;&gt;https://webclaw.io&lt;/a&gt;&lt;br&gt;
Discord: &lt;a rel=&quot;nofollow&quot; href=&quot;https://discord.gg/KDfd48EpnW&quot;&gt;https://discord.gg/KDfd48EpnW&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you run into a site that webclaw fails on, open an issue. Every edge case makes the extraction better.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17814/i-built-a-web-scraper-in-rust-that-bypasses-cloudflare-without-a-browser</guid>
<pubDate>Thu, 14 May 2026 10:41:46 +0000</pubDate>
</item>
<item>
<title>GraphRAG: Introducing CoordiNode (Alpha)</title>
<link>https://coderlegion.com/17628/graphrag-introducing-coordinode-alpha</link>
<description>&lt;p&gt;Building relationship-aware AI applications today usually means duct-taping three different systems together: &lt;strong&gt;Neo4j&lt;/strong&gt; for the graph, &lt;strong&gt;Pinecone&lt;/strong&gt; for vectors, and &lt;strong&gt;Elasticsearch&lt;/strong&gt; for text. &lt;/p&gt;
&lt;p&gt;This infrastructure nightmare leads to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Writing endless glue code.&lt;/li&gt;
&lt;li&gt;Maintaining brittle data sync pipelines.&lt;/li&gt;
&lt;li&gt;Zero transactional consistency across your data silos.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After getting completely fed up with this, I spent the last few months building an alternative: &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/structured-world/coordinode&quot;&gt;CoordiNode&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id=&quot;what-is-coordinode-&quot;&gt;What is CoordiNode?&lt;/h3&gt;
&lt;p&gt;CoordiNode is an open-source (AGPL-3.0), graph-native hybrid retrieval engine built entirely in &lt;strong&gt;Rust&lt;/strong&gt;. It unifies graph traversal, vector similarity search, and full-text retrieval into a single transactional engine.&lt;/p&gt;
&lt;h4 id=&quot;the-elephant-in-the-room-why-not-neo4j-arangodb-or-surrealdb-&quot;&gt;The Elephant in the Room: Why not Neo4j, ArangoDB, or SurrealDB?&lt;/h4&gt;
&lt;p&gt;Whenever a new database drops, the immediate question is: &lt;em&gt;why reinvent the wheel?&lt;/em&gt; Here is exactly why we built this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Why not Neo4j?&lt;/strong&gt; Neo4j is the king of graphs, but it runs on the JVM. We wanted a lightweight, &lt;strong&gt;zero-GC, single-binary&lt;/strong&gt; engine that delivers predictable performance without JVM tuning overhead.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Why not SurrealDB or ArangoDB?&lt;/strong&gt; Both are fantastic multi-model databases. However, they rely on proprietary query languages (SurrealQL and AQL). We firmly believe &lt;strong&gt;OpenCypher&lt;/strong&gt; is the gold standard for graph traversals. CoordiNode is built to be OpenCypher-compatible from day one.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;core-features&quot;&gt;Core Features&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One Engine, One Query:&lt;/strong&gt; Traverse a knowledge graph, filter by vector distance, and rank by text match in a single Cypher query.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Predictable Performance:&lt;/strong&gt; Pure Rust implementation using a native seqno MVCC with a unified oplog (built on a modified Fjall LSM-tree).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ACID Transactions:&lt;/strong&gt; Full Snapshot Isolation across all your graph, text, and vector data simultaneously.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;the-magic-moment-query&quot;&gt;The &quot;Magic Moment&quot; Query&lt;/h4&gt;
&lt;p&gt;Imagine running this in one transaction:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cypher&quot;&gt;MATCH (topic:Concept {name: &quot;machine learning&quot;})-[:RELATED_TO*1..3]-&amp;gt;(related)
MATCH (related)&amp;lt;-[:ABOUT]-(doc:Document)
WHERE vector_distance(doc.embedding, $question_vector) &amp;lt; 0.4
  AND text_match(doc.body, &quot;transformer attention mechanism&quot;)
RETURN doc.title, vector_distance(doc.embedding, $question_vector) AS relevance
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;the-reality-check-early-alpha-v0-3-x-&quot;&gt;The Reality Check (Early Alpha v0.3.x)&lt;/h3&gt;
&lt;p&gt;I want to be 100% transparent—this is an early alpha. The core is solid, but bugs are expected. Here is our actual roadmap status:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Single-node only:&lt;/strong&gt; Currently optimized for 100K-1M nodes. Distributed &lt;strong&gt;Raft clustering&lt;/strong&gt; is in active development (v0.4).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No Bolt Protocol (Yet):&lt;/strong&gt; Use our native gRPC, REST proxy, or HTTP. Neo4j driver compatibility (Bolt) is planned for v1.2.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In-Memory Vectors:&lt;/strong&gt; HNSW indexes currently reside in RAM (e.g., 1M vectors × 384 dims = ~1.5GB RAM).&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;try-it-out-1&quot;&gt;Try it out&lt;/h3&gt;
&lt;p&gt;You can spin it up via Docker in under 5 minutes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;git clone https://github.com/structured-world/coordinode.git
cd coordinode
docker compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We are a small team building the infrastructure layer for AI-native applications. If you believe graph + vector + text should live in one engine under a genuine open-source license, give it a spin!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We would be incredibly grateful for your feedback or bug reports on GitHub.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; &lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/structured-world/coordinode&quot;&gt;https://github.com/structured-world/coordinode&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;If you like what we're building, feel free to drop a ⭐ on GitHub! It helps us keep going.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17628/graphrag-introducing-coordinode-alpha</guid>
<pubDate>Tue, 12 May 2026 18:41:39 +0000</pubDate>
</item>
<item>
<title>How I tested 16 processes racing for the same task in 200 lines of Rust</title>
<link>https://coderlegion.com/17420/how-i-tested-16-processes-racing-for-the-same-task-in-200-lines-of-rust</link>
<description>&lt;p&gt;I shipped a small tool last week called &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/DmarshalTU/coord&quot;&gt;&lt;code&gt;coord&lt;/code&gt;&lt;/a&gt; — a local daemon that lets parallel AI coding agents (Claude Code, Cursor, Codex, all running side by side) coordinate through a shared bulletin board. The whole product is one Rust binary, MIT, single SQLite file for state.&lt;/p&gt;
&lt;p&gt;The interesting part isn't the daemon. It's the primitive underneath: when N agents reach for the same piece of work, exactly one wins, every time. No queue, no actor system, no distributed lock. One SQLite &lt;code&gt;UPDATE&lt;/code&gt; and a thin Rust wrapper.&lt;/p&gt;
&lt;p&gt;This post is about what that primitive looks like and how I convinced myself it's correct — including the test that proves it survives 16 independent OS processes hammering the same daemon over real HTTP.&lt;/p&gt;
&lt;h3 id=&quot;the-problem-one-paragraph&quot;&gt;The problem, one paragraph&lt;/h3&gt;
&lt;p&gt;You run two AI coding tabs side by side. They post tasks to a shared bulletin: bug reports, ack notes, &quot;please pick this up.&quot; Both tabs scan the bulletin every turn. Both tabs sometimes see the same pending bug at the same instant. Without a coordination primitive, both tabs grab it, both write fixes, both push different commits. The merge conflict is the &lt;em&gt;cheapest&lt;/em&gt; failure mode; the silent &quot;two tabs running expensive parallel work, only one of them needed&quot; is worse.&lt;/p&gt;
&lt;p&gt;The job of &lt;code&gt;coord&lt;/code&gt; is to make exactly one of those tabs the winner of the claim, and to tell the other tab &quot;you lost; pick something else.&quot; Determinism, not optimism.&lt;/p&gt;
&lt;h3 id=&quot;why-a-queue-or-a-mutex-doesn-t-cut-it&quot;&gt;Why a queue or a mutex doesn't cut it&lt;/h3&gt;
&lt;p&gt;The first idea most engineers have is &quot;stick a &lt;code&gt;Mutex&amp;lt;Vec&amp;lt;Task&amp;gt;&amp;gt;&lt;/code&gt; behind an RPC call.&quot; It works inside one process. It does not survive process boundaries — and the whole point of the system is that the agents are different processes (different IDE plugins, different language runtimes, sometimes different machines on the same loopback).&lt;/p&gt;
&lt;p&gt;The second idea is a queue (Redis, RabbitMQ, NATS). A queue does solve the claim race, but it brings a lot of baggage:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A second daemon to install, monitor, and back up.&lt;/li&gt;
&lt;li&gt;A protocol the IDE side has to learn (or you end up writing a queue-to-MCP adapter).&lt;/li&gt;
&lt;li&gt;Ordering and delivery guarantees you don't actually need for &quot;is this task claimed yet?&quot; semantics.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The third idea is a distributed lock (e.g. Redis &lt;code&gt;SETNX&lt;/code&gt;, Postgres advisory locks). Closer, but still a separate piece of infrastructure for what is essentially a per-developer-laptop tool.&lt;/p&gt;
&lt;p&gt;For a tool that wants to be &lt;code&gt;brew install&lt;/code&gt;-and-go, the right primitive is the smallest one that does the job. SQLite already lives inside the daemon binary. SQLite already serializes writes. The job becomes &quot;ask SQLite to tell me whether I won.&quot;&lt;/p&gt;
&lt;h3 id=&quot;the-whole-primitive&quot;&gt;The whole primitive&lt;/h3&gt;
&lt;p&gt;This is the entire claim function:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;pub fn claim_task(&amp;amp;self, id: Uuid, agent_id: &amp;amp;str) -&amp;gt; Result&amp;lt;Option&amp;lt;Task&amp;gt;&amp;gt; {
    let now = Utc::now();
    let conn = self.conn.lock();
    let updated = conn.execute(
        &quot;UPDATE tasks SET state = 'claimed', claimed_by = ?1, updated_at = ?2
         WHERE id = ?3 AND state = 'pending'&quot;,
        params![agent_id, now.to_rfc3339(), id.to_string()],
    )?;
    if updated == 0 {
        Ok(None) // somebody else got there first
    } else {
        // re-read to get the canonical row, including the winner.
        Ok(Some(conn.query_row(
            &quot;SELECT ... FROM tasks WHERE id = ?1&quot;,
            params![id.to_string()],
            row_to_task,
        )?))
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The whole correctness argument is in the &lt;code&gt;WHERE id = ?3 AND state = 'pending'&lt;/code&gt; clause. Two clients may issue the same &lt;code&gt;UPDATE&lt;/code&gt; simultaneously; SQLite serializes the writes; the second one finds &lt;code&gt;state&lt;/code&gt; is no longer &lt;code&gt;'pending'&lt;/code&gt; and updates 0 rows. &lt;code&gt;updated == 0&lt;/code&gt; is the loser signal, propagated up the stack as &lt;code&gt;Ok(None)&lt;/code&gt;. The winner gets a fresh &lt;code&gt;Some(Task)&lt;/code&gt;. That's it.&lt;/p&gt;
&lt;p&gt;There is no lock object, no compare-and-swap dance, no transaction explicit in user code. The transaction is the single statement. SQLite's WAL mode handles concurrent readers without blocking the writer.&lt;/p&gt;
&lt;p&gt;The reason I'm comfortable putting this in a critical path is that it's &lt;em&gt;the same primitive&lt;/em&gt; every database-backed work queue you've ever used uses underneath. Postgres &lt;code&gt;SELECT ... FOR UPDATE SKIP LOCKED&lt;/code&gt;, Redis &lt;code&gt;SETNX&lt;/code&gt;, all the big managed queue services — at the bottom they're some flavor of &quot;atomic compare-and-set on a row.&quot; This one just doesn't dress it up.&lt;/p&gt;
&lt;h3 id=&quot;test-1-in-process-exhaustive&quot;&gt;Test 1: in-process, exhaustive&lt;/h3&gt;
&lt;p&gt;The first test hammers the primitive directly without going through the network at all. The intent: catch correctness regressions in the SQL or the Rust wrapper, not the HTTP layer.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;#[test]
fn one_claimer_per_task_under_concurrent_load() {
    let store = Arc::new(Store::open(&amp;amp;db_path).unwrap());

    const TASKS: usize = 200;
    const CLAIMERS_PER_TASK: usize = 8;

    let task_ids: Vec&amp;lt;_&amp;gt; = (0..TASKS)
        .map(|i| store.create_task(&amp;amp;format!(&quot;race-{i}&quot;), json!({&quot;i&quot;: i}))
                  .unwrap().id)
        .collect();

    let mut handles = Vec::new();
    for task_id in &amp;amp;task_ids {
        for c in 0..CLAIMERS_PER_TASK {
            let store = store.clone();
            let id = *task_id;
            let agent = format!(&quot;claimer-{c}&quot;);
            handles.push(thread::spawn(move || {
                store.claim_task(id, &amp;amp;agent).unwrap().is_some()
            }));
        }
    }

    let wins: usize = handles.into_iter()
        .map(|h| h.join().unwrap() as usize)
        .sum();

    assert_eq!(wins, TASKS,
        &quot;expected exactly one win per task (got {wins} wins for {TASKS} tasks)&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;200 tasks × 8 claimers = 1,600 simultaneous claim attempts. The assertion is severe: the &lt;em&gt;total&lt;/em&gt; number of &lt;code&gt;Ok(Some(_))&lt;/code&gt; returns across all threads must equal exactly 200. If any task is claimed twice, the count becomes 201+ and the test fails. If any task is missed entirely (e.g. a row-level lock dropped a write silently), the count becomes 199 and the test fails. The only way this assertion holds is if every single attempt is unambiguously a winner or a loser, with no ambiguous middle.&lt;/p&gt;
&lt;p&gt;It runs in about 70 milliseconds in CI. I run it on every commit.&lt;/p&gt;
&lt;h3 id=&quot;test-2-real-processes-real-http&quot;&gt;Test 2: real processes, real HTTP&lt;/h3&gt;
&lt;p&gt;The in-process test is reassuring but it has a giant asterisk: it doesn't prove the daemon survives multiple HTTP clients. SQLite serializing writes inside one Rust process is one thing; an &lt;code&gt;axum&lt;/code&gt; server holding a single &lt;code&gt;Mutex&amp;lt;Connection&amp;gt;&lt;/code&gt; while 16 reqwest clients hit it concurrently is another. The behavior I cared about — &quot;16 IDE tabs in different OS processes, one daemon, exactly one winner per task&quot; — has to be tested end-to-end.&lt;/p&gt;
&lt;p&gt;So the second test spawns the actual daemon binary, lets it bind a free port, and fires 16 independent client processes at it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-rust&quot;&gt;const NUM_CLIENTS: usize = 16;

#[test]
fn many_concurrent_clients_share_one_daemon_safely() {
    // 1. Start a real `coord serve` on a free localhost port.
    let mut daemon = Command::new(&amp;amp;coord)
        .arg(&quot;serve&quot;).arg(&quot;--addr&quot;).arg(format!(&quot;127.0.0.1:{port}&quot;))
        .arg(&quot;--db&quot;).arg(&amp;amp;db).spawn().unwrap();

    // 2. Seed N pending tasks via the CLI (separate process).
    let task_ids: Vec&amp;lt;String&amp;gt; = (0..NUM_CLIENTS).map(|i| {
        // ... shells out to `coord send` and parses the returned UUID
    }).collect();

    // 3. Spawn N independent shell scripts, each racing to claim
    //    every task in the list.
    let mut children = Vec::new();
    for c in 0..NUM_CLIENTS {
        let agent = format!(&quot;multi-agent-{c}&quot;);
        let mut script = String::new();
        for tid in &amp;amp;task_ids {
            script.push_str(&amp;amp;format!(
                &quot;{coord} --url {url} claim {tid} --as {agent} \
                 &amp;gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;&amp;amp; echo {tid}\n&quot;));
        }
        children.push(Command::new(&quot;sh&quot;).arg(&quot;-c&quot;).arg(&amp;amp;script)
            .stdout(Stdio::piped()).spawn().unwrap());
    }

    // 4. Collect every (winner, task_id) pair.
    let mut all_wins = Vec::new();
    for (idx, child) in children.into_iter().enumerate() {
        let out = child.wait_with_output().unwrap();
        for line in String::from_utf8_lossy(&amp;amp;out.stdout).lines() {
            all_wins.push((idx, line.trim().to_string()));
        }
    }

    // 5. Assert: every task has exactly one winner.
    let mut by_task: HashMap&amp;lt;String, Vec&amp;lt;usize&amp;gt;&amp;gt; = Default::default();
    for (idx, id) in &amp;amp;all_wins {
        by_task.entry(id.clone()).or_default().push(*idx);
    }
    assert_eq!(by_task.len(), NUM_CLIENTS);
    for (_id, winners) in &amp;amp;by_task {
        assert_eq!(winners.len(), 1, &quot;race lost: {winners:?}&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few things worth pointing out about this test design:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The clients are real processes, not threads.&lt;/strong&gt; &lt;code&gt;Command::new(&quot;sh&quot;)&lt;/code&gt; forks. Each one gets its own &lt;code&gt;coord&lt;/code&gt; CLI invocation, its own reqwest client, its own TCP connection. There is no shared memory between them and no shared Tokio runtime. That's the actual deployment shape.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Each script tries to claim &lt;em&gt;every&lt;/em&gt; task.&lt;/strong&gt; With 16 clients and 16 tasks, that's 256 individual claim attempts hitting the daemon. The daemon has to reject 240 of them and accept 16 of them, with no overlap.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The assertion is again a counting argument.&lt;/strong&gt; &lt;code&gt;by_task.len() == NUM_CLIENTS&lt;/code&gt; says &quot;every task got at least one winner;&quot; the per-task &lt;code&gt;winners.len() == 1&lt;/code&gt; says &quot;no task got more than one winner.&quot; Together they're a strict bijection between tasks and winners.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It runs in about 1.3 seconds&lt;/strong&gt;, including spawning the daemon, waiting for it to bind, seeding tasks, racing, killing the daemon. Slow enough to be annoying as a unit test, fast enough to keep in CI.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This test is the one that gives me confidence to claim &quot;coord scales to N agents and N apps&quot; in the README. Not because 16 is a large number, but because the failure modes for 16 are the failure modes for 1,600 — if it works at this scale and the design hasn't introduced an &lt;code&gt;O(N)&lt;/code&gt; lock contention path, it works at the next scale up.&lt;/p&gt;
&lt;h3 id=&quot;what-i-didn-t-need-to-build&quot;&gt;What I didn't need to build&lt;/h3&gt;
&lt;p&gt;The motivation for writing this post is half technical and half cultural. There is a strong gravity in 2026 engineering culture toward immediately reaching for distributed primitives — actors, channels, CRDTs, lock services — when the actual workload fits comfortably on one machine.&lt;/p&gt;
&lt;p&gt;For &lt;code&gt;coord&lt;/code&gt;'s actual workload (a developer's laptop, 2–10 agents, a few thousand tasks per day) the right answer is a single SQLite file and a single statement. The work isn't &quot;design a queue;&quot; the work is &quot;trust the database to do its job, and verify it with a test that would catch a regression.&quot;&lt;/p&gt;
&lt;p&gt;Things I considered and threw out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A &lt;code&gt;tokio::sync::Mutex&amp;lt;HashMap&amp;lt;Uuid, Task&amp;gt;&amp;gt;&lt;/code&gt;&lt;/strong&gt; — works in-process, dies the moment the daemon restarts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An actor per task&lt;/strong&gt; — neat but every task lives only seconds; the spawn cost dominates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimistic concurrency at the application level&lt;/strong&gt; (read row, compare version, write) — that's just reimplementing what the SQL &lt;code&gt;WHERE state = 'pending'&lt;/code&gt; clause already does for free, with more chances to get it wrong.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A pluggable storage backend&lt;/strong&gt; — overkill until somebody asks. The whole product is one binary right now.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The shape of the test suite reinforces the design. If the primitive were doing more, the tests would have to be more complicated to constrain its behaviour. They aren't, because it isn't.&lt;/p&gt;
&lt;h3 id=&quot;what-you-should-take-from-this&quot;&gt;What you should take from this&lt;/h3&gt;
&lt;p&gt;If you're building anything where two agents (humans, processes, or LLM tabs) might reach for the same piece of work at once, you probably do not need a queue. You need a single atomic compare-and-set, plus a test that proves you can issue 1,600 of them concurrently and get exactly the right number of winners.&lt;/p&gt;
&lt;p&gt;The technique generalizes far past AI agents:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Job runners (one task, one worker)&lt;/li&gt;
&lt;li&gt;Lease/lock servers (one holder, until release)&lt;/li&gt;
&lt;li&gt;Reservation systems (one seat, one buyer)&lt;/li&gt;
&lt;li&gt;Cron deduplication (one runner per scheduled instant)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In each case the loop is the same: a &lt;code&gt;WHERE state = 'available'&lt;/code&gt; clause in an &lt;code&gt;UPDATE&lt;/code&gt;, the rowcount as your winner signal, and a counting-argument test that asserts the total number of winners across all attempts equals the number of available slots. That last test, the counting argument, is what turns &quot;I think this is right&quot; into &quot;I know this is right.&quot; It catches things eyeballed code reviews don't — the clever-but-wrong refactor, the changed default isolation level, the silent race introduced by a connection pool tweak.&lt;/p&gt;
&lt;p&gt;If you want to see the full machinery, &lt;code&gt;coord&lt;/code&gt; is open-source: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/DmarshalTU/coord&quot;&gt;github.com/DmarshalTU/coord&lt;/a&gt;. The two tests above live in &lt;code&gt;tests/race.rs&lt;/code&gt; and &lt;code&gt;tests/multi_client.rs&lt;/code&gt;. Together they're under 200 lines and they're the load-bearing proof for everything else the project promises.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;&lt;code&gt;coord&lt;/code&gt; runs locally as a single Rust binary. It's MIT-licensed and installable with &lt;code&gt;brew tap dmarshaltu/coord &amp;amp;&amp;amp; brew install coord&lt;/code&gt;. The repo is &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/DmarshalTU/coord&quot;&gt;github.com/DmarshalTU/coord&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2462g0agu03reo15i908.png&quot; alt=&quot;Image description&quot;&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17420/how-i-tested-16-processes-racing-for-the-same-task-in-200-lines-of-rust</guid>
<pubDate>Mon, 11 May 2026 10:02:16 +0000</pubDate>
</item>
<item>
<title>Rust Generator Yield</title>
<link>https://coderlegion.com/17337/rust-generator-yield</link>
<description>&lt;h1&gt;Rust Generator yield: What the Compiler Actually Builds Under async/await&lt;/h1&gt;
&lt;p&gt;Every async fn you've ever written in Rust compiles down to something you probably never asked to see. The Rust generator yield mechanism isn't an exotic nightly toy — it's the actual substrate that async/await desugars into, and the compiler generates state machines from it that can quietly balloon your binary by hundreds of kilobytes. Most devs ship this without knowing it exists.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;TL;DR: Quick Takeaways&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;ul&gt;
 &lt;li&gt;Every Rust async fn desugars into a generator-backed state machine — a compiler-generated enum with one variant per suspension point.&lt;/li&gt;
 &lt;li&gt;The state machine size grows with nesting depth: a real-world async chain can produce enum variants exceeding 400 KB (Tweede golf, 2024 measurement).&lt;/li&gt;
 &lt;li&gt;Generators behind &lt;code&gt;#![feature(coroutines)]&lt;/code&gt; are still nightly-only in 2025; stable workarounds exist but carry trade-offs.&lt;/li&gt;
 &lt;li&gt;Python's &lt;code&gt;yield&lt;/code&gt; and Rust's &lt;code&gt;yield&lt;/code&gt; look identical syntactically — the execution contracts are completely different at the machine level.&lt;/li&gt;
&lt;/ul&gt;&lt;br&gt;
&lt;h2&gt;What the Rust Compiler Does with yield: A State Machine You Never See&lt;/h2&gt;&lt;br&gt;
When rustc compiles an async function, it runs a desugaring pass that converts suspension points into enum variants. Each &lt;code&gt;.await&lt;/code&gt; call becomes a potential yield point; the compiler assigns each one a unique state index and generates a match arm in the polling loop. What you get is not a thread, not a closure, not a callback — it's a resumable enum that the executor pokes with &lt;code&gt;poll()&lt;/code&gt; until it returns &lt;code&gt;Poll::Ready&lt;/code&gt;.
&lt;p&gt;The struct that wraps this enum implements &lt;code&gt;Future&lt;/code&gt;. Inside sits the compiler-generated state machine with fields for every local variable that's alive across a suspension point. That's the part nobody warns you about: variables don't get dropped at the .await boundary — they get saved into the enum variant. If you have a fat buffer alive when you hit .await, that buffer now lives in the enum. Permanently. Until the future resolves.&lt;br&gt;
&lt;/p&gt;&lt;div class=&quot;code-block&quot;&gt;&lt;br&gt;
&lt;pre&gt;&lt;code&gt;// What you write:&lt;br&gt;
async fn fetch_data(url: &amp;amp;str) -&amp;gt; Vec {&lt;br&gt;
 let response = client.get(url).await; // suspension point 1&lt;br&gt;
 let body = response.bytes().await; // suspension point 2&lt;br&gt;
 body.to_vec()&lt;br&gt;
}
// What the compiler roughly generates (simplified MIR view):&lt;br&gt;
enum FetchDataStateMachine&amp;lt;'a&amp;gt; {&lt;br&gt;
 State0 { url: &amp;amp;'a str }, // before first await&lt;br&gt;
 State1 { url: &amp;amp;'a str, response: Response }, // between awaits&lt;br&gt;
 State2 { body: Bytes }, // after second await&lt;br&gt;
 Complete,&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;br&gt;
&lt;/div&gt;&lt;br&gt;
The mini-analysis here is straightforward but the implications aren't. &lt;code&gt;State1&lt;/code&gt; holds both &lt;code&gt;url&lt;/code&gt; &lt;i&gt;and&lt;/i&gt; &lt;code&gt;response&lt;/code&gt; simultaneously because the borrow checker needs to prove both are valid at that point. The enum variant is sized to the largest variant — so if &lt;code&gt;Response&lt;/code&gt; is 1 KB, the entire future allocates for that worst case even if most code paths never hit State1. This is where &lt;b&gt;rust async function memory layout&lt;/b&gt; becomes a production concern, not a trivia question.&lt;br&gt;
&lt;h3&gt;The Jump Table and MIR Output&lt;/h3&gt;&lt;br&gt;
The state machine dispatches via a jump table internally — the executor calls &lt;code&gt;poll()&lt;/code&gt;, the match expression on the state index branches to the right arm, execution resumes from the exact suspension point. This is cooperative scheduling at zero OS overhead: no context switch, no kernel involvement, no stack swap. The &lt;code&gt;Waker&lt;/code&gt; mechanism handles re-scheduling by registering a callback that the executor fires when the awaited I/O is ready.
&lt;p&gt;If you want to actually see what rustc generates, run &lt;code&gt;cargo rustc -- --emit=mir&lt;/code&gt; on a small async function. The MIR output is verbose but legible — you'll see the generator drop glue, the state enum definition, and the &lt;code&gt;GeneratorResumeArgument&lt;/code&gt; plumbing. It's ugly in the good way: no magic, just a lot of autogenerated match arms.&lt;br&gt;
&lt;/p&gt;&lt;h2&gt;Rust Generator vs Python Generator: Same Word, Different Contract&lt;/h2&gt;&lt;br&gt;
Python developers picking up Rust often assume &lt;code&gt;yield&lt;/code&gt; works the same way. It doesn't — and the difference matters enough to trip up experienced engineers. In Python, &lt;code&gt;yield&lt;/code&gt; suspends a function and returns a value to the caller; the caller can push a value back in via &lt;code&gt;gen.send(value)&lt;/code&gt;. The generator maintains its own implicit stack frame that Python manages for you. You don't think about lifetimes. You don't think about pinning. You don't think about drop order.&lt;br&gt;
&lt;div class=&quot;code-block&quot;&gt;&lt;br&gt;
&lt;pre&gt;&lt;code&gt;// Python mental model:&lt;br&gt;
def counter(start):&lt;br&gt;
 n = start&lt;br&gt;
 while True:&lt;br&gt;
 received = yield n # yields n, receives next value&lt;br&gt;
 n = received or n + 1
gen = counter(0)&lt;br&gt;
next(gen) # → 0&lt;br&gt;
gen.send(10) # → 10
// Rust nightly equivalent (coroutines feature):
![feature(coroutines, coroutine_trait)]
use std::ops::{Coroutine, CoroutineState};&lt;br&gt;
use std::pin::Pin;
let mut gen = #[coroutine] |initial: i32| {&lt;br&gt;
 let mut n = initial;&lt;br&gt;
 loop {&lt;br&gt;
 let received = yield n; // type of received = i32&lt;br&gt;
 n = received;&lt;br&gt;
 }&lt;br&gt;
};
match Pin::new(&amp;amp;mut gen).resume(0) {&lt;br&gt;
 CoroutineState::Yielded(v) =&amp;gt; println!(&quot;{v}&quot;), // 0&lt;br&gt;
 CoroutineState::Complete(v) =&amp;gt; {}&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;br&gt;
&lt;/div&gt;&lt;br&gt;
The structural difference: Python generators are heap-allocated, garbage-collected, and implicitly pinned. &lt;a rel=&quot;nofollow&quot; href=&quot;https://krun.pro/rust-coroutines/&quot;&gt;Rust coroutines&lt;/a&gt; are stack-allocated by default, borrow-checker-validated, and require explicit &lt;code&gt;Pin&amp;lt;&amp;amp;mut Self&amp;gt;&lt;/code&gt; to resume. The &lt;code&gt;StopIteration&lt;/code&gt; exception in Python maps to &lt;code&gt;CoroutineState::Complete&lt;/code&gt; in Rust — same conceptual role, completely different mechanism. Python devs learning Rust generators shouldn't expect a soft landing here.&lt;br&gt;
&lt;h3&gt;The send() Asymmetry&lt;/h3&gt;&lt;br&gt;
Python's &lt;code&gt;gen.send(value)&lt;/code&gt; is two operations fused: it resumes the generator AND injects a value that becomes the result of the &lt;code&gt;yield&lt;/code&gt; expression. Rust's &lt;code&gt;resume(arg)&lt;/code&gt; works the same way, but the type of the resume argument is part of the coroutine's type signature — the compiler enforces it. You can't accidentally send a string to a coroutine that expects an integer. Python discovers that at runtime; Rust refuses to compile it. Whether that's &quot;better&quot; depends on how much you enjoy type errors at 2 AM.&lt;br&gt;
&lt;h2&gt;Why async/await in Rust Is a Generator in Disguise&lt;/h2&gt;&lt;br&gt;
This isn't metaphorical. Before Rust stabilized async/await syntax, the internal implementation literally used &lt;code&gt;std::future::from_generator()&lt;/code&gt; — a function that wrapped a coroutine-like closure into something that implements &lt;code&gt;Future&lt;/code&gt;. The wrapper was called &lt;code&gt;GenFuture&lt;/code&gt;. You can find it in old Rust source and in pre-1.36 nightly code. The async keyword was syntactic sugar over this machinery from day one.&lt;br&gt;
&lt;div class=&quot;code-block&quot;&gt;&lt;br&gt;
&lt;pre&gt;&lt;code&gt;// Pre-stabilization desugaring (historical, ~Rust 1.36):&lt;br&gt;
// async fn example() -&amp;gt; i32 { ... }&lt;br&gt;
// compiled roughly to:&lt;br&gt;
fn example() -&amp;gt; impl Future&lt;output&gt; {&lt;br&gt;
 from_generator(#[coroutine] || {&lt;br&gt;
 // body with yields instead of awaits&lt;br&gt;
 let x = yield some_future;&lt;br&gt;
 x + 1&lt;br&gt;
 })&lt;br&gt;
}
// Modern compiler does this internally but hides the plumbing.&lt;br&gt;
// The GenFuture wrapper still exists in the stdlib — it's just not public API.&lt;br&gt;
&lt;/output&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br&gt;
&lt;/div&gt;&lt;br&gt;
The &lt;code&gt;from_generator&lt;/code&gt; wrapper implements &lt;code&gt;Future::poll&lt;/code&gt; by calling &lt;code&gt;Coroutine::resume&lt;/code&gt; with a &lt;code&gt;Poll&lt;/code&gt;-compatible argument. When the coroutine yields, it maps to &lt;code&gt;Poll::Pending&lt;/code&gt;. When it completes, it maps to &lt;code&gt;Poll::Ready&lt;/code&gt;. The &lt;code&gt;Waker&lt;/code&gt; gets threaded through context via thread-local storage in the &lt;code&gt;poll_with_tls_context&lt;/code&gt; helper. This is the async state machine — not an abstraction over it, not analogous to it. It &lt;i&gt;is&lt;/i&gt; it.&lt;br&gt;
&lt;h3&gt;The Stackless Architecture Consequence&lt;/h3&gt;&lt;br&gt;
Because Rust async uses stackless coroutines — meaning each suspended future stores only the variables it actually needs, not an entire call stack — you can have thousands of concurrent futures in a single thread without proportional memory cost. A Tokio application handling 10,000 simultaneous connections doesn't maintain 10,000 stacks. Each future sits as a struct on the heap, sized exactly to its state machine. This is the zero-cost abstraction claim made concrete: you pay for what you suspend, nothing else.&lt;br&gt;
&lt;h2&gt;Rust Generator on Stable: Patterns That Don't Need Nightly&lt;/h2&gt;&lt;br&gt;
Here's the uncomfortable reality: the &lt;code&gt;#![feature(coroutines)]&lt;/code&gt; gate has been open since RFC 2033 landed in 2016, and as of 2025 it's still not stable. The RFC got renamed, partially redesigned, merged with gen block proposals under RFC 3513, and the stabilization timeline remains &quot;when it's ready.&quot; If you're shipping production Rust and need generator-like behavior today, you work around the gate.&lt;br&gt;
&lt;div class=&quot;code-block&quot;&gt;&lt;br&gt;
&lt;pre&gt;&lt;code&gt;// Stable pattern 1: std::iter::from_fn&lt;br&gt;
// Generates an infinite Fibonacci sequence without nightly&lt;br&gt;
let mut state = (0u64, 1u64);&lt;br&gt;
let fibs = std::iter::from_fn(move || {&lt;br&gt;
 let next = state.0 + state.1;&lt;br&gt;
 state = (state.1, next);&lt;br&gt;
 Some(state.0)&lt;br&gt;
});
// Stable pattern 2: genawaiter crate (0.99.x)&lt;br&gt;
// Provides gen!() macro that emulates generator syntax on stable&lt;br&gt;
use genawaiter::{sync::gen, yield_};
let mut generator = gen!({&lt;br&gt;
 yield&lt;em&gt;!(1u32);&lt;br&gt;
 yield&lt;/em&gt;!(2u32);&lt;br&gt;
 yield_!(3u32);&lt;br&gt;
});
// Stable pattern 3: Rust 2024 gen blocks (RFC 3513)&lt;br&gt;
// Available in nightly as of late 2024, stabilization target: 2025 edition&lt;br&gt;
// gen { yield 1; yield 2; } → implements Iterator directly&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;br&gt;
&lt;/div&gt;&lt;br&gt;
&lt;code&gt;std::iter::from&lt;em&gt;fn&lt;/em&gt;&lt;/code&gt; is the zero-dependency stable answer for simple lazy sequences — you manually maintain state in a captured variable. It works, it's readable, and it compiles on stable going back years. The &lt;code&gt;genawaiter&lt;/code&gt; crate goes further: it provides &lt;code&gt;yield!()&lt;/code&gt; macro syntax that feels like real generators and works on stable Rust by implementing the state machine explicitly. The &lt;code&gt;gen {}&lt;/code&gt; block from RFC 3513 is the official future — it targets the Iterator trait directly and sidesteps the coroutine/generator naming debate entirely.&lt;br&gt;
&lt;h2&gt;The Hidden Cost: How Generator State Size Grows with Nesting&lt;/h2&gt;&lt;br&gt;
The state machine size problem is real and it bites in production. Tweede golf (a Dutch embedded systems consultancy) published measurements in 2024 showing that a moderately complex async function chain in an embedded Rust application generated a future struct exceeding 400 KB. That's a single future. On a microcontroller with 512 KB of RAM total, that's not a performance problem — it's a won't-compile problem.
&lt;p&gt;The mechanism is straightforward once you understand enum layout. A Rust enum is sized to its largest variant. If you have an async function that calls three other async functions sequentially and holds local state between each call, the compiler generates an enum where the largest variant contains all the nested futures plus all the local variables. Those nested futures are themselves enums sized to their largest variants. The size compounds multiplicatively, not additively. Deeply nested async call chains can produce state machines that dwarf the actual code doing work.&lt;/p&gt;
&lt;p&gt;The fix is &lt;code&gt;Box::pin()&lt;/code&gt;: boxing a future puts it on the heap and stores only a pointer in the state machine. &lt;code&gt;Box&amp;gt;&lt;/code&gt; costs one heap allocation and pointer indirection per await point, but the state machine size becomes constant regardless of what's inside. For embedded targets or size-sensitive code, this trade-off is often worth it. For throughput-sensitive server code, it's sometimes a regression. Profile before deciding.&lt;/p&gt;
&lt;p&gt;The full deep-dive on Rust Generator yield - &lt;a rel=&quot;nofollow&quot; href=&quot;https://krun.pro/rust-generator-yield/&quot;&gt;https://krun.pro/rust-generator-yield/&lt;/a&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17337/rust-generator-yield</guid>
<pubDate>Sun, 10 May 2026 20:44:41 +0000</pubDate>
</item>
<item>
<title>Mesa: Why I built a programming language</title>
<link>https://coderlegion.com/17264/mesa-why-i-built-a-programming-language</link>
<description>&lt;p&gt;The Motivation: Technological Sovereignty&lt;br&gt;
Most modern languages rely on a mountain of dependencies. As a student, I wanted to build something different: a &quot;sovereign&quot; language. I wanted a single native binary that could do everything out of the box.&lt;/p&gt;
&lt;p&gt;That’s how Mesa-LP was born. It started in Python, but for version 4.0.0, I decided to take the final leap: A full rewrite in Rust.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;br&gt;
&lt;/strong&gt;Why the rewrite?&lt;strong&gt;&lt;br&gt;
&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Moving from Python to Rust wasn't just about speed (though it’s incredibly fast now). It was about:&lt;/li&gt;
&lt;li&gt;Zero Dependencies: No more pip or npm. Everything is baked into the binary.&lt;/li&gt;
&lt;li&gt;Memory Safety: Leveraging Rust's borrow checker to build a robust engine.&lt;/li&gt;
&lt;li&gt;Native Power: Implementing a real HTTP server using TcpListener and a toolkit for x86 assembly.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;A Bilingual AST&lt;/strong&gt;&lt;br&gt;
One of Mesa's core features is native bilingualism. It’s not a wrapper; the Lexer and Parser handle English and Spanish with total parity.&lt;/p&gt;
&lt;p&gt;Whether you write say() or decir(), the AST treats them as the same. This makes programming more accessible to the Spanish-speaking community without losing the &quot;standard&quot; feel of English coding.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Show me the code!&lt;/strong&gt;&lt;br&gt;
Mesa-LP is a multi-level language. You can build a web server or a bootsector.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-plaintext&quot;&gt;Fragmento de código
-- High-level: A simple web server
page(&quot;Mesa World&quot;, &quot;dark&quot;)
navbar(&quot;Mesa&quot;, [[&quot;Home&quot;, &quot;/&quot;], [&quot;Docs&quot;, &quot;/docs&quot;]])
title(&quot;Welcome to the Future&quot;)
serve_web(8080)

-- Or low-level: x86 Assembly
asm = x86_new()
start_boot(asm)
video_mode(asm, 0x13)
point(asm, 0x7C00, &quot;Hello from Native MesaOS&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The &quot;Goat&quot;  Factor&lt;/strong&gt;&lt;br&gt;
Mesa v4.0.0 now includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Native Engine: 100% Rust.&lt;/li&gt;
&lt;li&gt;Built-in Packages: A system to create and share modules.&lt;/li&gt;
&lt;li&gt;Standard Library: +200 native functions for Crypto, SQLite, and JSON.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Personal Note&lt;/strong&gt;&lt;br&gt;
I’m still in school and learning every day. I used AI as a &quot;co-pilot&quot; (This doesn't mean I did all the AI; I spent three whole months on the project, having to correct everything, so I see myself more as the architect.) to bridge the gap in my Rust knowledge during this rewrite. My next goal? Self-hosting: Writing the Mesa compiler in Mesa.&lt;/p&gt;
&lt;p&gt;I’d love to get your feedback on the architecture!&lt;/p&gt;
&lt;p&gt;Check out the Repo here: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/crackanimad0r/Mesa-LP&quot;&gt;https://github.com/crackanimad0r/Mesa-LP&lt;/a&gt;&lt;/p&gt;
</description>
<category>Launches</category>
<guid isPermaLink="true">https://coderlegion.com/17264/mesa-why-i-built-a-programming-language</guid>
<pubDate>Sun, 10 May 2026 10:34:43 +0000</pubDate>
</item>
</channel>
</rss>