<?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 — releases</title>
<link>https://readme.news/tags/releases/</link>
<atom:link href="https://readme.news/tags/releases/feed.xml" rel="self" type="application/rss+xml"/>
<description>README pieces tagged releases.</description>
<language>en-us</language>
<lastBuildDate>Fri, 02 Oct 2026 12:39:18 +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>The annual Postgres upgrade you keep deferring</title><link>https://readme.news/the-annual-postgres-upgrade-you-keep-deferring/</link><guid isPermaLink="true">https://readme.news/the-annual-postgres-upgrade-you-keep-deferring/</guid><pubDate>Thu, 03 Sep 2026 09:00:00 +0000</pubDate><description>A new major version every autumn, a five-year support window, and a migration that gets worse the longer you leave it.</description><content:encoded><![CDATA[<p>PostgreSQL ships a major version every autumn and has done for well over a decade. Each one is supported for five years. The arithmetic is simple and most organisations still get it wrong in the same direction.</p>
<h2 id="the-trap">the trap<a class="anchor" href="#the-trap" aria-label="link to this section">#</a></h2>
<p>Five years of support sounds generous, so the upgrade never makes the roadmap. Then one of two things happens:</p>
<p><strong>You reach end of life</strong> and have to do four or five major versions at once, under time pressure, with an accumulated set of behaviour changes nobody has read about.</p>
<p><strong>You quietly lose years of free improvement.</strong> Postgres releases carry substantial planner, vacuum and I/O work every single year. A database three versions behind is slower than it needs to be, on hardware you are already paying for.</p>
<p>The teams that find upgrades painful are always the ones who defer them. The teams that upgrade every year find it boring, because one version of behaviour change is a <a class="xref" href="/the-unreasonable-effectiveness-of-a-changelog/" title="The unreasonable effectiveness of a changelog">changelog</a> and four versions is a project.</p>
<h2 id="what-to-actually-do">what to actually do<a class="anchor" href="#what-to-actually-do" aria-label="link to this section">#</a></h2>
<p><strong>Read one document: the release notes' incompatibilities section.</strong> Not the feature list — the "Migration to Version N" section at the top. It is usually under a page and it is the only part that can hurt you.</p>
<p><strong>Test on a copy of production, at production size.</strong> Restore a real dump, run your application's test suite against it, and run your slowest queries. Planner changes are the main source of surprise, and they only show up with real statistics and real data volume.</p>
<p><strong>Check your extensions first.</strong> This is the most common blocker. Every extension must have a build for the target version, and if you depend on something niche, verify before you plan anything else.</p>
<p><strong>Use logical replication for anything where downtime matters.</strong> Replicate old to new, let it catch up, cut over. <code>pg_upgrade --link</code> is fast and requires a maintenance window; logical replication is more setup and needs seconds.</p>
<p><strong>Run <code>ANALYZE</code> immediately after.</strong> Modern <code>pg_upgrade</code> carries statistics across, which removed the classic post-upgrade outage where a statistics-free database planned everything terribly. Verify it happened rather than assuming.</p>
<h2 id="the-operational-habit-worth-adopting">the operational habit worth adopting<a class="anchor" href="#the-operational-habit-worth-adopting" aria-label="link to this section">#</a></h2>
<p>Put it in the calendar: <strong>evaluate in October, upgrade in the first quarter.</strong></p>
<p>That gives the release a few point versions to settle, keeps you at most one version behind, and turns the whole thing into a scheduled chore rather than an emergency. The same pattern works for any dependency with an annual cadence, and the calendar entry is what makes it happen — nobody ever gets around to it otherwise.</p>
<h2 id="the-version-everyone-is-actually-on">the version everyone is actually on<a class="anchor" href="#the-version-everyone-is-actually-on" aria-label="link to this section">#</a></h2>
<p>Run this and find out where you really are:</p>
<div class="code"><span class="code-lang">sql</span><pre><code class="lang-sql">SELECT version();
SELECT name, setting FROM pg_settings WHERE name IN
  ('server_version_num', 'shared_buffers', 'effective_cache_size',
   'work_mem', 'max_connections', 'effective_io_concurrency');</code></pre></div>
<p>While you are there: <code>max_connections</code> is almost certainly too high and <code>effective_io_concurrency</code> is almost certainly too low for SSD storage. Both defaults date from an era of different hardware, and both are one-line changes with measurable effects.</p>
<p>The upgrade is the boring part. The settings nobody has revisited since the database was provisioned are usually the bigger win.</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>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>Node 26 and the build step that finally disappeared</title><link>https://readme.news/node-26-and-the-build-step-that-finally-disappeared/</link><guid isPermaLink="true">https://readme.news/node-26-and-the-build-step-that-finally-disappeared/</guid><pubDate>Fri, 03 Apr 2026 09:00:00 +0000</pubDate><description>Type stripping, a mature test runner, and a standard library that absorbed the ecosystem. You can ship TypeScript with no toolchain.</description><content:encoded><![CDATA[<p>Node.js 26 landed this week, and the accumulated effect of the last several major versions is worth stating plainly: <strong>you can now write a TypeScript server application, test it, and run it in production, with zero build tooling.</strong></p>
<p>That was not true three years ago and it is a meaningful simplification.</p>
<h2 id="the-workflow">the workflow<a class="anchor" href="#the-workflow" aria-label="link to this section">#</a></h2>
<div class="code"><span class="code-lang">json</span><pre><code class="lang-json">{
  "compilerOptions": {
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true,
    "module": "nodenext",
    "strict": true,
    "noEmit": true
  }
}</code></pre></div>
<div class="code"><span class="code-lang">bash</span><pre><code class="lang-bash">node --test          # test runner, built in
node --watch app.ts  # dev, with reload, running TypeScript directly
npx tsc              # typecheck only, in CI
node app.ts          # production</code></pre></div>
<p>No bundler. No transpiler in the runtime path. No <code>ts-node</code>. No <code>nodemon</code>. No <code>dotenv</code>. No test framework. No <code>node-fetch</code>.</p>
<p>The <code>erasableSyntaxOnly</code> flag is what makes it safe: it rejects any TypeScript syntax that cannot be removed by whitespace substitution — enums, namespaces with runtime values, parameter properties — so your source is guaranteed to run under type stripping.</p>
<h2 id="what-got-absorbed-cumulatively">what got absorbed, cumulatively<a class="anchor" href="#what-got-absorbed-cumulatively" aria-label="link to this section">#</a></h2>
<p>Over the last several major versions Node has taken into the runtime:</p>
<div class="table-wrap"><table><thead><tr><th style="text-align:left">capability</th><th style="text-align:left">the package it replaced</th></tr></thead><tbody><tr><td style="text-align:left">test runner</td><td style="text-align:left">jest, mocha, ava</td></tr><tr><td style="text-align:left">watch mode</td><td style="text-align:left">nodemon</td></tr><tr><td style="text-align:left"><code>.env</code> loading</td><td style="text-align:left">dotenv</td></tr><tr><td style="text-align:left">TypeScript execution</td><td style="text-align:left">ts-node, tsx</td></tr><tr><td style="text-align:left">SQLite</td><td style="text-align:left">better-sqlite3</td></tr><tr><td style="text-align:left"><code>fetch</code>, WebSocket</td><td style="text-align:left">node-fetch, ws, axios</td></tr><tr><td style="text-align:left"><a class="xref" href="/nodejs-24-and-the-slow-reinvention-of-the-runtime/" title="Node.js 24 and the slow reinvention of the runtime">permission model</a></td><td style="text-align:left">(nothing existed)</td></tr><tr><td style="text-align:left"><code>using</code> / disposal</td><td style="text-align:left">various</td></tr></tbody></table></div>
<p>Every one of those is a dependency removed, which is a supply chain surface removed, an upgrade treadmill removed, and a <code>postinstall</code> script removed.</p>
<p>For a small service, a fresh project's dependency count is now dramatically lower than it would have been in 2022. That is the most important consequence and it is rarely framed that way.</p>
<h2 id="the-division-of-labor-that-makes-sense">the division of labor that makes sense<a class="anchor" href="#the-division-of-labor-that-makes-sense" aria-label="link to this section">#</a></h2>
<p><strong>Typechecking belongs in CI and your editor.</strong> Not in the production runtime hot path. <code>tsc --noEmit</code> in CI, language server in your editor, type stripping at runtime.</p>
<p>A type error will happily run under stripping and fail at runtime. That is the correct trade — you check types where checking is cheap and run where running should be fast — and it requires that CI actually gates on the typecheck. If it does not, you have given up types.</p>
<p><strong>Bundling is still right for some things.</strong> If you deploy to a serverless platform where cold start scales with file count, bundling helps. If you ship to browsers, obviously. For a long-running server process, it buys you nothing.</p>
<h2 id="what-is-still-missing">what is still missing<a class="anchor" href="#what-is-still-missing" aria-label="link to this section">#</a></h2>
<p><strong>Decorators.</strong> They are not erasable — they generate runtime code. If your framework depends on decorator-based dependency injection or routing, you need a transform. Several major frameworks are in this category, and it is the single most common blocker for adopting the no-build workflow.</p>
<p><strong>Path aliases.</strong> <code>@/components/foo</code> requires resolution mapping. Node supports <code>imports</code> in <code>package.json</code> with the <code>#</code> prefix, which works and requires changing your import style.</p>
<p><strong>Anything in your build that is not TypeScript.</strong> CSS modules, GraphQL documents, asset imports. If your build does more than strip types, you still have a build.</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>If you are on Node 22 or earlier, plan the move. Check:</p>
<ul><li>Base image compatibility — minimum glibc and macOS versions have moved.</li><li>Deprecated APIs that now throw rather than warn.</li><li>Native modules, which need a rebuild.</li></ul>
<p>Run the full suite on the new version in CI before switching the default. The upgrade is usually uneventful and "usually" is load-bearing.</p>
<h2 id="the-broader-observation">the broader observation<a class="anchor" href="#the-broader-observation" aria-label="link to this section">#</a></h2>
<p>Node's response to competitive pressure from Deno and Bun was to absorb their best ideas rather than to argue about philosophy.</p>
<p>That was correct, it took about four years, and the ecosystem is meaningfully better for it. Competition in developer tooling works, and the incumbent that responds by improving rather than by defending is the one that keeps the position.</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>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>Node.js 24 goes LTS and the runtime wars settle into a truce</title><link>https://readme.news/nodejs-24-goes-lts-and-the-runtime-wars-settle-into-a-truce/</link><guid isPermaLink="true">https://readme.news/nodejs-24-goes-lts-and-the-runtime-wars-settle-into-a-truce/</guid><pubDate>Tue, 28 Oct 2025 09:00:00 +0000</pubDate><description>Long-term support for the batteries-included Node, plus a look at where Deno and Bun actually landed.</description><content:encoded><![CDATA[<p>Node.js 24 entered long-term support this week. That makes it the version most teams will run for the next two years, and it is a good moment to take stock of the three-way runtime competition that has defined server-side JavaScript since 2022.</p>
<h2 id="what-you-get-in-24-lts">what you get in 24 LTS<a class="anchor" href="#what-you-get-in-24-lts" aria-label="link to this section">#</a></h2>
<p>The accumulated result of the last two years of Node absorbing its own ecosystem:</p>
<ul><li><strong>A test runner</strong> (<code>node --test</code>) with coverage, mocking, and watch mode.</li><li><strong>TypeScript type stripping</strong>, no build step.</li><li><strong>A <a class="xref" href="/nodejs-24-and-the-slow-reinvention-of-the-runtime/" title="Node.js 24 and the slow reinvention of the runtime">permission model</a></strong> for filesystem, network, and child process access.</li><li><strong><code>.env</code> file loading</strong> built in.</li><li><strong>A SQLite module</strong> in the standard library.</li><li><strong><code>fetch</code>, <code>WebSocket</code>, <code>URLPattern</code></strong> as globals.</li><li><strong><code>using</code> declarations</strong> for deterministic resource cleanup.</li><li><strong>A watch mode</strong> that does not require nodemon.</li></ul>
<p>Every one of those replaced a dependency. That is the whole story of Node's last three years, and it was the correct response to competitive pressure.</p>
<h2 id="where-the-three-landed">where the three landed<a class="anchor" href="#where-the-three-landed" aria-label="link to this section">#</a></h2>
<p><strong>Node</strong> won by absorbing. It has the ecosystem, the deployment targets, the enterprise support, and now most of the convenience. Its remaining weaknesses are startup time and the accumulated weight of API decisions from 2010 that cannot be changed.</p>
<p><strong>Deno</strong> built the best <a class="xref" href="/the-component-model-and-the-plugin-problem/" title="The component model and the plugin problem">security model</a> and the most coherent standard library, then spent years walking back the decisions that made adoption hard — first adding npm compatibility, then <code>node_modules</code>, then <code>package.json</code>. It is excellent and it is a niche, and the trademark dispute over "JavaScript" was an unusual way to spend a year.</p>
<p><strong>Bun</strong> won on speed and on the package manager. <code>bun install</code> is dramatically faster than npm and a very large number of teams use it for exactly that while running Node in production. That is a real and defensible position — being the best tool for one step of the workflow.</p>
<h2 id="what-actually-changed-for-developers">what actually changed for developers<a class="anchor" href="#what-actually-changed-for-developers" aria-label="link to this section">#</a></h2>
<p>The competition worked. All three runtimes are much better than Node was in 2021, and Node specifically improved in ways it had resisted for a decade.</p>
<p>That is the argument for competition in developer tooling, and it is worth remembering when a dominant tool seems permanent.</p>
<h2 id="practical-guidance">practical guidance<a class="anchor" href="#practical-guidance" aria-label="link to this section">#</a></h2>
<p><strong>For a new production service:</strong> Node 24 LTS. The ecosystem compatibility and operational maturity dominate, and the convenience gap has closed.</p>
<p><strong>For a CLI or a script:</strong> Bun, probably. Startup time and the single-binary story are genuinely better, and the compatibility risk is low for a program you control end to end.</p>
<p><strong>For anything where sandboxing matters:</strong> Deno's permission model is the most mature, though Node's is now credible.</p>
<p><strong>For your package manager:</strong> Bun or pnpm. npm is fine and it is slower, and the difference on a large install is minutes rather than seconds.</p>
<h2 id="the-migration-note">the migration note<a class="anchor" href="#the-migration-note" aria-label="link to this section">#</a></h2>
<p>If you are on Node 20, plan the move to 24. Node 22 is a reasonable interim stop and there is no strong reason to linger there.</p>
<p>Things to check:</p>
<ul><li><strong>Base image compatibility.</strong> Node 24 raises minimum glibc and macOS versions.</li><li><strong>Deprecated APIs that now throw</strong> rather than warning.</li><li><strong>Native modules.</strong> Anything with a compiled component needs a rebuild and possibly an upgrade.</li></ul>
<p>Run your full test suite on 24 in CI before you switch the default. The upgrade is usually uneventful and "usually" is doing work in that sentence.</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.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>Linux 6.16 and the steady grind of filesystem work</title><link>https://readme.news/linux-616-and-the-steady-grind-of-filesystem-work/</link><guid isPermaLink="true">https://readme.news/linux-616-and-the-steady-grind-of-filesystem-work/</guid><pubDate>Mon, 28 Jul 2025 09:00:00 +0000</pubDate><description>Faster ext4 writes, more Rust bindings, and a release with no headline feature. That&#x27;s a good sign.</description><content:encoded><![CDATA[<p>Linux 6.16 shipped this week. There is no headline feature, which is worth writing about, because a project that ships boring releases on schedule is a healthy one.</p>
<h2 id="the-notable-bits">the notable bits<a class="anchor" href="#the-notable-bits" aria-label="link to this section">#</a></h2>
<p><strong>ext4 gains support for concurrent direct I/O writes to the same file</strong> when using extent-based allocation without needing exclusive inode locking. For database workloads on ext4 this is a meaningful throughput improvement — the exclusive lock was a real serialization point on write-heavy workloads with many threads hitting one large file.</p>
<p>If you run Postgres or MySQL on ext4 with direct I/O, this is worth benchmarking after you upgrade.</p>
<p><strong>Btrfs</strong> continues its steady improvement, including better handling of large folios. <strong>XFS</strong> got more work on its atomic write support, which is one of those capabilities that unlocks meaningful optimizations in databases that can rely on it — no more double-write buffers if the filesystem can guarantee torn-write protection.</p>
<p><strong>More Rust bindings</strong> landed, including additional infrastructure for DRM drivers. The Rust-for-Linux effort continues its subsystem-by-subsystem advance.</p>
<p><strong>Futex improvements</strong> for scalability under contention, which matters for any heavily threaded userspace runtime — the JVM, Go's scheduler, and every async runtime with a work-stealing thread pool ultimately bottom out here.</p>
<h2 id="the-thing-about-boring-releases">the thing about boring releases<a class="anchor" href="#the-thing-about-boring-releases" aria-label="link to this section">#</a></h2>
<p>The Linux kernel ships roughly every nine to ten weeks. It has done so for two decades. Every release contains thousands of commits from over a thousand developers across hundreds of companies who mostly compete with each other.</p>
<p>There is no roadmap document. There is no product manager. There is a maintainer hierarchy, a merge window, a stabilization period, and a release.</p>
<p>It works better than almost any commercially-managed software project on earth, and the reason is worth thinking about: the process optimizes for <strong>not regressing</strong> above everything else. The famous "we do not break userspace" rule is not politeness, it is the constraint that makes an enormous decentralized project tractable. If the <a class="xref" href="/the-interface-is-the-product/" title="The interface is the product">interface</a> contract is inviolable, subsystems can evolve independently without coordination.</p>
<p>That is the actual lesson for people building large systems. Stable interfaces between components are what let you have many independent teams. Every organization that struggles with cross-team coordination is, underneath, struggling with unstable interfaces.</p>
<h2 id="the-maintainer-question">the maintainer question<a class="anchor" href="#the-maintainer-question" aria-label="link to this section">#</a></h2>
<p>Worth noting alongside: the kernel has a succession problem. A meaningful number of critical subsystems are maintained by people who have been doing it for fifteen or twenty years, and the pipeline of replacements is thin.</p>
<p>This is true of most critical infrastructure software and it does not get attention because it is not an incident until it is. The number of load-bearing open source projects with exactly one active maintainer remains one of the scariest statistics in computing.</p>
<p>If your company depends on the kernel — and it does — funding maintainers is a better use of security budget than most of the things in your security budget.</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>Gemini 2.5 goes generally available with a thinking dial</title><link>https://readme.news/gemini-25-goes-generally-available-with-a-thinking-dial/</link><guid isPermaLink="true">https://readme.news/gemini-25-goes-generally-available-with-a-thinking-dial/</guid><pubDate>Wed, 18 Jun 2025 09:00:00 +0000</pubDate><description>Pro and Flash hit GA, Flash-Lite arrives, and every tier exposes a thinking budget.</description><content:encoded><![CDATA[<p><a class="xref" href="/gemini-25-pro-is-googles-best-model-and-it-shows/" title="Gemini 2.5 Pro is Google&#x27;s best model and it shows">Gemini 2.5 Pro</a> and Flash are generally available today, with Flash-Lite entering preview. All three expose a configurable thinking budget.</p>
<p>The lineup now reads as a clean cost-capability ladder, which is a thing Google has struggled to communicate for two years:</p>
<div class="table-wrap"><table><thead><tr><th style="text-align:left">model</th><th style="text-align:left">shape</th><th style="text-align:left">thinking</th></tr></thead><tbody><tr><td style="text-align:left">2.5 Pro</td><td style="text-align:left">frontier reasoning</td><td style="text-align:left">on, budgeted</td></tr><tr><td style="text-align:left">2.5 Flash</td><td style="text-align:left">fast, cheap, capable</td><td style="text-align:left">on, budgeted, can be 0</td></tr><tr><td style="text-align:left">2.5 Flash-Lite</td><td style="text-align:left">cheapest, fastest</td><td style="text-align:left">off by default, can enable</td></tr></tbody></table></div>
<h2 id="the-thinking-budget-properly">the thinking budget, properly<a class="anchor" href="#the-thinking-budget-properly" aria-label="link to this section">#</a></h2>
<p>Every tier takes a <code>thinking_budget</code> parameter. Setting it to 0 disables reasoning entirely; setting it to -1 lets the model decide.</p>
<div class="code"><span class="code-lang">python</span><pre><code class="lang-python">from google import genai
from google.genai import types

client = genai.Client()
resp = client.models.generate_content(
    model="gemini-2.5-flash",
    contents="Classify this ticket: ...",
    config=types.GenerateContentConfig(
        thinking_config=types.ThinkingConfig(thinking_budget=0)
    ),
)</code></pre></div>
<p>I want to be emphatic about this because it is the single largest cost lever available in a reasoning model and most teams are not touching it.</p>
<p>Thinking tokens are output tokens. They are billed. A classification task that gets 2,000 thinking tokens is paying for reasoning it did not need. Across a million requests that is a very real number.</p>
<p>The practical method:</p>
<ol><li>Run your eval set with <code>thinking_budget=0</code>.</li><li>Run it with 512, 2048, 8192.</li><li>Plot quality against cost.</li><li>Pick the knee of the curve.</li></ol>
<p>For most production tasks — extraction, classification, routing, summarization — the curve is flat and the answer is zero or near it. For planning, debugging, and multi-step math, the curve is steep. You cannot guess which without measuring, and measuring takes an hour.</p>
<h2 id="the-deprecation-note">the deprecation note<a class="anchor" href="#the-deprecation-note" aria-label="link to this section">#</a></h2>
<p>Google is deprecating the 1.5 models. If you are still on 1.5 Pro, you have a migration to do, and the behavior differences are real enough that you should re-run your evals rather than assuming a drop-in.</p>
<p>This is going to keep happening. Model deprecation on a roughly annual cadence is now the norm across every provider, and the teams that are handling it well are the ones who wrote their eval harness first.</p>
<p>If you do not have one, the cost of every future model migration is a week of vibes-based testing and a production incident. If you do, it is an afternoon.</p>
<h2 id="the-competitive-position">the competitive position<a class="anchor" href="#the-competitive-position" aria-label="link to this section">#</a></h2>
<p>Flash is, at time of writing, the best price-performance point available from any major provider for general work, by a margin that is not close. TPU economics are real.</p>
<p>Pro is competitive at the frontier without clearly leading. That is a much better position than Google was in a year ago, and the volume is going to come from Flash regardless.</p>
<h2 id="the-thing-to-watch">the thing to watch<a class="anchor" href="#the-thing-to-watch" aria-label="link to this section">#</a></h2>
<p>Google's remaining weakness is developer experience: three overlapping SDKs, a confusing split between AI Studio and Vertex, documentation that assumes GCP familiarity, and a model naming scheme with <code>-preview-05-20</code> suffixes.</p>
<p>The new unified <code>google-genai</code> SDK is an improvement. It is not yet where the competition is, and for a lot of teams the API ergonomics are what actually decides the default.</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>Node.js 24 and the slow reinvention of the runtime</title><link>https://readme.news/nodejs-24-and-the-slow-reinvention-of-the-runtime/</link><guid isPermaLink="true">https://readme.news/nodejs-24-and-the-slow-reinvention-of-the-runtime/</guid><pubDate>Tue, 06 May 2025 09:00:00 +0000</pubDate><description>V8 13.6, npm 11, `fetch` no longer experimental, and the permission model losing its dashes.</description><content:encoded><![CDATA[<p>Node.js 24 is out. It becomes LTS in October. The individual items are small; the direction they add up to is not.</p>
<h2 id="whats-in-it">what's in it<a class="anchor" href="#whats-in-it" aria-label="link to this section">#</a></h2>
<p><strong>V8 13.6.</strong> Brings <code>RegExp.escape</code> (finally), <code>Float16Array</code>, <code>Atomics.pause</code>, and explicit resource management — the <code>using</code> declaration:</p>
<div class="code"><span class="code-lang">js</span><pre><code class="lang-js">{
  using file = await open("data.txt");
  // ...
}  // file[Symbol.asyncDispose]() called automatically</code></pre></div>
<p>That last one is a genuinely useful addition to the language. Deterministic cleanup without try/finally pyramids, and it composes with async.</p>
<p><strong>npm 11.</strong> Faster, stricter, and with better handling of the lifecycle-script security surface.</p>
<p><strong>AsyncLocalStorage</strong> now defaults to <code>AsyncContextFrame</code>, which is a substantially faster implementation. If you use context propagation for request tracing — and if you have an observability stack, you do — this is a real performance improvement you get for free.</p>
<p><strong>The permission model drops the experimental dashes.</strong> <code>--permission</code> instead of <code>--experimental-permission</code>.</p>
<div class="code"><span class="code-lang">bash</span><pre><code class="lang-bash">node --permission --allow-fs-read=./data --allow-net=api.example.com app.js</code></pre></div>
<p><strong>URLPattern</strong> is global. <strong>Undici updated</strong>, so <code>fetch</code> behavior tracks the platform more closely.</p>
<h2 id="the-direction">the direction<a class="anchor" href="#the-direction" aria-label="link to this section">#</a></h2>
<p>Look at what Node has added over the last three major versions: a test runner, a watch mode, <code>.env</code> file support, a permission model, TypeScript type stripping, a SQLite module, and a full fetch stack.</p>
<p>Every one of those was previously a dependency. <code>jest</code> or <code>mocha</code>. <code>nodemon</code>. <code>dotenv</code>. Nothing, because there was no sandbox. <code>ts-node</code>. <code>better-sqlite3</code>. <code>node-fetch</code> or <code>axios</code>.</p>
<p>Node is absorbing its own ecosystem's most common packages into the runtime. This is a direct response to Deno and Bun, both of which shipped batteries-included from day one and made Node's "small core" philosophy look like an excuse.</p>
<p>I think this is correct and overdue. "Small core, rich ecosystem" was a reasonable position in 2012, when the ecosystem was small and trustworthy. In 2025, when a fresh Express app pulls three hundred transitive dependencies and each one is a supply chain risk, every capability moved into the runtime is one fewer package with a <code>postinstall</code> script.</p>
<h2 id="the-migration-notes">the migration notes<a class="anchor" href="#the-migration-notes" aria-label="link to this section">#</a></h2>
<ul><li>Node 24 requires a newer minimum glibc and macOS version. Check your base images.</li><li>Some deprecated APIs finally throw instead of warning. Run your test suite before you upgrade, not after.</li><li>If you are on 20, plan to go to 24 when it hits LTS in October rather than stopping at 22.</li></ul>
<h2 id="the-type-stripping-question">the type stripping question<a class="anchor" href="#the-type-stripping-question" aria-label="link to this section">#</a></h2>
<p>Node can now run <code>.ts</code> files by erasing types. It does not typecheck. That is the correct division of labor — typechecking belongs in your editor and your CI, not in your production runtime hot path — but it does mean a type error will happily run and fail at runtime.</p>
<p>The workflow that makes sense: <code>tsc --noEmit</code> in CI, <code>node app.ts</code> in production, <code>erasableSyntaxOnly</code> set so you cannot accidentally use syntax that requires a transform.</p>
<p>No build step. That is a real ergonomic win and it has been a long time coming.</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>Linux 6.14 and Rust in the kernel, one driver at a time</title><link>https://readme.news/linux-614-and-rust-in-the-kernel-one-driver-at-a-time/</link><guid isPermaLink="true">https://readme.news/linux-614-and-rust-in-the-kernel-one-driver-at-a-time/</guid><pubDate>Mon, 24 Mar 2025 09:00:00 +0000</pubDate><description>Faster fuse, better hardware support, and a Rust for Linux effort that keeps advancing through genuine disagreement.</description><content:encoded><![CDATA[<p>Linux 6.14 is out. The release is modest by kernel standards — improved AMD and Intel graphics support, <code>ntsync</code> for better Wine and Proton performance, more filesystem work — but the ongoing story underneath is Rust, and this cycle had one of its sharper moments.</p>
<h2 id="where-rust-for-linux-actually-is">where Rust for Linux actually is<a class="anchor" href="#where-rust-for-linux-actually-is" aria-label="link to this section">#</a></h2>
<p>The infrastructure has been merged for several cycles. What has been slower is the <em>bindings</em>: the safe Rust abstractions over kernel C APIs that a driver author would actually use. You cannot write a Rust network driver until somebody writes a sound Rust abstraction over the network device API, and doing that correctly requires deep agreement between the Rust folks and the maintainer of the subsystem in question.</p>
<p>That is where the friction is, and it is not primarily technical.</p>
<p>The concern from some longtime maintainers is legitimate and worth stating plainly: if a Rust abstraction wraps a C API, then a change to the C API can break the Rust side, and now the C maintainer is on the hook for a language they did not sign up to learn. Multiply across dozens of subsystems and you have a real maintenance burden distributed to people who did not choose it.</p>
<p>The counter-position, also legitimate: memory safety bugs in drivers are a substantial and ongoing fraction of kernel CVEs, drivers are where most kernel code lives, and drivers are exactly the place where a language with compile-time memory safety pays off most.</p>
<p>Linus's stated position has been consistent — Rust is welcome, the experiment continues, and maintainers are not required to accept Rust in their subsystems but also are not permitted to block it purely on preference. That is a politically difficult line to hold and he has mostly held it.</p>
<h2 id="the-technical-state">the technical state<a class="anchor" href="#the-technical-state" aria-label="link to this section">#</a></h2>
<p>What exists and works today:</p>
<ul><li>Kernel allocation, <a class="xref" href="/error-handling-across-a-boundary/" title="Error handling across a boundary">error handling</a>, and the <code>Result</code>-based error propagation.</li><li>Enough abstraction for real drivers — the Android Binder rewrite and the Asahi Linux GPU driver are both substantial Rust codebases running in production on real hardware.</li><li><code>pin-init</code>, which solves the self-referential-struct problem that makes kernel data structures awkward in safe Rust.</li></ul>
<p>What is still hard:</p>
<ul><li>The compiler version floor keeps moving, which distributions dislike.</li><li>Rust's story for <code>no_std</code> allocation failure handling required work that only exists because the kernel needed it.</li><li>Architecture coverage. Rust needs an LLVM backend for the target, which rules out several architectures the kernel still supports.</li></ul>
<h2 id="the-honest-assessment">the honest assessment<a class="anchor" href="#the-honest-assessment" aria-label="link to this section">#</a></h2>
<p>This will take longer than advocates hope and will succeed more than skeptics expect. The pattern is already visible: new drivers in Rust where a maintainer is willing, C everywhere else, indefinitely. Nobody is rewriting the VFS.</p>
<p>That is a fine outcome. The goal was never a Rust kernel. It was to stop shipping new memory-safety bugs in the parts of the kernel that get the most new code, and on that specific goal the trajectory is good.</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>TypeScript 5.8 quietly bets on type stripping</title><link>https://readme.news/typescript-58-quietly-bets-on-type-stripping/</link><guid isPermaLink="true">https://readme.news/typescript-58-quietly-bets-on-type-stripping/</guid><pubDate>Sat, 01 Mar 2025 09:00:00 +0000</pubDate><description>`--erasableSyntaxOnly` exists because Node can now run TypeScript, and enums can&#x27;t be erased.</description><content:encoded><![CDATA[<p>TypeScript 5.8 shipped with a flag that looks like a footnote and is actually a statement about where the language is going: <code>--erasableSyntaxOnly</code>.</p>
<h2 id="the-context">the context<a class="anchor" href="#the-context" aria-label="link to this section">#</a></h2>
<p>Node.js can now strip TypeScript types and run the resulting JavaScript directly. Deno and Bun have done this for a while. The approach is deliberately dumb — it does not typecheck, it does not transform, it replaces type annotations with whitespace and runs what is left.</p>
<p>That works beautifully for the ninety-five percent of TypeScript that is JavaScript plus annotations. It breaks completely for the parts of TypeScript that <em>generate code</em>:</p>
<div class="code"><span class="code-lang">ts</span><pre><code class="lang-ts">enum Color { Red, Green }           // emits an object at runtime
namespace Utils { export const x = 1 }  // emits an IIFE
class Point {
  constructor(private x: number) {}  // parameter properties emit assignments
}
declare module "foo" {}              // fine, erasable</code></pre></div>
<p>You cannot erase an enum. There is nothing to erase to; the enum <em>is</em> runtime code wearing type-shaped syntax.</p>
<h2 id="what-the-flag-does">what the flag does<a class="anchor" href="#what-the-flag-does" aria-label="link to this section">#</a></h2>
<p><code>erasableSyntaxOnly: true</code> makes the compiler reject any construct that cannot be removed by whitespace substitution. Enums, namespaces with runtime values, parameter properties, and old-style <code>import =</code> are all errors.</p>
<div class="code"><span class="code-lang">json</span><pre><code class="lang-json">{
  "compilerOptions": {
    "erasableSyntaxOnly": true,
    "verbatimModuleSyntax": true,
    "module": "nodenext"
  }
}</code></pre></div>
<p>Turn it on and your TypeScript is guaranteed to run under any type-stripping runtime with no build step at all.</p>
<h2 id="the-alternatives-to-what-it-bans">the alternatives to what it bans<a class="anchor" href="#the-alternatives-to-what-it-bans" aria-label="link to this section">#</a></h2>
<p>You are losing very little.</p>
<p>Instead of an enum, a const object with a derived union type — which most codebases already prefer because it produces cleaner types and does not have <code>enum</code>'s bizarre bidirectional-mapping behavior:</p>
<div class="code"><span class="code-lang">ts</span><pre><code class="lang-ts">const Color = { Red: "red", Green: "green" } as const;
type Color = typeof Color[keyof typeof Color];  // "red" | "green"</code></pre></div>
<p>Instead of parameter properties, an explicit assignment. Two extra lines, and the ES class fields proposal made the ergonomics fine anyway.</p>
<p>Instead of namespaces, modules. You should have done this in 2019.</p>
<p><code>const enum</code> remains available with <code>preserveConstEnums</code>, but the honest advice is to stop using it — it has never played well with isolated module compilation and it never will.</p>
<h2 id="also-in-58">also in 5.8<a class="anchor" href="#also-in-58" aria-label="link to this section">#</a></h2>
<ul><li><strong>Checked returns for conditional expressions.</strong> Return statements whose type is a conditional now get checked against each branch individually rather than against the collapsed union, which catches a real class of bug.</li><li><strong><code>--module nodenext</code> supports <code>require()</code> of ESM</strong>, tracking Node's own change.</li><li><strong>Faster program updates</strong> on the editor path, which you will feel in a large monorepo more than any feature.</li></ul>
<h2 id="the-direction">the direction<a class="anchor" href="#the-direction" aria-label="link to this section">#</a></h2>
<p>The build step for TypeScript is dissolving. Between type stripping in runtimes, <code>tsc --noEmit</code> as a pure typechecker in CI, and bundlers that handle TypeScript natively, the days of TypeScript-as-a-compile-target are ending.</p>
<p>What remains is TypeScript as a <a class="xref" href="/type-systems-and-the-cost-of-being-right/" title="Type systems and the cost of being right">type system</a> layered on JavaScript — which is what it always claimed to be and only recently became true. Set the flag, delete your enums, and stop thinking about the build.</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>Bun 1.2 stops being a runtime and starts being a platform</title><link>https://readme.news/bun-12-stops-being-a-runtime-and-starts-being-a-platform/</link><guid isPermaLink="true">https://readme.news/bun-12-stops-being-a-runtime-and-starts-being-a-platform/</guid><pubDate>Fri, 17 Jan 2025 09:00:00 +0000</pubDate><description>Native S3 and Postgres clients, a real Node compatibility push, and a bundled package manager that keeps getting faster.</description><content:encoded><![CDATA[<p>Bun 1.2 landed this week and the release is a good moment to notice what the project has actually become. It started as "a fast JavaScript runtime." It is now attempting to be the entire server-side JavaScript toolchain in one binary, and the 1.2 feature list is unsubtle about it.</p>
<h2 id="whats-new">what's new<a class="anchor" href="#whats-new" aria-label="link to this section">#</a></h2>
<p><strong>A built-in S3 client.</strong> <code>Bun.s3</code> gives you presigned URLs, streaming reads and writes, and it works against any S3-compatible endpoint — R2, MinIO, Backblaze, the actual thing.</p>
<div class="code"><span class="code-lang">js</span><pre><code class="lang-js">import { s3 } from "bun";

const file = s3.file("uploads/report.pdf");
await file.write(pdfBytes, { type: "application/pdf" });
const url = file.presign({ expiresIn: 3600 });</code></pre></div>
<p><strong>A built-in Postgres client.</strong> <code>Bun.sql</code> is a tagged-template SQL <a class="xref" href="/the-interface-is-the-product/" title="The interface is the product">interface</a> with <a class="xref" href="/connection-pooling-explained-properly/" title="Connection pooling, explained properly">connection pooling</a>, prepared statements, and parameter binding that does not require you to think about <code>$1</code> ordering.</p>
<div class="code"><span class="code-lang">js</span><pre><code class="lang-js">import { sql } from "bun";
const users = await sql`select * from users where team = ${teamId} limit 20`;</code></pre></div>
<p><strong>Node compatibility as a first-class metric.</strong> Bun now publishes its pass rate against Node's own test suite and treats regressions as bugs. That is a much more honest signal than a feature checklist, and the number went up substantially in this cycle.</p>
<p><strong>Text-based lockfile.</strong> <code>bun.lock</code> replaces the binary <code>bun.lockb</code> as the default. Reviewable in a pull request, diffable, mergeable. This was the single most common complaint about adopting Bun in a team setting and it is now gone.</p>
<h2 id="the-strategic-read">the strategic read<a class="anchor" href="#the-strategic-read" aria-label="link to this section">#</a></h2>
<p>Node's answer to the same problem has been to add capabilities to the runtime incrementally and carefully: a built-in test runner, <code>--watch</code>, a <a class="xref" href="/nodejs-24-and-the-slow-reinvention-of-the-runtime/" title="Node.js 24 and the slow reinvention of the runtime">permission model</a>, and now stable-ish TypeScript stripping. Deno's answer was to bundle everything from the start and then spend years walking back the parts of its opinionated design that made adoption hard.</p>
<p>Bun's answer is to bundle everything <em>and</em> be compatible with Node's ecosystem rather than replacing it. That is a harder engineering problem and a much easier sales problem. Nobody has to rewrite anything. You point <code>bun</code> at an existing Express app and it mostly runs.</p>
<h2 id="the-honest-caveats">the honest caveats<a class="anchor" href="#the-honest-caveats" aria-label="link to this section">#</a></h2>
<p>Bun's ecosystem edge cases still bite. Native modules that assume V8 internals will not work, because Bun is JavaScriptCore. Some observability agents assume Node. Long-tail packages that reach into <code>process.binding</code> or undocumented internals will surprise you.</p>
<p>The pattern for adoption in 2025 looks like: use Bun as the package manager and test runner immediately, because those are drop-in and dramatically faster; use the runtime in production when your dependency tree is one you actually understand.</p>
<p>That is not a hedge. That is just what shipping looks like.</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>
