<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>README — languages</title>
<link>https://readme.news/tags/languages/</link>
<atom:link href="https://readme.news/tags/languages/feed.xml" rel="self" type="application/rss+xml"/>
<description>README pieces tagged languages.</description>
<language>en-us</language>
<lastBuildDate>Thu, 01 Oct 2026 13:20:31 +0000</lastBuildDate>
<item><title>Java 27 and the LTS question</title><link>https://readme.news/java-27-and-the-lts-question/</link><guid isPermaLink="true">https://readme.news/java-27-and-the-lts-question/</guid><pubDate>Tue, 08 Sep 2026 09:00:00 +0000</pubDate><description>Six-month releases, an LTS every two years, and a decision most teams make by default rather than deliberately.</description><content:encoded><![CDATA[<p>Java ships every March and September, with a long-term-support release every two years. That cadence has been stable since Java 9 changed everything in 2017, and it presents every team with a choice they usually make by not making it.</p>
<h2 id="the-two-strategies">the two strategies<a class="anchor" href="#the-two-strategies" aria-label="link to this section">#</a></h2>
<p><strong>Ride the LTS train.</strong> Upgrade only on LTS versions, roughly every two years. Vendors support them for years. This is what most enterprises do.</p>
<p><strong>Ride every release.</strong> Upgrade every six months. Each step is small.</p>
<p>The instinct is that LTS is the conservative choice. It is worth examining that, because it is not obviously true.</p>
<h2 id="the-case-against-lts-only">the case against LTS-only<a class="anchor" href="#the-case-against-lts-only" aria-label="link to this section">#</a></h2>
<p><strong>Bigger jumps.</strong> Two years of change absorbed at once, including four releases' worth of deprecations and removals, all discovered in the same week.</p>
<p><strong>Deprecation surprises.</strong> Features are deprecated in one release and removed a few later. On the every-release path you see the warning and have six months. On the LTS path the warning and the removal can arrive in the same upgrade.</p>
<p><strong>You are testing a configuration fewer people ran.</strong> Ironically, the LTS jump from N to N+8 is a path exercised by fewer teams than each individual step.</p>
<p><strong>Two years of free performance left on the table.</strong> GC and JIT work lands continuously.</p>
<h2 id="the-case-for-it">the case for it<a class="anchor" href="#the-case-for-it" aria-label="link to this section">#</a></h2>
<p><strong>Vendor support.</strong> For some organisations this is contractual and ends the discussion.</p>
<p><strong>Fewer upgrade events.</strong> Each one has fixed overhead — testing, coordination, sign-off. Four small upgrades can cost more total effort than one large one, even if each is easier.</p>
<p><strong>Library ecosystem lag.</strong> Frameworks target LTS versions first. On a non-LTS release you can be waiting for a dependency.</p>
<p>That last one is the real constraint, and it is the honest reason most teams stay on LTS.</p>
<h2 id="the-strategy-that-gets-the-benefit-of-both">the strategy that gets the benefit of both<a class="anchor" href="#the-strategy-that-gets-the-benefit-of-both" aria-label="link to this section">#</a></h2>
<p>Run your <strong>tests</strong> on every release; run <strong>production</strong> on LTS.</p>
<p>Add the latest JDK to your CI matrix as a non-blocking job. It costs a few minutes per build. When something breaks, you find out six months early, in a build, rather than during the upgrade under a deadline.</p>
<p>This is a small change with a large effect on how the LTS jump feels, and it is the single most useful thing a team on the LTS path can do.</p>
<h2 id="the-upgrade-checklist">the upgrade checklist<a class="anchor" href="#the-upgrade-checklist" aria-label="link to this section">#</a></h2>
<ul><li><strong>Check the removal list first</strong>, not the feature list. Removed APIs and changed defaults are what break you.</li><li><strong>Run with <code>-Xlint:all</code> and <code>--enable-preview</code> off.</strong> Preview features are not a migration target.</li><li><strong>Re-benchmark rather than assuming.</strong> GC defaults and JIT behaviour change; usually for the better, occasionally not for your allocation pattern.</li><li><strong>Check your agents.</strong> Profilers, APM agents and anything doing bytecode instrumentation are the most common source of upgrade breakage, and they break loudly at startup rather than subtly at runtime.</li><li><strong>Look at what <a class="xref" href="/virtual-threads-two-years-on/" title="Virtual threads, two years on">virtual threads</a> did to your pool sizing</strong> if you have adopted them. Connection pools sized for a thread-pool world are now the bottleneck.</li></ul>
<h2 id="the-general-point">the general point<a class="anchor" href="#the-general-point" aria-label="link to this section">#</a></h2>
<p>Any dependency with a fixed cadence — Java, Go, Rust, Python, Node, Postgres — presents the same question: small steps often, or large steps rarely.</p>
<p>The answer that keeps being right is <strong>small steps often, with the large-step path tested continuously in CI</strong>. It converts a scary infrequent event into a boring frequent one, which is the same trick that makes deployment safe.</p>]]></content:encoded></item><item><title>Rust's six-week metronome, seven years on</title><link>https://readme.news/rusts-six-week-metronome-seven-years-on/</link><guid isPermaLink="true">https://readme.news/rusts-six-week-metronome-seven-years-on/</guid><pubDate>Thu, 27 Aug 2026 09:00:00 +0000</pubDate><description>A release every six weeks, no exceptions, no marketing cycle. What that cadence actually produces.</description><content:encoded><![CDATA[<p>Another Rust release lands today, six weeks after the last one, as it has since</p>
<ol><li>There is no launch event, the release notes are a list, and by tomorrow</li></ol>
<p>nobody will be discussing it.</p>
<p>That is the interesting part.</p>
<h2 id="what-a-metronome-produces">what a metronome produces<a class="anchor" href="#what-a-metronome-produces" aria-label="link to this section">#</a></h2>
<p><strong>Features ship when they are ready, not when a date needs filling.</strong> A fixed train with frequent departures means nothing is ever rushed to make a release — it just goes in the next one, six weeks later. Compare with an annual release, where missing the window costs a year and the pressure to ship something half-finished is enormous.</p>
<p><strong>No release is a big deal, so no release is risky.</strong> Upgrading across six weeks of change is routine. Upgrading across a year of change is a project. The frequency is what keeps each step small enough to be boring.</p>
<p><strong>Continuous improvement is invisible and enormous.</strong> Any single Rust release looks minor. The five-year delta is a different language: async in traits, let chains, <a class="xref" href="/rust-189-and-the-long-tail-of-const-generics/" title="Rust 1.89 and the long tail of const generics">const generics</a>, a rewritten <a class="xref" href="/rust-184-and-the-quiet-rewrite-underneath/" title="Rust 1.84 and the quiet rewrite underneath">trait solver</a>, dramatically better <a class="xref" href="/error-messages-are-a-user-interface/" title="Error messages are a user interface">error messages</a>, a linker that made everyone's builds faster without anyone opting in.</p>
<p>Nobody experienced that as an upgrade. It arrived six weeks at a time.</p>
<h2 id="the-part-that-makes-it-work">the part that makes it work<a class="anchor" href="#the-part-that-makes-it-work" aria-label="link to this section">#</a></h2>
<p>The cadence alone would not be enough. What makes it safe is the stability guarantee: code that compiled on stable keeps compiling. Breaking changes go through editions — opt-in, per-crate, with automated migration, on a multi-year cycle.</p>
<p>So the six-week train carries only additions and fixes. The scary changes ride a different, much slower vehicle, and you choose when to board.</p>
<p>That separation is the actual design insight, and it is the thing most projects miss when they copy the cadence without the guarantee.</p>
<h2 id="what-to-do-on-release-day">what to do on release day<a class="anchor" href="#what-to-do-on-release-day" aria-label="link to this section">#</a></h2>
<p>The same short list, every time:</p>
<ol><li><strong>Update and run your tests.</strong> <code>rustup update stable</code>. This is almost always uneventful, which is the point.</li><li><strong>Read the release notes for stabilised APIs.</strong> This is where you find the <code>std</code> function that lets you delete a dependency or an <code>unsafe</code> block.</li><li><strong>Run <code>cargo clippy</code>.</strong> New releases teach clippy new lints, and those lints find real bugs in code that has been compiling for years.</li><li><strong>Check <code>cargo build --timings</code></strong> occasionally. Compiler performance work lands continuously and you will never notice it unless you look.</li></ol>
<h2 id="the-transferable-lesson">the transferable lesson<a class="anchor" href="#the-transferable-lesson" aria-label="link to this section">#</a></h2>
<p>If you maintain anything other people depend on, the two-track structure is worth stealing regardless of your language:</p>
<ul><li><strong>A frequent, predictable, purely-additive train</strong> that is always safe to board.</li><li><strong>A separate, rare, opt-in mechanism</strong> for the changes that break things, with tooling to migrate.</li></ul>
<p>Most projects have neither — they ship when they ship, and breaking changes arrive mixed into ordinary releases. That combination is what makes upgrades scary, and upgrades being scary is what leaves everyone three versions behind.</p>
<p>Rust's release notes are boring because the interesting decisions were made about process, once, a decade ago.</p>]]></content:encoded></item><item><title>Go 1.27 and the six-month metronome</title><link>https://readme.news/go-127-and-the-six-month-metronome/</link><guid isPermaLink="true">https://readme.news/go-127-and-the-six-month-metronome/</guid><pubDate>Thu, 06 Aug 2026 09:00:00 +0000</pubDate><description>Go ships every February and August, has done for a decade, and the predictability is worth more than any feature in it.</description><content:encoded><![CDATA[<p>Go's release cadence is one of the most boring facts in software: a version in February, a version in August, every year since 2015. Today is one of those days.</p>
<p>The predictability is not incidental to Go's success. It is a substantial part of it, and it is worth examining because most projects get this wrong in the other direction.</p>
<h2 id="what-a-fixed-cadence-buys">what a fixed cadence buys<a class="anchor" href="#what-a-fixed-cadence-buys" aria-label="link to this section">#</a></h2>
<p><strong>Planning that does not require guessing.</strong> A team can put "upgrade Go" in the calendar twice a year, permanently, and never think about it again. Compare with a project that ships when it is ready: every upgrade is an unscheduled interruption someone has to notice and argue for.</p>
<p><strong>Features that are not held hostage.</strong> When the train leaves on a fixed date, an unfinished feature waits for the next one instead of delaying the release. That means no pressure to ship something half-done to make a date, and no feature holding the whole release. Rust's six-week cycle works the same way, and both languages are visibly healthier for it.</p>
<p><strong>A support window you can reason about.</strong> Go supports the two most recent releases. That is a rolling twelve months, always, with no announcement needed.</p>
<h2 id="the-compatibility-promise-underneath">the compatibility promise underneath<a class="anchor" href="#the-compatibility-promise-underneath" aria-label="link to this section">#</a></h2>
<p>None of the cadence would matter without Go 1's compatibility guarantee: code written against Go 1 continues to compile and run. That has held since 2012.</p>
<p>The combination is what makes Go upgrades routine rather than projects. You are not choosing between "stay on an old version" and "spend a sprint migrating" — the upgrade is a version bump in one file, a test run, and a deploy.</p>
<p>Very few ecosystems can say that. It is the single most underrated thing about the language and it almost never appears in comparisons, because absence of pain is hard to notice.</p>
<h2 id="what-to-actually-do-on-release-day">what to actually do on release day<a class="anchor" href="#what-to-actually-do-on-release-day" aria-label="link to this section">#</a></h2>
<p>Nothing dramatic, which is the point.</p>
<ol><li><strong>Read the release notes</strong>, specifically the "minor changes to the library" section. That is where behaviour shifts that are technically compatible and practically surprising tend to live.</li><li><strong>Bump the toolchain line</strong> in <code>go.mod</code> and run your tests.</li><li><strong>Check the vet and linter output.</strong> New releases usually teach <code>go vet</code> new checks, and those checks find real bugs in code that has been running for years.</li><li><strong>Look at the GC and runtime notes</strong> if you run anything latency-sensitive. Runtime changes are where the performance surprises are, in both directions.</li><li><strong>Do not skip versions.</strong> Two at a time is fine. Four is where people start having a bad week.</li></ol>
<h2 id="the-general-lesson">the general lesson<a class="anchor" href="#the-general-lesson" aria-label="link to this section">#</a></h2>
<p>If you maintain anything with users — a library, an internal platform, a service other teams build on — the cadence question is worth asking directly: <em>do people know when the next version arrives, and can they plan around it?</em></p>
<p>"When it's ready" is the honest answer for a side project and a liability for anything load-bearing. A published date, even a slow one, lets everyone downstream stop guessing.</p>
<p>Boring, on a schedule, is a feature.</p>]]></content:encoded></item><item><title>WWDC 2026 and the platform that keeps its own counsel</title><link>https://readme.news/wwdc-2026-and-the-platform-that-keeps-its-own-counsel/</link><guid isPermaLink="true">https://readme.news/wwdc-2026-and-the-platform-that-keeps-its-own-counsel/</guid><pubDate>Mon, 08 Jun 2026 09:00:00 +0000</pubDate><description>New OS versions, more on-device model surface, and a developer relationship that remains complicated.</description><content:encoded><![CDATA[<p>Apple's developer conference ran this week. The pattern of the last few years continues: strong <a class="xref" href="/pixel-10-and-the-on-device-model-as-a-platform-feature/" title="Pixel 10 and the on-device model as a platform feature">on-device</a> capability, tight platform integration, and a set of platform policy questions that are being settled in courtrooms rather than on stage.</p>
<h2 id="the-on-device-strategy-holding">the on-device strategy, holding<a class="anchor" href="#the-on-device-strategy-holding" aria-label="link to this section">#</a></h2>
<p>Apple's position has been consistent and, I think, correct for their situation:</p>
<ul><li>A <a class="xref" href="/haiku-45-and-the-collapsing-cost-of-good-enough/" title="Haiku 4.5 and the collapsing cost of good-enough">small model</a> on device, free, private, offline, exposed to third-party apps through a constrained system API.</li><li>A larger model available for the cases the small one cannot handle.</li><li>Routing handled by the system.</li><li>Privacy as the differentiating property rather than raw capability.</li></ul>
<p>That is not going to win a benchmark comparison and it was never trying to. It is going to win on the axis where Apple competes, which is a coherent product where the default behavior is the one most users want.</p>
<p>For developers, the practical implication has not changed: <strong>design features so the small model handles the common case.</strong></p>
<p>Concretely — the 90% that the on-device model can do is free, instant, and works on a plane. The 10% that needs escalation costs money and needs a network. Getting that split right is the engineering, and it is a different skill from prompt design.</p>
<h2 id="the-swift-trajectory">the Swift trajectory<a class="anchor" href="#the-swift-trajectory" aria-label="link to this section">#</a></h2>
<p>Swift continues its expansion beyond Apple platforms — server-side, embedded, <a class="xref" href="/the-component-model-and-the-plugin-problem/" title="The component model and the plugin problem">WebAssembly</a>, <a class="xref" href="/cross-platform-is-a-promise-you-make-to-your-budget/" title="Cross-platform is a promise you make to your budget">cross-platform</a> tooling. The concurrency model's strict checking continues to produce a language that catches data races at compile time, which is a genuinely valuable property that the migration cost has made contentious.</p>
<p>The honest state: strict concurrency checking is correct and it is a real migration burden for existing codebases, and the <a class="xref" href="/error-messages-are-a-user-interface/" title="Error messages are a user interface">error messages</a> when you get it wrong are still harder to act on than they should be.</p>
<p>Swift is a better language than it gets credit for outside the Apple ecosystem and its adoption outside that ecosystem remains limited, mostly for reasons of tooling gravity rather than language quality.</p>
<h2 id="the-platform-policy-question">the platform policy question<a class="anchor" href="#the-platform-policy-question" aria-label="link to this section">#</a></h2>
<p>The regulatory pressure on app distribution and payments continues, differently in different jurisdictions, with the result that the rules now vary by region in ways that are genuinely confusing for developers to comply with.</p>
<p>I do not have a clean position on the substance. What I will say is the practical part:</p>
<p><strong>If you ship an app with any monetization, you now have jurisdiction-specific compliance work</strong>, and the rules are still moving. Budget for it, watch the changes, and do not assume a policy you read last year is current.</p>
<p>The broader observation, which is not about Apple specifically: the era of one global set of platform rules is over. Every major platform now operates under different obligations in the EU, the US, and several other markets, and that fragmentation is going to increase rather than resolve.</p>
<h2 id="the-things-worth-actually-adopting">the things worth actually adopting<a class="anchor" href="#the-things-worth-actually-adopting" aria-label="link to this section">#</a></h2>
<p>Filtering out the keynote, the items that will matter to a working developer:</p>
<p><strong>Anything that reduces app size or launch time.</strong> These are the metrics that correlate with retention and they get the least stage time.</p>
<p><strong>Testing and debugging improvements in the toolchain.</strong> Consistently the most valuable and least covered part of every WWDC.</p>
<p><strong>Deprecation notices.</strong> The most important announcements at any platform conference are the ones about what is going away, and they are never in the keynote. Read the release notes.</p>
<h2 id="the-honest-assessment">the honest assessment<a class="anchor" href="#the-honest-assessment" aria-label="link to this section">#</a></h2>
<p>Apple ships coherent, well-integrated platforms with genuinely good on-device capability and a privacy posture that is a real product differentiator rather than only marketing.</p>
<p>It also operates the developer relationship with less flexibility than any comparable platform, and the regulatory environment is the mechanism by which that is being adjusted rather than any change of heart.</p>
<p>Both of those have been true for a decade and neither is changing this year.</p>]]></content:encoded></item><item><title>Python 3.15 and free-threading's second act</title><link>https://readme.news/python-315-and-free-threadings-second-act/</link><guid isPermaLink="true">https://readme.news/python-315-and-free-threadings-second-act/</guid><pubDate>Mon, 11 May 2026 09:00:00 +0000</pubDate><description>The GIL-free build is officially supported and the ecosystem work is the actual story. A progress report.</description><content:encoded><![CDATA[<p>Python 3.15 is in beta. The headline features are incremental. The story worth tracking is the free-threading migration, which is now in the phase where it succeeds or stalls based on ecosystem work rather than on CPython.</p>
<h2 id="where-free-threading-actually-stands">where free-threading actually stands<a class="anchor" href="#where-free-threading-actually-stands" aria-label="link to this section">#</a></h2>
<p>The GIL-free build has been officially supported since 3.14. The question was never whether CPython could do it — it demonstrably can — but whether the ecosystem would follow.</p>
<p><strong>What has gone well:</strong></p>
<p>The major numerical and data libraries did the work. NumPy, and the array and dataframe ecosystem around it, ship free-threaded wheels. That was the critical path, because those libraries are the reason a large fraction of Python's CPU-bound workload exists.</p>
<p>Single-threaded performance in the free-threaded build has improved substantially since 3.13. The gap against the default build has narrowed to something most applications would not notice.</p>
<p>Build infrastructure caught up. Producing free-threaded wheels is now a configuration change rather than a project.</p>
<p><strong>What has gone slowly:</strong></p>
<p>The long tail. Thousands of packages with C extensions have not been audited, and most of them will not be until someone hits a problem.</p>
<p>The subtler issue: <strong>pure-Python code that was accidentally thread-safe.</strong> The GIL made many operations effectively atomic. Code written under that assumption — a dictionary mutated from multiple threads, a counter incremented without a lock — worked by accident. Under free-threading it does not.</p>
<p>Nobody knows how much library code depends on this, because nobody ever had to think about it. It will be discovered one race condition at a time.</p>
<h2 id="the-practical-guidance">the practical guidance<a class="anchor" href="#the-practical-guidance" aria-label="link to this section">#</a></h2>
<p><strong>Do not switch production workloads casually.</strong> The performance win only exists for CPU-bound multithreaded work. If your workload is I/O-bound, <code>asyncio</code> was already handling it and free-threading gives you nothing.</p>
<p><strong>Do test your libraries against it.</strong> The compatibility work has to happen and it happens when people run into problems and report them. If you maintain a package with a C extension, this is your work to do.</p>
<p><strong>Do use it for the workloads it is for.</strong> Data processing, image and signal processing, simulation, anything numeric that currently uses <code>multiprocessing</code> and pays serialization and memory-duplication costs.</p>
<p>The migration from <code>multiprocessing</code> to threads for those workloads is frequently dramatic — not just faster, but simpler, because you delete the serialization layer.</p>
<h2 id="the-other-thing-in-315">the other thing in 3.15<a class="anchor" href="#the-other-thing-in-315" aria-label="link to this section">#</a></h2>
<p><strong>Subinterpreters continue maturing.</strong> <code>concurrent.interpreters</code> gives you isolated interpreter instances in one process, with much lower overhead than processes and much stronger isolation than threads.</p>
<p>This is the underrated option. For a workload where you want parallelism and do not want shared mutable state — which is most workloads — subinterpreters give you the process model's safety at closer to the thread model's cost.</p>
<p>It is the right default for a lot of the cases people currently reach for <code>multiprocessing</code> for, and it does not require any of the thread-safety auditing that free-threading does.</p>
<p><strong><a class="xref" href="/error-messages-are-a-user-interface/" title="Error messages are a user interface">Error messages</a> continue improving</strong>, which has been a multi-release trend and has made Python meaningfully friendlier for people learning it.</p>
<h2 id="the-assessment">the assessment<a class="anchor" href="#the-assessment" aria-label="link to this section">#</a></h2>
<p>Python is executing a decade-long project to stop being single-threaded, and it is doing it without breaking the ecosystem, on an opt-in basis, with a fallback.</p>
<p>That is the right way to do it and it is slower than anyone wants. The alternative — a hard break, a Python 4 — would have been faster and would have cost the community what Python 3 cost it, which nobody has the appetite for.</p>
<p>Ask again in three years. The trajectory is good and the destination is not in doubt; only the timeline is.</p>]]></content:encoded></item><item><title>Pattern matching arrives everywhere at once</title><link>https://readme.news/pattern-matching-arrives-everywhere-at-once/</link><guid isPermaLink="true">https://readme.news/pattern-matching-arrives-everywhere-at-once/</guid><pubDate>Mon, 23 Mar 2026 09:00:00 +0000</pubDate><description>Java, Python, C#, and JavaScript are all converging on the same feature from ML languages, thirty years late.</description><content:encoded><![CDATA[<p>Four mainstream languages have shipped or are shipping structural pattern matching in the last several years. The feature is forty years old and came from ML and Haskell, and it is worth understanding why it took this long and what it actually buys.</p>
<h2 id="what-it-is">what it is<a class="anchor" href="#what-it-is" aria-label="link to this section">#</a></h2>
<p>Destructuring and dispatch in one construct: match a value against a shape, bind its parts to names, and branch on which shape matched.</p>
<p>Java:</p>
<div class="code"><span class="code-lang">java</span><pre><code class="lang-java">String describe(Shape s) {
    return switch (s) {
        case Circle c when c.radius() &gt; 100 -&gt; "big circle";
        case Circle c                        -&gt; "circle r=" + c.radius();
        case Rect(int w, int h) when w == h  -&gt; "square " + w;
        case Rect(int w, int h)              -&gt; "rect " + w + "x" + h;
    };
}</code></pre></div>
<p>Python:</p>
<div class="code"><span class="code-lang">python</span><pre><code class="lang-python">match command.split():
    case ["move", direction]:
        move(direction)
    case ["take", *items] if items:
        for item in items: take(item)
    case _:
        unknown()</code></pre></div>
<p>Both are doing the same thing: testing structure, extracting components, and binding them, in one expression.</p>
<h2 id="why-it-matters-more-than-it-looks">why it matters more than it looks<a class="anchor" href="#why-it-matters-more-than-it-looks" aria-label="link to this section">#</a></h2>
<p><strong>Exhaustiveness checking.</strong> This is the actual payoff and it is easy to miss.</p>
<p>If your <a class="xref" href="/type-systems-and-the-cost-of-being-right/" title="Type systems and the cost of being right">type system</a> knows the complete set of possible shapes — a sealed hierarchy in Java, a union type in TypeScript, an enum in Rust — the compiler can verify that your match handles all of them.</p>
<p>Which means: <strong>add a new case to your data model, and the compiler tells you every place that needs updating.</strong></p>
<p>That is a qualitatively different maintenance experience. Without it, adding a new variant means grepping for every switch statement and hoping. With it, the build fails until you have handled it everywhere.</p>
<p>This is the single largest practical benefit of algebraic data types and most discussion of pattern matching skips it entirely in favor of syntax.</p>
<p><strong>It replaces the visitor pattern.</strong> An entire design pattern existed because object-oriented languages could not dispatch on structure. That pattern is a significant amount of ceremony — an <a class="xref" href="/the-interface-is-the-product/" title="The interface is the product">interface</a>, an accept method on every node, a visitor implementation — to express what pattern matching says in five lines.</p>
<p>If you have a visitor in your codebase and your language now has pattern matching, you can delete a lot of code.</p>
<p><strong>It makes illegal states harder to express.</strong> Combined with sum types, you can model "this is one of exactly these things" and have the compiler enforce it, rather than a class with six nullable fields where only certain combinations are valid.</p>
<h2 id="the-caveats">the caveats<a class="anchor" href="#the-caveats" aria-label="link to this section">#</a></h2>
<p><strong>Python's <code>match</code> is not exhaustiveness-checked.</strong> It is a runtime construct. Without a sealed type system there is nothing to check against. Type checkers can do some of this with <code>Literal</code> and <code>Union</code> types and it is not enforced by the language.</p>
<p>That makes Python's version considerably less valuable than Java's or Rust's — it is nicer destructuring syntax rather than a correctness tool.</p>
<p><strong>Capture semantics surprise people.</strong> In Python, a bare name in a pattern <em>binds</em>, it does not compare. This is the number one confusion:</p>
<div class="code"><span class="code-lang">python</span><pre><code class="lang-python">match color:
    case RED:      # binds `color` to RED — matches everything!
        ...
    case Color.RED:  # this compares
        ...</code></pre></div>
<p>A dotted name compares. A bare name binds. Everybody gets this wrong once.</p>
<p><strong>Overuse.</strong> Pattern matching is satisfying and it is possible to write a match statement where an if would be clearer. If you are matching on one condition, use an if.</p>
<h2 id="the-trend-it-is-part-of">the trend it is part of<a class="anchor" href="#the-trend-it-is-part-of" aria-label="link to this section">#</a></h2>
<p>Pattern matching is one of several ideas migrating from functional languages into the mainstream, along with immutability by default, expression-oriented syntax, <code>Option</code>/<code>Result</code> types instead of null and exceptions, and real sum types.</p>
<p>Each of these took decades to cross over. The reason is not that the ideas were unknown — it is that mainstream languages have compatibility obligations that make adding a feature to an existing type system extraordinarily hard, and it takes a generation of language designers who grew up with the ideas to do the work.</p>
<p>The next ones in the queue, visibly: effect systems, better concurrency abstractions in the type system, and some form of ownership tracking outside of Rust.</p>
<p>Give it ten years.</p>]]></content:encoded></item><item><title>Type systems and the cost of being right</title><link>https://readme.news/type-systems-and-the-cost-of-being-right/</link><guid isPermaLink="true">https://readme.news/type-systems-and-the-cost-of-being-right/</guid><pubDate>Mon, 16 Feb 2026 09:00:00 +0000</pubDate><description>Every type system decision is a trade between what you can prove and what you can express. Here&#x27;s how to think about where to sit.</description><content:encoded><![CDATA[<p>Arguments about static versus dynamic typing are mostly tribal at this point, which is a shame, because the actual engineering question is interesting and has a real answer that depends on your situation.</p>
<h2 id="what-a-type-system-buys">what a type system buys<a class="anchor" href="#what-a-type-system-buys" aria-label="link to this section">#</a></h2>
<p><strong>Errors found before running.</strong> The obvious one. A type error is a bug that cannot reach production, and the earlier a bug is found the cheaper it is.</p>
<p><strong>Documentation that cannot rot.</strong> A signature is documentation the compiler checks. Comments lie; types cannot.</p>
<p><strong>Refactoring confidence.</strong> Rename a field, and the compiler enumerates every place that breaks. In a dynamic language you grep and hope.</p>
<p><strong>Editor intelligence.</strong> Completion, go-to-definition, and find-references are functions of type information. The difference in tooling quality between typed and untyped codebases is enormous and underweighted in these arguments.</p>
<p><strong>Machine-checked verification.</strong> The one that got more valuable recently. If generated code has to pass a typechecker, that is verification you did not have to do by reading.</p>
<h2 id="what-it-costs">what it costs<a class="anchor" href="#what-it-costs" aria-label="link to this section">#</a></h2>
<p><strong>Expressiveness constraints.</strong> Some correct programs are rejected. Every type system rejects programs that would have worked, and the more powerful the system, the more elaborate the workarounds for the cases it cannot express.</p>
<p><strong>Ceremony.</strong> Type annotations, generic parameters, explicit conversions, <a class="xref" href="/the-interface-is-the-product/" title="The interface is the product">interface</a> declarations. Real typing overhead on the keyboard and real reading overhead on the page.</p>
<p><strong>Compile time.</strong> Typechecking is work. In a large codebase with a sophisticated type system, it can be a lot of work.</p>
<p><strong>A learning curve that is genuinely steep at the top end.</strong> Higher-kinded types, variance, existentials, effect systems. These are powerful and they are also the reason some codebases are unapproachable.</p>
<h2 id="the-honest-heuristic">the honest heuristic<a class="anchor" href="#the-honest-heuristic" aria-label="link to this section">#</a></h2>
<p><strong>Script under 200 lines that you will run once:</strong> dynamic. The type system's benefit does not have time to accrue.</p>
<p><strong>Anything with more than one author:</strong> static. The documentation and refactoring benefits scale directly with the number of people who need to understand code they did not write.</p>
<p><strong>Anything that will live more than a year:</strong> static. You are the other author, in eight months, with no memory.</p>
<p><strong>Library or API consumed by others:</strong> static, and be maximally explicit. Your types are the contract.</p>
<p><strong>Exploratory data work:</strong> dynamic, usually. The shapes change constantly and the consequence of an error is a wrong number in a notebook, which you will see.</p>
<p><strong>Anything where a bug is expensive:</strong> the strongest type system you can afford, and consider a language with real algebraic data types so you can make illegal states unrepresentable.</p>
<h2 id="the-gradual-typing-reality">the gradual typing reality<a class="anchor" href="#the-gradual-typing-reality" aria-label="link to this section">#</a></h2>
<p>Most of the interesting action is in gradual systems — TypeScript, Python's type hints, Ruby's type tooling, PHP's declarations.</p>
<p>These are genuinely useful and they have a specific failure mode worth naming: <strong>the guarantees are only as strong as the least-typed part of the path.</strong></p>
<p>A TypeScript codebase with <code>any</code> scattered through it, or with hand-written type declarations for an untyped dependency that are subtly wrong, provides confidence that is not backed by anything. That is worse than no types, because you trust it.</p>
<p>The practical discipline:</p>
<ul><li><strong>Turn on strict mode.</strong> <code>strict: true</code> in TypeScript, <code>strict</code> in mypy. The non-strict modes permit exactly the holes that make the system unreliable.</li><li><strong>Ban <code>any</code> at the boundary.</strong> Runtime validation at every point where external data enters — API responses, user input, environment variables, database rows. A parsed-and-validated type is a guarantee; a cast is a wish.</li></ul>
<div class="code"><span class="code-lang">ts</span><pre><code class="lang-ts">// this is a lie
const user = await res.json() as User;

// this is a guarantee
const user = UserSchema.parse(await res.json());</code></pre></div>
<ul><li><strong>Type the boundaries first.</strong> Interior code benefits less. External interfaces benefit most.</li></ul>
<h2 id="the-thing-that-changed">the thing that changed<a class="anchor" href="#the-thing-that-changed" aria-label="link to this section">#</a></h2>
<p>Type systems got more valuable in the last two years for a reason that has nothing to do with the traditional arguments.</p>
<p>If a substantial share of your code is generated, then every constraint the compiler can check is verification you do not have to perform by reading. And reading is now the bottleneck.</p>
<p>A generated function that typechecks against a well-specified interface has already been verified against one important class of error. A generated function in an untyped language has been verified against nothing.</p>
<p>That is a genuinely new argument and it has moved teams that were unmoved by twenty years of the old ones.</p>]]></content:encoded></item><item><title>Go and the case for boring</title><link>https://readme.news/go-and-the-case-for-boring/</link><guid isPermaLink="true">https://readme.news/go-and-the-case-for-boring/</guid><pubDate>Wed, 04 Feb 2026 09:00:00 +0000</pubDate><description>A language that ships small releases, breaks nothing, and refuses features. Its detractors and its users are describing the same thing.</description><content:encoded><![CDATA[<p>Go's release notes are the least exciting in the industry, and its adoption in infrastructure software is close to total. Those two facts are the same fact.</p>
<h2 id="the-design-decision-that-defines-it">the design decision that defines it<a class="anchor" href="#the-design-decision-that-defines-it" aria-label="link to this section">#</a></h2>
<p>Go's compatibility promise is stronger than almost any language's: code written against Go 1 continues to compile and run. That has held since 2012.</p>
<p>Combined with a deliberate resistance to adding features, the effect is that Go code from 2015 reads like Go code from today. There is one way to do most things. There is no dialect. There is no "modern Go" versus "legacy Go."</p>
<p>For a language used to write infrastructure that must be maintained for a decade by rotating teams, that property is worth more than any individual feature.</p>
<h2 id="what-the-criticism-gets-right">what the criticism gets right<a class="anchor" href="#what-the-criticism-gets-right" aria-label="link to this section">#</a></h2>
<p><strong>Error handling is verbose.</strong> <code>if err != nil</code> is a large share of the lines in a lot of Go code. The proposals to fix it have all been rejected, repeatedly, and the reasoning — that explicit error handling at every call site is a feature, not a bug — is coherent and is genuinely annoying to live with.</p>
<p><strong>Generics arrived late and are limited.</strong> No sum types. No method type parameters. The type inference gives up in cases where you expect it to work. Generics in Go are useful and they are clearly a retrofit.</p>
<p><strong>The nil <a class="xref" href="/the-interface-is-the-product/" title="The interface is the product">interface</a> trap.</strong> A nil pointer stored in an interface produces a non-nil interface. This bites everyone once and it is a genuine wart.</p>
<p><strong>No sum types.</strong> The single most requested feature. Modeling "this is one of three things" in Go requires an interface with unexported methods and a type switch, which is neither exhaustive-checked nor pleasant.</p>
<h2 id="what-the-criticism-gets-wrong">what the criticism gets wrong<a class="anchor" href="#what-the-criticism-gets-wrong" aria-label="link to this section">#</a></h2>
<p>The complaints are almost all about <em>expressiveness</em>, and expressiveness is not the thing Go optimizes for. It optimizes for a large team maintaining a large codebase over a long time, with new people joining constantly.</p>
<p>Under that objective function:</p>
<ul><li><strong>Verbosity is fine</strong> if it makes the code obvious without context.</li><li><strong>Few features is good</strong>, because every feature is a dialect someone will write in and someone else will have to read.</li><li><strong>Fast compilation</strong> matters enormously, because it determines the <a class="xref" href="/why-your-tests-are-slow/" title="Why your tests are slow">feedback loop</a> and therefore how people work.</li><li><strong>A good standard library</strong> means fewer dependencies, which means fewer supply chain problems and fewer upgrade treadmills.</li><li><strong><code>gofmt</code></strong> ended formatting arguments permanently, which saved the industry an incalculable number of hours.</li></ul>
<p>Go is not trying to be a good language for writing clever code. It is trying to be a good language for reading code someone else wrote three years ago while you are on call at 3 a.m.</p>
<p>It is very good at that.</p>
<h2 id="the-concurrency-story-honestly">the concurrency story, honestly<a class="anchor" href="#the-concurrency-story-honestly" aria-label="link to this section">#</a></h2>
<p>Goroutines and channels were revolutionary in 2012 and are now table stakes. Several languages have equivalent or better models.</p>
<p>What has held up: the scheduler is excellent, the tooling around concurrency — the race detector especially — is best in class, and <code>context</code> for cancellation propagation is a good design that most ecosystems lack an equivalent of.</p>
<p>What has not: channels are overused by people who learned Go from the tour. Most Go code should use a mutex and a plain function call. "Do not communicate by sharing memory; share memory by communicating" is good advice that has produced a lot of unnecessarily channel-based code.</p>
<h2 id="where-it-wins">where it wins<a class="anchor" href="#where-it-wins" aria-label="link to this section">#</a></h2>
<p>Network services, CLI tools, infrastructure daemons, anything deployed as a single static binary, anything with a large team.</p>
<p>The ecosystem effect is real: a very large fraction of the cloud-native stack is Go. If you work in infrastructure, you will read Go whether or not you write it.</p>
<h2 id="where-it-does-not">where it does not<a class="anchor" href="#where-it-does-not" aria-label="link to this section">#</a></h2>
<p>Anything needing precise memory control. Anything numeric or scientific, where the ecosystem is thin and the language does not help. Anything where expressiveness genuinely pays — a compiler, a complex domain model, anything with rich algebraic structure.</p>
<h2 id="the-thing-worth-stealing">the thing worth stealing<a class="anchor" href="#the-thing-worth-stealing" aria-label="link to this section">#</a></h2>
<p>Whatever language you use: Go's compatibility promise, its formatting standardization, and its resistance to feature accumulation are choices, not accidents.</p>
<p>Most projects would benefit from adopting the spirit of all three. Pick one way to do things. Automate the formatting argument out of existence. Say no to the feature that adds a second way to express something that already has one.</p>
<p>That is available to you regardless of your language, and it is most of what makes Go codebases pleasant.</p>]]></content:encoded></item><item><title>Virtual threads, two years on</title><link>https://readme.news/virtual-threads-two-years-on/</link><guid isPermaLink="true">https://readme.news/virtual-threads-two-years-on/</guid><pubDate>Thu, 29 Jan 2026 09:00:00 +0000</pubDate><description>Java&#x27;s concurrency change looked incremental and turned out to reshape how services get written. A field report.</description><content:encoded><![CDATA[<p>Virtual threads went final in Java 21 and are now running in production across a large number of services. Enough time has passed to say something more useful than "it is fast."</p>
<h2 id="what-they-are">what they are<a class="anchor" href="#what-they-are" aria-label="link to this section">#</a></h2>
<p>A virtual thread is a thread managed by the JVM rather than the operating system. Creating one costs on the order of a few hundred bytes rather than a megabyte of stack. Blocking one parks it and frees the underlying carrier thread rather than blocking an OS thread.</p>
<p>The consequence: you can have millions of them, and blocking is no longer expensive.</p>
<div class="code"><span class="code-lang">java</span><pre><code class="lang-java">try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    for (var request : requests) {
        executor.submit(() -&gt; handle(request));   // one thread per request, fine
    }
}</code></pre></div>
<h2 id="why-this-mattered-more-than-it-looked">why this mattered more than it looked<a class="anchor" href="#why-this-mattered-more-than-it-looked" aria-label="link to this section">#</a></h2>
<p>The Java ecosystem spent a decade building around the assumption that threads are expensive. That assumption produced:</p>
<ul><li>Thread pools everywhere, with sizing that nobody understood.</li><li>Reactive programming frameworks, with their entirely separate ecosystem of types, operators, and debugging misery.</li><li><code>CompletableFuture</code> chains that are unreadable after three composed steps.</li><li>Stack traces that told you nothing because the actual work happened on a different thread than the code you wrote.</li></ul>
<p>All of that complexity existed to avoid blocking. Virtual threads make blocking cheap, which removes the reason for all of it.</p>
<p><strong>The single biggest practical win is debugging.</strong> A virtual thread's stack trace is the actual call stack of the actual logical operation. Compare that to a reactive pipeline, where the stack trace is the scheduler and the real causal chain is reconstructed by squinting at operator names.</p>
<p>Teams that migrated report this as the thing they did not expect and would not give up.</p>
<h2 id="the-pitfalls-that-are-real">the pitfalls that are real<a class="anchor" href="#the-pitfalls-that-are-real" aria-label="link to this section">#</a></h2>
<p><strong>Pinning.</strong> A virtual thread inside a <code>synchronized</code> block cannot unmount from its carrier thread — it pins it. If that code then blocks, you have consumed an OS thread for the duration, and with a small carrier pool you can deadlock.</p>
<p>The fix is <code>ReentrantLock</code> instead of <code>synchronized</code>. Later JDK releases reduced the pinning cases substantially; library code you do not control can still do it.</p>
<p>Detect it:</p>
<div class="code"><pre><code>-Djdk.tracePinnedThreads=full</code></pre></div>
<p>Run that in a load test before you go to production. Every migration finds something.</p>
<p><strong>ThreadLocal at scale.</strong> A <code>ThreadLocal</code> with a million threads is a million copies. Libraries that cache expensive objects per-thread — some serializers, some date formatters, some connection helpers — become a memory problem.</p>
<p>Scoped values are the intended replacement and are the right answer for request-scoped context.</p>
<p><strong>Unbounded concurrency.</strong> Thread pools were an accidental rate limiter. Remove them and you can now issue ten thousand concurrent requests to a downstream service that handles two hundred. You have moved the failure from your service to theirs, which is worse.</p>
<p>You still need bounded concurrency. It just belongs at the resource — a semaphore around the downstream call — rather than as a global thread pool.</p>
<p><strong>Connection pools.</strong> Your database connection pool is sized for a thread-pool world. It is now the bottleneck, and the right size is a different calculation. This surprises people.</p>
<h2 id="the-migration-advice">the migration advice<a class="anchor" href="#the-migration-advice" aria-label="link to this section">#</a></h2>
<p><strong>Do not rewrite anything.</strong> Virtual threads work with the code you have. Switch the executor, run your load tests, look for pinning, size your connection pools, done.</p>
<p><strong>Do not migrate reactive code that works.</strong> A well-functioning reactive service should be left alone. The migration cost is real and the benefit is maintainability, which is a slow-compounding return.</p>
<p><strong>Do migrate new services.</strong> Writing a new service with virtual threads and plain blocking code is dramatically simpler than the reactive equivalent, and simpler code is the whole point.</p>
<h2 id="the-broader-lesson">the broader lesson<a class="anchor" href="#the-broader-lesson" aria-label="link to this section">#</a></h2>
<p>An enormous amount of accumulated architectural complexity existed to work around one runtime limitation. Removing the limitation made the complexity obsolete overnight — but only for code written after, because nobody rewrites working systems.</p>
<p>That is worth remembering the next time you build elaborate machinery around a platform constraint. Ask how likely the constraint is to persist, and whether the machinery will outlive it.</p>
<p>Usually it will, sitting in your codebase, load-bearing and pointless.</p>]]></content:encoded></item><item><title>Rust's eleventh year and the shape of a finished language</title><link>https://readme.news/rusts-eleventh-year-and-the-shape-of-a-finished-language/</link><guid isPermaLink="true">https://readme.news/rusts-eleventh-year-and-the-shape-of-a-finished-language/</guid><pubDate>Tue, 20 Jan 2026 09:00:00 +0000</pubDate><description>Six-week releases, no breakage, and a backlog that is finally shorter than it was. What&#x27;s left is the hard part.</description><content:encoded><![CDATA[<p>Another <a class="xref" href="/rusts-six-week-metronome-seven-years-on/" title="Rust&#x27;s six-week metronome, seven years on">Rust release</a>, another set of const stabilizations and library additions. The release notes have been pleasantly dull for a year, which is the best thing you can say about a language with load-bearing production deployments.</p>
<p>Rather than enumerate, it is worth asking what "finished" would look like and how close this is.</p>
<h2 id="what-got-finished">what got finished<a class="anchor" href="#what-got-finished" aria-label="link to this section">#</a></h2>
<p>The list of "this obviously should work and does not" items has genuinely shrunk. Trait upcasting, let chains, RPIT capture, anonymous pipes, const in more places, SIMD intrinsics, async closures. Each of those was a multi-year open question and each is now just how the language works.</p>
<p>That is the signature of a language moving from the growth phase into the maintenance phase, and it is a good place to be. The compiler is faster than it was. The <a class="xref" href="/error-messages-are-a-user-interface/" title="Error messages are a user interface">error messages</a> remain the best in the industry. The tooling story — cargo, clippy, rustfmt, rust-analyzer — is coherent in a way that most languages never achieve.</p>
<h2 id="what-is-left-honestly">what is left, honestly<a class="anchor" href="#what-is-left-honestly" aria-label="link to this section">#</a></h2>
<p><strong>Async.</strong> Still the sharpest edge. Async traits work. Async closures work. What does not work smoothly: cancellation semantics that do not surprise you, async drop, <code>Send</code> bound propagation through generic async code, and the absence of a standard executor <a class="xref" href="/the-interface-is-the-product/" title="The interface is the product">interface</a> which fragments the ecosystem.</p>
<p>The <code>Pin</code> API remains the piece of Rust that experienced engineers most often describe as genuinely confusing, and the ergonomic improvements have been incremental.</p>
<p>This is the area where Rust is meaningfully harder than it needs to be, and where a new user is most likely to conclude the language is too complicated. It matters because async is how you write network services, and network services are most of software.</p>
<p><strong><a class="xref" href="/rust-189-and-the-long-tail-of-const-generics/" title="Rust 1.89 and the long tail of const generics">Const generic</a> expressions.</strong> <code>[T; N * 2]</code> still needs nightly. The blocker is real — deciding when two type-level expressions are equal is undecidable in general and the team will not ship a rule they cannot explain.</p>
<p><strong>GUI.</strong> Ten-plus years, several serious projects, no consensus. Partly a Rust problem: the borrow checker's relationship with the retained-mode widget tree that every traditional GUI framework uses is genuinely awkward. Partly not a Rust problem: <a class="xref" href="/cross-platform-is-a-promise-you-make-to-your-budget/" title="Cross-platform is a promise you make to your budget">cross-platform</a> GUI is unsolved everywhere.</p>
<p><strong>Compile times.</strong> Better every year. Still slower than Go. Structurally will remain so, because monomorphization and the optimization Rust performs are not free and are the reason the output is fast.</p>
<h2 id="the-adoption-picture-unsentimentally">the adoption picture, unsentimentally<a class="anchor" href="#the-adoption-picture-unsentimentally" aria-label="link to this section">#</a></h2>
<p><strong>Won decisively:</strong> systems programming, CLI tooling, JavaScript build tooling, cryptography implementations, embedded, <a class="xref" href="/the-component-model-and-the-plugin-problem/" title="The component model and the plugin problem">WebAssembly</a> targets, kernel drivers in both Linux and Windows.</p>
<p><strong>Winning slowly:</strong> backend services, where Go's simplicity and compile speed are a real competitor and where the async ergonomics cost is felt daily.</p>
<p><strong>Not winning and probably will not:</strong> application development, data science, scripting. Different constraints, different right answers.</p>
<p>That is a good outcome. A language does not need to win everything, and the places Rust won are the places where memory safety bugs were causing the most damage.</p>
<h2 id="the-thing-to-appreciate">the thing to appreciate<a class="anchor" href="#the-thing-to-appreciate" aria-label="link to this section">#</a></h2>
<p>Eleven years of six-week releases and no broken stable program. Three editions with automated migration and no ecosystem split. A governance structure that has survived real disagreements in public.</p>
<p>For a language that has changed as much as Rust has, that stability record is the actual achievement, and it is the reason people are willing to put infrastructure on it. Features are cheap. Trust is not.</p>
<h2 id="for-someone-deciding-whether-to-learn-it-in-2026">for someone deciding whether to learn it in 2026<a class="anchor" href="#for-someone-deciding-whether-to-learn-it-in-2026" aria-label="link to this section">#</a></h2>
<p>If you write systems software, networking infrastructure, embedded code, or anything where a memory safety bug is a security incident: yes, and you probably already know that.</p>
<p>If you write web applications: the async ergonomics are the thing you will fight, and Go or a good typed scripting language will make you more productive faster. Learn Rust anyway, eventually, because understanding ownership makes you better in every language.</p>
<p>If you tried it in 2021 and bounced off the compile times or the error messages: try again. Both are substantially different now.</p>]]></content:encoded></item><item><title>2025, in order</title><link>https://readme.news/2025-in-order/</link><guid isPermaLink="true">https://readme.news/2025-in-order/</guid><pubDate>Tue, 30 Dec 2025 09:00:00 +0000</pubDate><description>The year in one page: what happened, what mattered, and the three things that will still matter in 2030.</description><content:encoded><![CDATA[<p>A hundred pieces this year. Here is the compressed version.</p>
<h2 id="the-year-in-one-paragraph">the year in one paragraph<a class="anchor" href="#the-year-in-one-paragraph" aria-label="link to this section">#</a></h2>
<p>An open reasoning model under an MIT license repriced the sector in January. Reasoning became a runtime dial rather than a model choice. Coding agents went from <a class="xref" href="/operator-and-the-long-road-to-an-agent-that-can-click/" title="Operator, and the long road to an agent that can click">research preview</a> to standard tooling in about six months and made code review the bottleneck. The npm ecosystem got a self-propagating worm. Two of the largest infrastructure providers had multi-hour outages caused by config, not code. Python removed the GIL, optionally. Rust shipped its largest edition. The frontier models converged and the competition moved to price and distribution.</p>
<h2 id="the-three-things-that-will-still-matter-in-2030">the three things that will still matter in 2030<a class="anchor" href="#the-three-things-that-will-still-matter-in-2030" aria-label="link to this section">#</a></h2>
<p><strong>1. Reasoning as a controllable runtime parameter.</strong></p>
<p>The most durable technical idea of the year. Every provider independently arrived at the same design: the caller decides how much the model thinks, per request.</p>
<p>This is durable because it reflects something true about the problem — the appropriate amount of computation is a property of the task, not the model, and only the caller knows the task. Interfaces that reflect true structure survive. Interfaces that reflect an implementation detail do not.</p>
<p><strong>2. The agent-review bottleneck.</strong></p>
<p>Generation throughput increased dramatically. Verification throughput did not. That gap is the central engineering problem of the next several years, and nobody has a good answer.</p>
<p>Everything downstream follows from it: how teams are structured, what tests are for, what "code review" means, whether we build systems we understand or systems that pass tests. This is not a tooling problem that gets solved with a better diff viewer. It is a fundamental asymmetry between producing and checking, and it shows up in every field where automation outpaced verification.</p>
<p><strong>3. Supply chain trust has no technical fix yet.</strong></p>
<p>Four significant npm incidents this year, escalating in sophistication, ending with a worm. Every one exploited the same structural fact: install-time code execution plus long-lived publishing credentials plus a dependency graph nobody designed.</p>
<p>The controls that work — trusted publishing, no install scripts, version cooldowns, phishing-resistant auth — are known and unevenly adopted. The ecosystems that are structurally safer got that way by design decisions made years ago that cannot be retrofitted cheaply.</p>
<p>This gets worse before it gets better, and it is a solvable problem that we are choosing not to solve at the speed it requires.</p>
<h2 id="the-things-that-felt-big-and-were-not">the things that felt big and were not<a class="anchor" href="#the-things-that-felt-big-and-were-not" aria-label="link to this section">#</a></h2>
<p><strong>Model benchmark leapfrogging.</strong> Every launch claimed the frontier. The differences were within evaluation noise for most real tasks. The benchmark discourse consumed enormous attention and predicted very little about what was useful.</p>
<p><strong>Agent frameworks.</strong> Most of the orchestration layer got absorbed into the models, exactly as function-calling libraries and JSON-repair libraries were absorbed before. The durable layer was never orchestration.</p>
<p><strong>The browser wars, round two.</strong> Everyone shipped a Chromium fork with a model in it. That does not diversify the engine landscape; it diversifies the UI on top of one engine.</p>
<h2 id="the-things-that-felt-small-and-were-not">the things that felt small and were not<a class="anchor" href="#the-things-that-felt-small-and-were-not" aria-label="link to this section">#</a></h2>
<p><strong>LLD as the default linker in Rust.</strong> A default change delivered a build-time improvement to everyone at once that documentation had failed to deliver for years. The general lesson — defaults are the highest-leverage thing a toolchain ships — applies far beyond Rust.</p>
<p><strong>Template strings in Python.</strong> A one-character syntax change that makes the safe path as easy as the unsafe one. If library adoption follows, an entire category of injection vulnerability becomes hard to write. Security features that depend on diligence fail; security features enforced by the <a class="xref" href="/type-systems-and-the-cost-of-being-right/" title="Type systems and the cost of being right">type system</a> work.</p>
<p><strong><a class="xref" href="/claude-sonnet-45-and-the-agent-that-runs-for-thirty-hours/" title="Claude Sonnet 4.5 and the agent that runs for thirty hours">Context editing</a> and memory tools.</strong> Unglamorous plumbing that determines whether long agent runs succeed. The context window was never the constraint. Goal retention was.</p>
<h2 id="the-thing-i-want-to-say-going-into-next-year">the thing I want to say going into next year<a class="anchor" href="#the-thing-i-want-to-say-going-into-next-year" aria-label="link to this section">#</a></h2>
<p>The most valuable skill in 2025 was not prompting, and it will not be in 2026 either.</p>
<p>It was the ability to tell whether something is correct. That skill was always valuable and it was previously bundled with the ability to produce the thing. The bundle has come apart. Producing is cheap now. Checking is not, and checking is what everything rests on.</p>
<p>Every argument this year about what AI does to engineering eventually reduces to that. The people who are getting enormous leverage out of these tools are, without exception, the people who can tell when the output is wrong.</p>
<p>That is not a comforting conclusion for anyone hoping the tools eliminate the need for expertise. It is the actual state of things, and it is worth building your career around.</p>
<p>See you in January.</p>]]></content:encoded></item><item><title>Rust 1.92 closes the year</title><link>https://readme.news/rust-192-closes-the-year/</link><guid isPermaLink="true">https://readme.news/rust-192-closes-the-year/</guid><pubDate>Fri, 12 Dec 2025 09:00:00 +0000</pubDate><description>More const, more stable APIs, and a language whose release notes have become pleasantly dull.</description><content:encoded><![CDATA[<p>Rust 1.92 shipped, closing out a year that included the 2024 edition, let chains, trait upcasting, LLD by default, and a steady march of const stabilizations.</p>
<p>Rather than enumerate this release, here is the year.</p>
<h2 id="what-landed-in-2025">what landed in 2025<a class="anchor" href="#what-landed-in-2025" aria-label="link to this section">#</a></h2>
<p><strong><a class="xref" href="/rust-2024-is-the-largest-edition-since-2018/" title="Rust 2024 is the largest edition since 2018">Rust 2024</a> edition</strong> (1.85, February). RPIT lifetime capture, unsafe attributes, tail expression temporary scope, <code>gen</code> reserved. The largest edition so far and the migration was mostly mechanical.</p>
<p><strong>Trait upcasting</strong> (1.86, April). Eight years open. Deleted a category of boilerplate from every plugin system in the ecosystem.</p>
<p><strong>Anonymous pipes in std</strong> (1.87, May), on the tenth anniversary of 1.0.</p>
<p><strong>Let chains</strong> (1.88, June). The single most requested ergonomic improvement, unblocked by the edition's scope changes.</p>
<p><strong>x86 SIMD stabilizations</strong> (1.89, August). Removed a real competitive disadvantage against C++ for numerical work.</p>
<p><strong>LLD as the default linker on Linux</strong> (1.90, September). Build times improved for everyone, most of whom did not notice a release note.</p>
<p><strong>Continuous const expansion</strong> across every release, moving more computation to compile time.</p>
<h2 id="the-shape-of-the-year">the shape of the year<a class="anchor" href="#the-shape-of-the-year" aria-label="link to this section">#</a></h2>
<p>No single dramatic feature. A lot of things that had been open for years finally closing.</p>
<p>That is what a language looks like when it has passed the phase of adding capability and entered the phase of finishing what it started. The backlog of "this should work and does not" is getting shorter.</p>
<h2 id="what-is-still-open">what is still open<a class="anchor" href="#what-is-still-open" aria-label="link to this section">#</a></h2>
<p><strong>Async.</strong> Async traits work but the ergonomics around lifetimes, <code>Send</code> bounds, and cancellation remain the sharpest edges in the language. Async closures landed this year and help. The full story — async drop, better cancellation semantics, a standard executor <a class="xref" href="/the-interface-is-the-product/" title="The interface is the product">interface</a> — is still ahead.</p>
<p><strong><a class="xref" href="/rust-189-and-the-long-tail-of-const-generics/" title="Rust 1.89 and the long tail of const generics">Const generic</a> expressions.</strong> <code>[T; N * 2]</code> still requires nightly. The blocker is the type-equality question and it is genuinely hard.</p>
<p><strong>GUI.</strong> Ten years of effort, several promising projects, no consensus. This is partly a Rust problem and mostly the fact that <a class="xref" href="/cross-platform-is-a-promise-you-make-to-your-budget/" title="Cross-platform is a promise you make to your budget">cross-platform</a> GUI is unsolved everywhere.</p>
<p><strong>Compile times.</strong> Better every year, still slower than Go, probably always will be for structural reasons.</p>
<h2 id="the-adoption-picture">the adoption picture<a class="anchor" href="#the-adoption-picture" aria-label="link to this section">#</a></h2>
<p>Rust in 2025 is: the default for new systems software, a significant and growing presence in the Linux and Windows kernels, the implementation language for most new JavaScript tooling, and increasingly common in infrastructure that used to be Go or C++.</p>
<p>It is not the default for application development and shows no sign of becoming so. That is fine. It was never the goal and the systems niche is where the memory-safety bugs were.</p>
<h2 id="the-thing-worth-appreciating">the thing worth appreciating<a class="anchor" href="#the-thing-worth-appreciating" aria-label="link to this section">#</a></h2>
<p>Rust ships every six weeks and has never broken a stable program. There has been no Python 3. There has been no C++11-style decade-long adoption lag.</p>
<p>For a language that has changed as much as Rust has in ten years, that is a remarkable engineering and governance achievement, and it is the reason people are willing to build load-bearing infrastructure in it.</p>
<p>Boring release notes are the reward for getting the hard part right.</p>]]></content:encoded></item><item><title>Rust 1.91 and the slow work of shrinking unsafe</title><link>https://readme.news/rust-191-and-the-slow-work-of-shrinking-unsafe/</link><guid isPermaLink="true">https://readme.news/rust-191-and-the-slow-work-of-shrinking-unsafe/</guid><pubDate>Fri, 31 Oct 2025 09:00:00 +0000</pubDate><description>More const, more stable APIs, and an ecosystem that keeps finding ways to need less unsafe code.</description><content:encoded><![CDATA[<p>Rust 1.91 shipped this week with the usual batch of API stabilizations and const improvements. Rather than enumerate them, it is worth looking at the trend they are part of, because it is the most underrated thing about Rust's development.</p>
<h2 id="the-pattern">the pattern<a class="anchor" href="#the-pattern" aria-label="link to this section">#</a></h2>
<p>A large fraction of Rust's per-release API additions exist to let you delete an <code>unsafe</code> block.</p>
<p>Some recent examples across the last year of releases:</p>
<ul><li><code>HashMap::get_disjoint_mut</code> — previously required unsafe or a clumsy dance.</li><li><code>&lt;[T]&gt;::as_chunks</code> — previously a transmute or a manual loop with unchecked indexing.</li><li><code>Vec::extract_if</code> — previously either an allocation or unsafe in-place work.</li><li><code>std::io::pipe</code> — previously platform-specific unsafe FFI.</li><li>Const-stabilized functions across the library — previously a <code>lazy_static</code> or a build script.</li></ul>
<p>None of these are exciting individually. Cumulatively they are the mechanism by which the amount of unsafe code in the average Rust program keeps going down.</p>
<h2 id="why-this-matters-more-than-features">why this matters more than features<a class="anchor" href="#why-this-matters-more-than-features" aria-label="link to this section">#</a></h2>
<p>The value proposition of Rust is memory safety without a garbage collector. Every <code>unsafe</code> block is a hole in that guarantee — a place where the compiler stops checking and a human is asserting correctness.</p>
<p>Studies of real-world Rust code consistently find that most unsafe usage falls into a small number of patterns, and that a majority of those patterns exist because the safe standard library did not offer the operation.</p>
<p>So the library team's strategy is: find the patterns, provide safe equivalents, watch the unsafe count fall. It works, and it is measurable.</p>
<p>The remaining unsafe concentrates in the places where it belongs — FFI, hardware interaction, and hand-optimized data structures — where it is reviewed by people who know what they are doing, in crates that are audited.</p>
<h2 id="the-const-story-specifically">the const story specifically<a class="anchor" href="#the-const-story-specifically" aria-label="link to this section">#</a></h2>
<p>Const stabilization is the other steady drip. Every release moves more functions into <code>const fn</code>, meaning they can run at compile time.</p>
<p>The practical effect is that more computation moves from runtime to compile time, and more static data can be computed rather than hand-written. Lookup tables, parsed configuration, precomputed constants — all of it can now be expressed as code that runs during compilation.</p>
<div class="code"><span class="code-lang">rust</span><pre><code class="lang-rust">const TABLE: [u32; 256] = {
    let mut t = [0u32; 256];
    let mut i = 0;
    while i &lt; 256 { t[i] = crc_entry(i as u8); i += 1; }
    t
};</code></pre></div>
<p>That would have required a build script or a generated file a few years ago.</p>
<h2 id="the-honest-limitations">the honest limitations<a class="anchor" href="#the-honest-limitations" aria-label="link to this section">#</a></h2>
<p><a class="xref" href="/rust-189-and-the-long-tail-of-const-generics/" title="Rust 1.89 and the long tail of const generics">Const generics</a> still cannot do arithmetic in type position without nightly. Async in traits works but has sharp edges around lifetimes and <code>Send</code> bounds. The GUI ecosystem remains unsettled after a decade of attempts.</p>
<p>None of those are getting fixed this release, or probably next release. Rust's development model trades speed for not making mistakes it cannot undo, and the consequence is that the hard problems stay open for a long time.</p>
<p>That is a defensible trade for a language with a stability guarantee, and it is genuinely frustrating if you are blocked on one of them.</p>
<h2 id="the-meta-observation">the meta-observation<a class="anchor" href="#the-meta-observation" aria-label="link to this section">#</a></h2>
<p>If you want to evaluate a language's health, do not look at its feature announcements. Look at whether the amount of dangerous code required to do ordinary things is going up or down.</p>
<p>By that measure Rust is doing better than almost anything else, and it is doing it in increments so small that nobody writes headlines about them.</p>]]></content:encoded></item><item><title>Python 3.14 makes free-threading official</title><link>https://readme.news/python-314-makes-free-threading-official/</link><guid isPermaLink="true">https://readme.news/python-314-makes-free-threading-official/</guid><pubDate>Tue, 07 Oct 2025 09:00:00 +0000</pubDate><description>The GIL-free build graduates from experimental, template strings land, and annotations finally get lazy evaluation.</description><content:encoded><![CDATA[<p>Python 3.14 is out. Three things in it are structurally important and one of them has been thirty years coming.</p>
<h2 id="free-threading-is-officially-supported">free-threading is officially supported<a class="anchor" href="#free-threading-is-officially-supported" aria-label="link to this section">#</a></h2>
<p>The GIL-free build, introduced experimentally in 3.13, is now an officially supported configuration. Not the default — you install <code>python3.14t</code> alongside the standard build — but supported, with a commitment to maintain it.</p>
<p>This is the largest change to Python's execution model since the language existed. The global interpreter lock has meant that CPU-bound Python code cannot use multiple cores in one process, which is why every CPU-bound Python program either uses <code>multiprocessing</code> (with its serialization overhead and memory duplication) or drops into C.</p>
<div class="code"><span class="code-lang">bash</span><pre><code class="lang-bash">python3.14t -c "import sys; print(sys._is_gil_enabled())"
# False</code></pre></div>
<p><strong>The caveats are real and you should read them before getting excited.</strong></p>
<p>Single-threaded performance in the free-threaded build is slower — the reference counting has to be thread-safe, and that costs. The gap has narrowed a lot between 3.13 and 3.14 and it has not closed.</p>
<p>C extensions must be explicitly compatible. Anything using the C API with assumptions about GIL protection needs auditing. The major numerical and data libraries have been doing this work for two years; the long tail has not.</p>
<p>And the hard part: <strong>your Python code is now actually concurrent.</strong> Race conditions that the GIL was accidentally preventing are now possible. Code that was "thread-safe" because bytecode operations were effectively atomic may not be. Nobody knows how much library code depends on this implicitly, because nobody has ever had to know.</p>
<p>Do not switch production workloads casually. Do start testing your libraries against it, because the compatibility work has to happen somewhere.</p>
<h2 id="t-strings">t-strings<a class="anchor" href="#t-strings" aria-label="link to this section">#</a></h2>
<p>PEP 750 lands. <code>t"..."</code> produces a <code>Template</code> with the static parts and the interpolated values separated, rather than a concatenated string.</p>
<div class="code"><span class="code-lang">python</span><pre><code class="lang-python">from string.templatelib import Template
name = input()
query = t"select * from users where name = {name}"
query.strings   # static segments
query.values    # (name,)</code></pre></div>
<p>A library receiving a <code>Template</code> can parameterize the SQL, escape by HTML context, or quote for a shell — correctly, because it can still tell the difference between the template and the data.</p>
<p>The value depends entirely on library adoption. Watch the database drivers.</p>
<h2 id="deferred-annotation-evaluation">deferred annotation evaluation<a class="anchor" href="#deferred-annotation-evaluation" aria-label="link to this section">#</a></h2>
<p>PEP 649. Annotations are no longer evaluated at definition time; they are computed lazily when something asks for them.</p>
<div class="code"><span class="code-lang">python</span><pre><code class="lang-python">def process(items: list[Order]) -&gt; Report:   # Order and Report need not exist yet
    ...</code></pre></div>
<p>This kills <code>from __future__ import annotations</code>, kills most string-quoted forward references, and fixes the circular import problems that plague any codebase using type hints heavily across modules.</p>
<p>It also makes runtime introspection cheaper for the common case where nobody looks at annotations at all.</p>
<h2 id="the-rest">the rest<a class="anchor" href="#the-rest" aria-label="link to this section">#</a></h2>
<ul><li><strong>Multiple interpreters in the standard library</strong> (<code>concurrent.interpreters</code>), giving true isolation with lower overhead than processes.</li><li><strong>A new REPL</strong> with syntax highlighting and better multi-line editing.</li><li><strong><code>except</code> without parentheses</strong> for multiple exception types.</li><li><strong>Substantially better <a class="xref" href="/error-messages-are-a-user-interface/" title="Error messages are a user interface">error messages</a></strong>, continuing a multi-release trend that has made Python meaningfully friendlier to learn.</li></ul>
<h2 id="the-assessment">the assessment<a class="anchor" href="#the-assessment" aria-label="link to this section">#</a></h2>
<p>Python's response to being slow and single-threaded has, for two decades, been "call C." 3.14 is the release where the language stops accepting that as the answer.</p>
<p><a class="xref" href="/python-315-and-free-threadings-second-act/" title="Python 3.15 and free-threading&#x27;s second act">Free-threading</a> plus subinterpreters plus the ongoing specializing-interpreter work is a coherent strategy for making Python a reasonable choice for CPU-bound work. It will take several more releases and a lot of ecosystem effort.</p>
<p>It is genuinely happening, which is more than most people expected five years ago.</p>]]></content:encoded></item><item><title>Rust 1.90 and the linker nobody talks about</title><link>https://readme.news/rust-190-and-the-linker-nobody-talks-about/</link><guid isPermaLink="true">https://readme.news/rust-190-and-the-linker-nobody-talks-about/</guid><pubDate>Fri, 19 Sep 2025 09:00:00 +0000</pubDate><description>LLD becomes the default linker on x86-64 Linux. Link times drop and nobody notices, which is the point.</description><content:encoded><![CDATA[<p>Rust 1.90 makes LLD the default linker for <code>x86_64-unknown-linux-gnu</code>. This is a build-time performance change with no user-facing API and it is one of the more useful things to ship this year.</p>
<h2 id="why-linking-matters">why linking matters<a class="anchor" href="#why-linking-matters" aria-label="link to this section">#</a></h2>
<p>For a large Rust binary, linking is frequently the dominant cost of an incremental build. You change one line, the compiler recompiles one crate quickly, and then the linker spends several seconds stitching together a hundred megabytes of object files and debug information.</p>
<p>GNU <code>ld</code> is old, single-threaded in the parts that matter, and was designed for a different era of binary sizes. <code>lld</code> is parallel, substantially faster, and has been production-ready for years — it is the default on several other platforms already.</p>
<p>Reported improvements vary by project, with the largest gains on debug builds of large dependency trees. Two to five times faster linking is a common range. If your edit-compile-run loop is four seconds and linking was two of them, you feel this every single time.</p>
<h2 id="how-to-check">how to check<a class="anchor" href="#how-to-check" aria-label="link to this section">#</a></h2>
<div class="code"><span class="code-lang">bash</span><pre><code class="lang-bash">cargo build --timings</code></pre></div>
<p>Opens an HTML report showing where time went, including link time as a distinct phase. Most people have never looked at this and are surprised by what it says.</p>
<p>If you were already opting into <code>lld</code> or <code>mold</code> via <code>.cargo/config.toml</code>, nothing changes for you. If you were not — which is most people, because the configuration was obscure and required knowing it existed — you get the improvement for free on upgrade.</p>
<h2 id="the-general-lesson">the general lesson<a class="anchor" href="#the-general-lesson" aria-label="link to this section">#</a></h2>
<p>Defaults are the most impactful thing a toolchain ships.</p>
<p>A faster linker has been available for years. Anyone could have configured it. The instructions were in a blog post. Almost nobody did, because it required knowing the option existed, knowing it was safe, and caring enough to edit a config file.</p>
<p>Changing the default delivers the improvement to everyone at once. That is worth more than a decade of documentation.</p>
<p>This generalizes. If you maintain a tool and you find yourself writing documentation explaining how to enable a better behavior, ask whether the better behavior should be the default. The answer is usually yes, and the reason it is not is usually caution about breaking a small number of unusual setups — which is a real concern that should be handled with a flag to opt <em>out</em>, not a flag to opt in.</p>
<h2 id="the-rest-of-190">the rest of 1.90<a class="anchor" href="#the-rest-of-190" aria-label="link to this section">#</a></h2>
<ul><li><strong>Cargo workspace publishing</strong> — <code>cargo publish --workspace</code> publishes multiple interdependent crates in the correct order. Anyone maintaining a multi-crate project has written a shell script for this. Now you can delete it.</li><li><strong>Demoted target tiers</strong> for several less-used platforms.</li><li><strong><code>x86_64-apple-darwin</code> demoted to tier 2</strong> with host tools still provided, which reflects where Apple hardware has actually gone.</li><li>The usual const stabilizations and API additions.</li></ul>
<h2 id="the-trend-line">the trend line<a class="anchor" href="#the-trend-line" aria-label="link to this section">#</a></h2>
<p>Rust's reputation for slow compilation is the single most cited objection to adopting it, and it has been steadily addressed for years: incremental compilation, parallel front-end work, the new <a class="xref" href="/rust-184-and-the-quiet-rewrite-underneath/" title="Rust 1.84 and the quiet rewrite underneath">trait solver</a>, better <a class="xref" href="/caching-is-the-only-optimization-that-reliably-works/" title="Caching is the only optimization that reliably works">caching</a>, and now the linker.</p>
<p>Compile times are meaningfully better than they were three years ago and still slower than Go. That gap is partly fundamental — monomorphization and the amount of optimization Rust does are not free — and partly still addressable.</p>
<p>Anyone who tried Rust in 2021 and bounced off the build times should try again. The numbers are different now.</p>]]></content:encoded></item><item><title>Rust 1.89 and the long tail of const generics</title><link>https://readme.news/rust-189-and-the-long-tail-of-const-generics/</link><guid isPermaLink="true">https://readme.news/rust-189-and-the-long-tail-of-const-generics/</guid><pubDate>Fri, 08 Aug 2025 09:00:00 +0000</pubDate><description>Inferred array lengths in const generic position, plus x86 SIMD stabilizations that matter for anyone doing math.</description><content:encoded><![CDATA[<p>Rust 1.89 is out. The headline is explicitly inferred const generic arguments — you can now write <code>_</code> where a const generic parameter would go and let inference figure it out.</p>
<div class="code"><span class="code-lang">rust</span><pre><code class="lang-rust">fn frobnicate&lt;const N: usize&gt;(arr: [u8; N]) -&gt; [u8; N] { /* ... */ }

let data = [1, 2, 3, 4];
let out: [u8; _] = frobnicate(data);   // N inferred as 4</code></pre></div>
<p>Previously you either wrote the number, which duplicated information the compiler already had, or restructured to avoid needing it. Small change, removes a real papercut, and it composes well with the growing amount of const-generic code in the ecosystem.</p>
<h2 id="the-simd-stabilizations">the SIMD stabilizations<a class="anchor" href="#the-simd-stabilizations" aria-label="link to this section">#</a></h2>
<p>A large batch of x86 intrinsics stabilized, including AVX-512 families. This matters for a specific and growing population: people writing hand-tuned kernels in Rust for <a class="xref" href="/compression-is-underrated/" title="Compression is underrated">compression</a>, encoding, cryptography, and increasingly ML inference.</p>
<p>Before this, using these intrinsics required nightly. Which meant that a production library wanting AVX-512 paths had to either require nightly — a non-starter for most consumers — or drop to C via FFI.</p>
<p>That was a genuine competitive disadvantage against C++ for numerical work and it is now mostly gone.</p>
<p>The pattern for using them safely:</p>
<div class="code"><span class="code-lang">rust</span><pre><code class="lang-rust">if is_x86_feature_detected!("avx512f") {
    unsafe { fast_path_avx512(data) }
} else if is_x86_feature_detected!("avx2") {
    unsafe { fast_path_avx2(data) }
} else {
    scalar_path(data)
}</code></pre></div>
<p>Runtime detection, multiple implementations, scalar fallback. Tedious and correct. <code>std::simd</code> remains unstable for the portable abstraction, which is the thing most people actually want and which continues to take a long time.</p>
<h2 id="also-in-the-release">also in the release<a class="anchor" href="#also-in-the-release" aria-label="link to this section">#</a></h2>
<ul><li><strong><code>unsafe</code> attributes</strong> get more coverage, continuing the 2024 edition direction.</li><li><strong><code>Result::flatten</code></strong> stabilizes. Small and frequently wanted.</li><li><strong>Cargo</strong> now supports <code>--compile-time-deps</code> for building only what is needed for procedural macros and build scripts, which speeds up some tooling workflows meaningfully.</li><li><strong>i686 targets</strong> raise their baseline CPU requirement, which will affect approximately nobody and will affect them a lot.</li></ul>
<h2 id="the-const-generics-arc-for-context">the const generics arc, for context<a class="anchor" href="#the-const-generics-arc-for-context" aria-label="link to this section">#</a></h2>
<p>Const generics were stabilized in a minimal form in Rust 1.51, four years ago. Since then the feature has been growing capability incrementally: first only integers, then inference improvements, then better <a class="xref" href="/error-messages-are-a-user-interface/" title="Error messages are a user interface">error messages</a>, now this.</p>
<p>The full feature — const generic expressions, where you can write <code>[T; N * 2]</code> — is still unstable and has been "coming soon" for a long time. The blocker is that arbitrary const expressions in type position require deciding when two type-level expressions are equal, which is undecidable in general and requires drawing a careful line.</p>
<p>Rust's approach to this has consistently been: ship the decidable subset, leave the hard part open, do not paint yourself into a corner. It is slow and it has not produced an unsound <a class="xref" href="/type-systems-and-the-cost-of-being-right/" title="Type systems and the cost of being right">type system</a>, which is more than several languages with similar features can say.</p>]]></content:encoded></item><item><title>Rust 1.88 ships let chains, and a decade-old papercut closes</title><link>https://readme.news/rust-188-ships-let-chains-and-a-decade-old-papercut-closes/</link><guid isPermaLink="true">https://readme.news/rust-188-ships-let-chains-and-a-decade-old-papercut-closes/</guid><pubDate>Mon, 30 Jun 2025 09:00:00 +0000</pubDate><description>`if let A = a &amp;&amp; let B = b` finally compiles. Also naked functions and automatic cargo cache cleaning.</description><content:encoded><![CDATA[<p>Rust 1.88 stabilizes let chains in the 2024 edition. If you have written Rust for more than a week you have wanted this.</p>
<h2 id="the-feature">the feature<a class="anchor" href="#the-feature" aria-label="link to this section">#</a></h2>
<p>You can now chain <code>let</code> bindings with boolean conditions in <code>if</code> and <code>while</code>:</p>
<div class="code"><span class="code-lang">rust</span><pre><code class="lang-rust">if let Some(user) = session.user()
    &amp;&amp; user.is_admin()
    &amp;&amp; let Ok(perms) = load_permissions(user.id)
    &amp;&amp; perms.contains(Permission::Delete)
{
    delete_record(id)?;
}</code></pre></div>
<p>Before this you wrote the pyramid:</p>
<div class="code"><span class="code-lang">rust</span><pre><code class="lang-rust">if let Some(user) = session.user() {
    if user.is_admin() {
        if let Ok(perms) = load_permissions(user.id) {
            if perms.contains(Permission::Delete) {
                delete_record(id)?;
            }
        }
    }
}</code></pre></div>
<p>Or you restructured with early returns and helper functions, which is often better anyway but was not always applicable — particularly inside a match arm or a loop body where you cannot return.</p>
<h2 id="the-edition-caveat">the edition caveat<a class="anchor" href="#the-edition-caveat" aria-label="link to this section">#</a></h2>
<p>Let chains require the 2024 edition. This is not arbitrary: the tail-expression temporary scope changes in 2024 are what make the drop semantics of a chained <code>let</code> well-defined. In earlier editions the temporaries created in the chain would live too long and the borrow behavior would be surprising in ways that could not be fixed without a breaking change.</p>
<p>This is a nice demonstration of what editions are actually for. A feature was blocked for years on a semantic problem; the edition boundary let them fix the semantics; the feature shipped.</p>
<h2 id="the-rest-of-the-release">the rest of the release<a class="anchor" href="#the-rest-of-the-release" aria-label="link to this section">#</a></h2>
<p><strong>Naked functions.</strong> <code>#[unsafe(naked)]</code> with a <code>naked_asm!</code> body produces a function with no compiler-generated prologue or epilogue. You need this for interrupt handlers, syscall entry points, and context switching. Previously it required an external crate with fragile assumptions.</p>
<p><strong>Automatic cargo cache cleaning.</strong> Cargo now garbage-collects unused files in its global cache. If you have been running <code>cargo clean</code> on a cron job or watching <code>~/.cargo/registry</code> grow to forty gigabytes, that is handled now.</p>
<p><strong><code>cfg(true)</code> and <code>cfg(false)</code></strong> as boolean literals in configuration predicates. Small, and it removes a genuinely silly workaround people were using.</p>
<p><strong><code>Cell::update</code></strong>, several <code>const</code> stabilizations, and the usual batch of API additions.</p>
<h2 id="the-pattern-in-rusts-release-cadence">the pattern in Rust's release cadence<a class="anchor" href="#the-pattern-in-rusts-release-cadence" aria-label="link to this section">#</a></h2>
<p>Rust ships every six weeks, forever, with no marketing cycle and no "major release." What that produces is a language that improves continuously in small increments, where any given release seems minor and the five-year delta is enormous.</p>
<p>Compare Rust in 2020 — no async traits, no GATs, no let chains, no let-else, no <a class="xref" href="/rust-189-and-the-long-tail-of-const-generics/" title="Rust 1.89 and the long tail of const generics">const generics</a> worth using, a much worse compiler error story — to Rust today. The difference is large and there was never a moment where it happened.</p>
<p>That is a good way to run a language. It is also why "should I learn Rust" gets different answers from people who last tried it at different times, and why the answer today is different from the answer three years ago.</p>]]></content:encoded></item><item><title>WWDC 2025: Liquid Glass, a version number reset, and a local model API</title><link>https://readme.news/wwdc-2025-liquid-glass-a-version-number-reset-and-a-local-model-api/</link><guid isPermaLink="true">https://readme.news/wwdc-2025-liquid-glass-a-version-number-reset-and-a-local-model-api/</guid><pubDate>Tue, 10 Jun 2025 09:00:00 +0000</pubDate><description>Apple renumbers every OS to 26, redesigns everything, and quietly ships the most developer-relevant thing in years.</description><content:encoded><![CDATA[<p>Apple's developer conference delivered a visual redesign, a version-numbering change, and an API that matters more than either.</p>
<h2 id="the-version-reset">the version reset<a class="anchor" href="#the-version-reset" aria-label="link to this section">#</a></h2>
<p>Every OS jumps to 26: iOS 26, macOS 26 Tahoe, watchOS 26, tvOS 26, visionOS 26. Year-based, aligned across platforms, matching the model-year convention.</p>
<p>Genuinely good housekeeping. "Requires iOS 17, macOS 14, watchOS 10" was needlessly hard to reason about. Now it is one number.</p>
<h2 id="liquid-glass">Liquid Glass<a class="anchor" href="#liquid-glass" aria-label="link to this section">#</a></h2>
<p>A system-wide redesign built around translucent, refractive material that reacts to content behind and beneath it. Controls float. Layers have depth. Things bend light.</p>
<p>Reactions split predictably. It is undeniably a strong visual identity and the first genuinely new direction since iOS 7 flattened everything in 2013. It is also, in the first betas, a legibility problem in a lot of contexts — text over a refractive layer over a busy background is exactly the situation typography guidance has warned about for a century.</p>
<p>Apple will iterate through the beta cycle. They always do. Contrast will be raised, blur will be increased, and the shipping version will be about 70% of the demo. That is the normal arc and knowing it saves you from having the argument twice.</p>
<p>For developers: if you use standard controls you get it for free. If you built custom UI, budget real time. Custom navigation bars and tab bars in particular are going to need work.</p>
<h2 id="foundation-models-framework">Foundation Models framework<a class="anchor" href="#foundation-models-framework" aria-label="link to this section">#</a></h2>
<p>Here is the actual news. Apple exposes the on-device model to <a class="xref" href="/pixel-10-and-the-on-device-model-as-a-platform-feature/" title="Pixel 10 and the on-device model as a platform feature">third-party apps</a> via a Swift API, with guided generation, tool calling, and streaming.</p>
<div class="code"><span class="code-lang">swift</span><pre><code class="lang-swift">import FoundationModels

@Generable
struct Recipe {
    @Guide(description: "Dish name") var name: String
    @Guide(.count(3...8)) var ingredients: [String]
    var minutes: Int
}

let session = LanguageModelSession()
let recipe = try await session.respond(to: "A quick pasta dish", generating: Recipe.self)</code></pre></div>
<p>That <code>@Generable</code> macro is the good part. You define a Swift type, the framework constrains decoding so the output is guaranteed to parse into it. No JSON parsing, no retry loop for malformed output, no schema drift between your prompt and your struct. Type safety all the way through.</p>
<p>And it costs nothing per call. No API key, no rate limit, no network, no privacy review. For a small on-device model that is enough for summarization, classification, extraction, and simple generation, that changes the calculus for a huge number of app features that were previously not worth a server bill.</p>
<p>The model is small — roughly 3B parameters — and you should not expect frontier behavior. Expect a good <a class="xref" href="/haiku-45-and-the-collapsing-cost-of-good-enough/" title="Haiku 4.5 and the collapsing cost of good-enough">small model</a> that is free and private, and design features that fit that envelope.</p>
<h2 id="containerization">Containerization<a class="anchor" href="#containerization" aria-label="link to this section">#</a></h2>
<p>A framework for running Linux containers on macOS with each container in its own lightweight VM, open source, with sub-second start times. Docker Desktop on macOS has been a performance complaint for a decade. This is Apple's answer and it is architecturally cleaner: per-container VMs rather than one shared Linux VM.</p>
<h2 id="xcode-26">Xcode 26<a class="anchor" href="#xcode-26" aria-label="link to this section">#</a></h2>
<p>Model integration in the editor with support for multiple providers including Claude, plus a new coding assistant experience. Apple shipping first-party support for a competitor's model inside its own IDE is notable — it means they have concluded the model layer is a component, not a differentiator.</p>
<h2 id="the-read">the read<a class="anchor" href="#the-read" aria-label="link to this section">#</a></h2>
<p>Consumer-facing, this was a design conference. Developer-facing, it was the conference where Apple's <a class="xref" href="/local-first-is-finally-practical/" title="Local-first is finally practical">local-first</a> AI strategy finally produced something you can build on.</p>
<p>The strategy is coherent: small models on device, free and private, with the big stuff handled elsewhere. It is not going to win benchmark comparisons and it was never trying to.</p>]]></content:encoded></item><item><title>Rust 1.87, and the standard library grows an anonymous pipe</title><link>https://readme.news/rust-187-and-the-standard-library-grows-an-anonymous-pipe/</link><guid isPermaLink="true">https://readme.news/rust-187-and-the-standard-library-grows-an-anonymous-pipe/</guid><pubDate>Thu, 15 May 2025 09:00:00 +0000</pubDate><description>`std::io::pipe`, `asm!` jumping to Rust code, and the tenth anniversary of 1.0.</description><content:encoded><![CDATA[<p>Rust 1.87 shipped today, ten years and one day after Rust 1.0. The release is a good one and the anniversary is worth a paragraph at the end.</p>
<h2 id="anonymous-pipes">anonymous pipes<a class="anchor" href="#anonymous-pipes" aria-label="link to this section">#</a></h2>
<p><code>std::io::pipe()</code> gives you a <a class="xref" href="/cross-platform-is-a-promise-you-make-to-your-budget/" title="Cross-platform is a promise you make to your budget">cross-platform</a> anonymous pipe in the standard library:</p>
<div class="code"><span class="code-lang">rust</span><pre><code class="lang-rust">use std::io::pipe;
use std::process::Command;

let (mut reader, writer) = pipe()?;
let mut child = Command::new("ls")
    .stdout(writer.try_clone()?)
    .stderr(writer)
    .spawn()?;

let mut merged = String::new();
reader.read_to_string(&amp;mut merged)?;
child.wait()?;</code></pre></div>
<p>Interleaving stdout and stderr into one stream, in order, has been a recurring annoyance requiring either a platform-specific <code>unsafe</code> block or a dependency. Now it is three lines of <code>std</code>.</p>
<h2 id="asm-can-jump-to-rust"><code>asm!</code> can jump to Rust<a class="anchor" href="#asm-can-jump-to-rust" aria-label="link to this section">#</a></h2>
<p>Inline assembly can now use labels that jump back into Rust code. This is deeply niche and matters enormously to the handful of people writing kernels, bootloaders, and hypervisors in Rust — which, at this point, is a real number of people.</p>
<h2 id="the-smaller-items">the smaller items<a class="anchor" href="#the-smaller-items" aria-label="link to this section">#</a></h2>
<ul><li><strong><code>Vec::extract_if</code> and <code>LinkedList::extract_if</code></strong> — remove and yield elements matching a predicate, in place, without collecting into a temporary.</li><li><strong><code>&lt;[T]&gt;::as_chunks</code></strong> — split a slice into fixed-size arrays, safely, with the remainder returned separately. Useful for everything from SIMD prep to parsing.</li><li><strong><code>i686-pc-windows-gnu</code> demoted</strong> to tier 2 without host tools.</li><li>Numerous <strong><code>const fn</code> stabilizations</strong>, which continue at a steady drip.</li></ul>
<h2 id="ten-years">ten years<a class="anchor" href="#ten-years" aria-label="link to this section">#</a></h2>
<p>Rust 1.0 shipped on 15 May 2015. The stability promise made then has held: code written against 1.0 still compiles, editions handled the syntax evolution without splitting the ecosystem, and there has been no Python 3 moment.</p>
<p>That is the achievement. Not the borrow checker, which is what everybody talks about. The borrow checker is a good idea that several research languages had first. What Rust did that no research language did was ship it with a package manager people liked, a stability guarantee people trusted, and an upgrade path that did not require rewriting anything.</p>
<p>Where it landed in a decade: Linux kernel drivers, Windows kernel components, Android's Bluetooth and media stacks, the core of several CDNs, most new CLI tooling in the JavaScript ecosystem, and a substantial share of new infrastructure software generally.</p>
<p>Where it did not land: application development, mostly. The GUI story is still unsettled after ten years of effort. Async Rust is still the sharpest edge in the language, and the "async traits and lifetime puzzles" complaint from 2020 is only partially resolved.</p>
<p>The honest summary: Rust won the systems niche decisively and did not win general-purpose programming, which is fine, because it was never going to and the systems niche is where the memory-safety bugs were.</p>
<p>Ten more years of the same trajectory and half the internet's infrastructure will be written in it.</p>]]></content:encoded></item><item><title>Rust 1.86 lands trait upcasting after eight years</title><link>https://readme.news/rust-186-lands-trait-upcasting-after-eight-years/</link><guid isPermaLink="true">https://readme.news/rust-186-lands-trait-upcasting-after-eight-years/</guid><pubDate>Thu, 03 Apr 2025 09:00:00 +0000</pubDate><description>You can finally coerce `dyn Sub` to `dyn Super`. Also: `HashMap::get_disjoint_mut`, and a soundness fix worth reading.</description><content:encoded><![CDATA[<p>Rust 1.86 stabilizes trait upcasting, a feature that has been open since 2017 and that everyone who has written a plugin system in Rust has stubbed their toe on.</p>
<h2 id="the-problem-it-fixes">the problem it fixes<a class="anchor" href="#the-problem-it-fixes" aria-label="link to this section">#</a></h2>
<p>Given a supertrait relationship, you could not coerce a trait object of the subtrait to a trait object of the supertrait:</p>
<div class="code"><span class="code-lang">rust</span><pre><code class="lang-rust">trait Animal { fn name(&amp;self) -&gt; String; }
trait Dog: Animal { fn bark(&amp;self); }

fn describe(a: &amp;dyn Animal) { println!("{}", a.name()); }

fn use_dog(d: &amp;dyn Dog) {
    describe(d);   // ERROR before 1.86
}</code></pre></div>
<p>The workaround was a manual <code>as_animal(&amp;self) -&gt; &amp;dyn Animal</code> method on every trait, implemented identically in every impl, existing purely to launder a pointer. Anyone building an ECS, a plugin registry, or any kind of heterogeneous object graph has written that boilerplate.</p>
<p>As of 1.86 the coercion just works. The vtable layout was changed so a subtrait's vtable embeds a pointer to the supertrait's, which is why this took eight years — it is a change to the ABI of trait objects, and getting it right without regressing size or dispatch cost required several redesigns.</p>
<h2 id="the-soundness-fix">the soundness fix<a class="anchor" href="#the-soundness-fix" aria-label="link to this section">#</a></h2>
<p>Also in this release: a long-standing hole around <code>Box&lt;T&gt;</code> in the presence of <code>Deref</code> implementations that could produce aliasing violations under certain generic code. The details are in the release notes and are worth reading if you write <code>unsafe</code>. If you do not write <code>unsafe</code>, this fixed a bug you never knew you could have had.</p>
<h2 id="the-small-ergonomics">the small ergonomics<a class="anchor" href="#the-small-ergonomics" aria-label="link to this section">#</a></h2>
<p><strong><code>HashMap::get_disjoint_mut</code></strong> — get mutable references to multiple distinct keys at once, checked at runtime for disjointness:</p>
<div class="code"><span class="code-lang">rust</span><pre><code class="lang-rust">let [a, b] = map.get_disjoint_mut(["alice", "bob"]);</code></pre></div>
<p>Previously this required either two lookups with an intermediate remove, or <code>RefCell</code>, or an <a class="xref" href="/rust-191-and-the-slow-work-of-shrinking-unsafe/" title="Rust 1.91 and the slow work of shrinking unsafe">unsafe block</a>, all to express something that is obviously fine.</p>
<p><strong><code>{float}::next_down</code> and <code>next_up</code></strong> — the adjacent representable float. Niche, and if you need it you <em>really</em> need it.</p>
<p><strong>Target tier changes</strong> — several targets moved up and down the support tiers, as always. Check if you cross-compile to anything unusual.</p>
<h2 id="the-pattern">the pattern<a class="anchor" href="#the-pattern" aria-label="link to this section">#</a></h2>
<p>Rust's stabilization pace on "obvious" features looks glacial from the outside and the reason is almost always the same: the obvious feature interacts with three other features in a way that produces unsoundness, and the team will not ship unsound.</p>
<p>Trait upcasting is a good example. The naive implementation is easy. The implementation that does not break <code>dyn</code> compatibility rules, does not bloat vtables, does not break existing <code>unsafe</code> code that assumed a layout, and composes correctly with associated types took most of a decade.</p>
<p>You can find that frustrating and still prefer it to the alternative.</p>]]></content:encoded></item><item><title>PEP 750 lands: Python gets template strings</title><link>https://readme.news/pep-750-lands-python-gets-template-strings/</link><guid isPermaLink="true">https://readme.news/pep-750-lands-python-gets-template-strings/</guid><pubDate>Fri, 14 Mar 2025 09:00:00 +0000</pubDate><description>A new `t` prefix produces a Template object instead of a string. It&#x27;s f-strings without the injection vulnerability.</description><content:encoded><![CDATA[<p>PEP 750 has been accepted for <a class="xref" href="/python-314-makes-free-threading-official/" title="Python 3.14 makes free-threading official">Python 3.14</a>. It adds a <code>t</code> string prefix that produces a <code>Template</code> object rather than a <code>str</code>, giving library authors access to the interpolated values before they are stringified.</p>
<p>This is a small feature with a large security implication.</p>
<h2 id="the-problem-it-solves">the problem it solves<a class="anchor" href="#the-problem-it-solves" aria-label="link to this section">#</a></h2>
<p>F-strings are wonderful and they are the most common source of injection bugs in modern Python, because they make the wrong thing effortless:</p>
<div class="code"><span class="code-lang">python</span><pre><code class="lang-python"># this is a SQL injection and it looks completely normal
cursor.execute(f"SELECT * FROM users WHERE name = '{name}'")

# so is this, for HTML
return f"&lt;div&gt;{user_bio}&lt;/div&gt;"</code></pre></div>
<p>The f-string evaluates and concatenates before the function ever sees it. By the time <code>execute</code> gets the string, the distinction between the query template and the user data is gone permanently. There is no way for a library to help you.</p>
<h2 id="what-t-strings-do">what t-strings do<a class="anchor" href="#what-t-strings-do" aria-label="link to this section">#</a></h2>
<p>A t-string does not produce a string. It produces a <code>Template</code> containing the static text segments and the interpolations separately:</p>
<div class="code"><span class="code-lang">python</span><pre><code class="lang-python">from string.templatelib import Template

name = "Robert'); DROP TABLE students;--"
t = t"SELECT * FROM users WHERE name = '{name}'"

type(t)          # &lt;class 'string.templatelib.Template'&gt;
t.strings        # ("SELECT * FROM users WHERE name = '", "'")
t.values         # ("Robert'); DROP TABLE students;--",)</code></pre></div>
<p>Now a library can do the right thing. A SQL driver can turn the static parts into a parameterized query and bind the values. An HTML library can escape each interpolation according to its context — attribute versus text node versus URL — which is something no escaping function can do correctly without knowing where the value landed.</p>
<div class="code"><span class="code-lang">python</span><pre><code class="lang-python"># a hypothetical driver that accepts templates
await conn.execute(t"SELECT * FROM users WHERE name = {name}")
# becomes: SELECT * FROM users WHERE name = $1, with name bound</code></pre></div>
<p>The syntax is identical to an f-string. Format specs and conversions work. Nesting works. The only difference is the prefix and the type.</p>
<h2 id="why-this-is-the-right-design">why this is the right design<a class="anchor" href="#why-this-is-the-right-design" aria-label="link to this section">#</a></h2>
<p>The alternative approaches all failed for the same reason: they required the developer to do extra work at the call site, and developers under deadline do the easy thing.</p>
<p><code>t"..."</code> is exactly as easy as <code>f"..."</code>. It is one character. And crucially, a function that expects a <code>Template</code> will <em>reject</em> an f-string with a type error — so a library can make the safe path the only path, and the unsafe call site becomes a build failure rather than a pentest finding.</p>
<p>That is the property that makes this work. Security features that depend on diligence do not scale. Security features that are enforced by the <a class="xref" href="/type-systems-and-the-cost-of-being-right/" title="Type systems and the cost of being right">type system</a> do.</p>
<h2 id="what-to-watch">what to watch<a class="anchor" href="#what-to-watch" aria-label="link to this section">#</a></h2>
<p>The value of this feature is entirely downstream: it depends on library authors adopting it. Watch for template support in the major database drivers, the templating engines, the shell-command helpers, and the logging libraries.</p>
<p>If that adoption happens over the next couple of release cycles, a whole category of Python vulnerability quietly stops being writable. If it does not, this is a nice piece of syntax nobody uses.</p>
<p>Bet on adoption. The ergonomics are too good and the security argument is too easy to make to a security team.</p>]]></content:encoded></item><item><title>Rust 2024 is the largest edition since 2018</title><link>https://readme.news/rust-2024-is-the-largest-edition-since-2018/</link><guid isPermaLink="true">https://readme.news/rust-2024-is-the-largest-edition-since-2018/</guid><pubDate>Thu, 20 Feb 2025 09:00:00 +0000</pubDate><description>New match ergonomics, `gen` blocks reserved, RPIT lifetime capture changes, and unsafe attributes. Here&#x27;s what will break.</description><content:encoded><![CDATA[<p>Rust 1.85 shipped today and with it the Rust 2024 edition — the third edition since 1.0 and the most substantial one. Editions are opt-in per crate and do not split the ecosystem: a 2015 crate and a 2024 crate link together fine. But migrating your own code will surface real changes.</p>
<p>Run <code>cargo fix --edition</code> first. It handles most of it. Here is what it cannot.</p>
<h2 id="the-changes-that-will-bite">the changes that will bite<a class="anchor" href="#the-changes-that-will-bite" aria-label="link to this section">#</a></h2>
<p><strong>RPIT lifetime capture.</strong> In edition 2024, <code>-&gt; impl Trait</code> in a free function captures <em>all</em> in-scope lifetime parameters by default, matching what <code>async fn</code> already did. Previously it captured only type parameters, and you had to add a <code>+ '_</code> or a witness parameter to opt in.</p>
<p>This makes the common case work without incantations. It also means some signatures that compiled before now fail borrow checking, because the returned opaque type is now considered to borrow something it did not before. The fix is the new <code>use&lt;..&gt;</code> syntax to state precisely what is captured:</p>
<div class="code"><span class="code-lang">rust</span><pre><code class="lang-rust">fn parse&lt;'a&gt;(input: &amp;'a str) -&gt; impl Iterator&lt;Item = &amp;'a str&gt; + use&lt;'a&gt; {
    input.split(',')
}</code></pre></div>
<p><strong><code>unsafe</code> attributes.</strong> <code>#[no_mangle]</code>, <code>#[export_name]</code> and <code>#[link_section]</code> are now written <code>#[unsafe(no_mangle)]</code>. These attributes can violate soundness — <code>no_mangle</code> can collide with a symbol the linker resolves differently — and the language now says so out loud. Mechanical fix, applied by <code>cargo fix</code>.</p>
<p><strong><code>unsafe extern</code> blocks.</strong> Declaring a foreign function is an unsafe assertion about its signature. Now spelled that way.</p>
<p><strong><code>gen</code> is a reserved keyword.</strong> Generator blocks are coming; the keyword is reserved now so it can land without another edition. If you have a variable named <code>gen</code>, rename it. Yes, this affects more code than you would think.</p>
<p><strong>Tail expression temporary scope.</strong> Temporaries in the final expression of a block now drop before local variables rather than after. This fixes a class of surprising deadlocks with <code>MutexGuard</code> in a tail position, and changes drop order in ways that could matter if you have <code>Drop</code> impls with side effects.</p>
<p><strong><code>if let</code> temporary scope.</strong> Temporaries in an <code>if let</code> scrutinee now drop at the end of the <code>if let</code>, not at the end of the enclosing statement. Same category: fixes real deadlocks, changes observable drop timing.</p>
<p><strong>Never type fallback.</strong> <code>!</code> now falls back to <code>!</code> instead of <code>()</code> in more positions. Mostly this makes bad code fail to compile rather than silently doing something odd.</p>
<p><strong><code>Cargo</code> resolver v3</strong> becomes the default, which is the MSRV-aware resolver from 1.84.</p>
<h2 id="should-you-migrate">should you migrate<a class="anchor" href="#should-you-migrate" aria-label="link to this section">#</a></h2>
<p>Yes, but not urgently and not during a crunch. The migration is mostly mechanical; the RPIT changes and the drop-order changes are the two places where you need to actually think.</p>
<p>Do it on a quiet week, run your full test suite, and pay specific attention to anything with a <code>Drop</code> impl that touches shared state. That is where a silently-changed behavior can hide.</p>
<h2 id="the-meta-point-about-editions">the meta-point about editions<a class="anchor" href="#the-meta-point-about-editions" aria-label="link to this section">#</a></h2>
<p>Rust's edition mechanism remains the best answer anyone has shipped to the problem of evolving a language without breaking the world. C++ cannot do this. Python's answer took a decade and cost the community enormously. Rust gets to change syntax and semantics on a six-year cadence, per-crate, with automated migration, and old code keeps compiling forever.</p>
<p>That is a genuinely important piece of language design and it is underrated because it works.</p>]]></content:encoded></item><item><title>Rust 1.84 and the quiet rewrite underneath</title><link>https://readme.news/rust-184-and-the-quiet-rewrite-underneath/</link><guid isPermaLink="true">https://readme.news/rust-184-and-the-quiet-rewrite-underneath/</guid><pubDate>Thu, 09 Jan 2025 09:00:00 +0000</pubDate><description>The next-generation trait solver goes live for coherence checking. Most people will never notice, which is the point.</description><content:encoded><![CDATA[<p>Rust 1.84 shipped today, and the headline item is a piece of plumbing that almost nobody will consciously interact with: the next-generation trait solver is now used for coherence checking.</p>
<p>If that sentence meant nothing to you, that is fine and arguably intended. Here is why it matters anyway.</p>
<h2 id="what-a-trait-solver-does">what a trait solver does<a class="anchor" href="#what-a-trait-solver-does" aria-label="link to this section">#</a></h2>
<p>When you write <code>impl Display for MyType</code>, the compiler has to answer questions like: does this impl overlap with another one? Can this generic bound be satisfied? Is this associated type equal to that one? The component answering those questions is the trait solver, and the old one grew organically over a decade of Rust's life. It has known unsoundness holes, it has performance cliffs, and it has behaviors nobody can fully explain including the people who wrote it.</p>
<p>The new solver is a ground-up reimplementation with a proper handling of cycles, coinduction, and <a class="xref" href="/caching-is-the-only-optimization-that-reliably-works/" title="Caching is the only optimization that reliably works">caching</a>. Coherence checking — the "do these two impls conflict" question — is the first place it has been switched on by default, because coherence is a relatively contained problem where a regression shows up loudly at compile time rather than quietly at runtime.</p>
<h2 id="what-you-might-actually-see">what you might actually see<a class="anchor" href="#what-you-might-actually-see" aria-label="link to this section">#</a></h2>
<p>A very small number of crates will now get errors they did not get before, because the old solver was accepting impls it should not have. That is a fix, not a regression, but it will feel like a regression if it happens to you. The release notes flag it.</p>
<p>Also in this release:</p>
<ul><li><strong>Strict provenance APIs stabilize.</strong> <code>ptr::with_addr</code>, <code>ptr::addr</code>, <code>ptr::map_addr</code> and friends. This is the vocabulary for expressing pointer arithmetic in a way that does not launder provenance through an integer, which matters enormously for Miri, for CHERI-style hardware, and for anyone whose code is going to be checked by a tool smarter than themselves.</li><li><strong>Cargo respects the <code>rust-version</code> field when resolving dependencies.</strong> The MSRV-aware resolver will now prefer a dependency version compatible with your declared minimum Rust version instead of picking the newest and detonating. This has been requested for years and quietly fixes a real papercut for anybody supporting older toolchains.</li></ul>
<div class="code"><span class="code-lang">toml</span><pre><code class="lang-toml">[package]
rust-version = "1.75"   # cargo now actually uses this during resolution</code></pre></div>
<h2 id="the-pattern-worth-noticing">the pattern worth noticing<a class="anchor" href="#the-pattern-worth-noticing" aria-label="link to this section">#</a></h2>
<p>Rust's release train has settled into a rhythm where the exciting user-facing features arrive in editions and the intervening six-week releases carry structural work. 1.84 is almost entirely structural. The new solver has been in development for something like three years and is being landed one surface at a time, with the more invasive switch — using it everywhere, not just coherence — still ahead.</p>
<p>That is how you replace a load-bearing component in a language with a stability guarantee. Slowly, in public, with the ability to flip back. It is unglamorous and it is the reason people trust the toolchain.</p>
<p><a class="xref" href="/rust-2024-is-the-largest-edition-since-2018/" title="Rust 2024 is the largest edition since 2018">Rust 2024</a> edition lands with 1.85 next month, and that one will be noisy.</p>]]></content:encoded></item>
</channel>
</rss>
