>>FIRST MISSION//LOGS//rust//rust-1-100-made-waiting-a-build-setting.MD
[ + ] TARGET_LOCKED200 OK
NOV 14, 2026timer 3 MIN READSYS_TAG: rust STATUS: VERIFIED_WORKING

Rust 1.100 Made Waiting a Build Setting

Rust 1.100 stabilized the never type after ten years and five failed attempts. The change I actually turned on is a Cargo line that ignores any crate version younger than I tell it to, and the 86 minutes arrayref spent on crates.io is the argument for it.

ENGINE: KERNEL // STABLE
BASE_ARCH: x86_64
HOST: VASTORIGINS.COM
GPG: OKSIG_ID: 0x4EB27DB0

I keep cargo update on a schedule the way I keep backups on a schedule: not because I expect a disaster, but because the cost of finding out late is worse than the cost of the habit. On November 12 the Rust team shipped 1.100.0, and the release notes contain two headings that should not be sitting in the same document. One is a type system feature that took ten years and five failed stabilization attempts. The other is a configuration line that tells Cargo to do nothing for a few days.

The second one is the more important change, and the reason is a number: 86 minutes.

🎉 The never type, ten years late

Rust 1.100 stabilizes the never type, spelled !. The Rust team’s own announcement says it plainly: after 10 years and 5 failed stabilization attempts, the never type is finally stable, and it completes a tracked project goal.

The narrow version of what changed is that ! can now be named anywhere. You could already write -> ! on a diverging function, and lean on the fact that return has type ! and coerces to anything. What you could not do was write Result<T, !> in a signature. That now type checks, so an infallible result can say so instead of pretending Infallible is a separate thing to remember.

It isn’t separate anymore. Infallible is now an alias for !, which is what the plan was from the beginning and one of the reasons this was so hard to land. Aliasing them required a breaking change: the never type fallback moves from () to ! in editions before 2024, so 2015, 2018, and 2021, matching what edition 2024 already did. The team shipped future compatibility warnings well ahead of time and released fixes to public crates that the change broke. In rare cases code that ignores the result of a function returning a generic parameter will behave differently, and the compiler may now flag more code as unreachable.

My honest read is that this is the clearest artifact of Rust’s culture that shipped this year. Ten years of arguing about inference is not indecision. It is the cost of refusing to put a footgun into an ecosystem where breakage is treated as an incident rather than a Tuesday.

⏱️ The line I actually turned on

Rust 1.100 also stabilizes dependency cooldowns under Cargo’s minimum publish age setting. In a repository’s .cargo/config.toml:

[registry]
global-min-publish-age = "3 days"

The semantics are worth being precise about, because the details are where the value lives. Versions whose pubtime is newer than your setting are treated as pubtime incompatible. With resolver.incompatible-publish-age at its default of deny, the resolver ignores them unless they are already recorded in Cargo.lock. The default value of global-min-publish-age is 0, so upgrading to 1.100 buys you exactly nothing until you write this line yourself.

You can scope it per registry, with registry.min-publish-age for crates.io and registries.<name>.min-publish-age for anything else. Cargo reports what it skipped, including the age of the version it passed over, so the resolver output turns into a small audit trail.

The failure mode is the design decision I find most interesting. If nothing satisfies both your version requirement and your age threshold, resolution errors out instead of quietly falling back:

error: failed to select a version for the requirement `some-package = "^1.3"`
version 1.3.0 is too new (published 2 days ago, minimum age 14 days)

RFC 3923 explains why. If the policy merely preferred older releases, an attacker could publish a malicious update and yank the safe versions, and the resolver would happily fall back to the malicious one. The August incident included exactly that yanking. Denying is the only setting that closes the route.

The escape hatch is an environment variable, CARGO_RESOLVER_INCOMPATIBLE_PUBLISH_AGE=allow, which you pair with cargo update --precise when you need a fix today. Once those versions land in the lockfile, later resolves keep them. That is the right shape for an override: loud, per invocation, and hard to leave on by accident.

The limits are real, and the RFC states them. cargo install ignores the check. Git and path dependencies have no pubtime, so they are out of scope. Registries that do not publish timestamps are never held back. And the RFC says directly that this is not a full solution to compromised dependencies and should not be relied on for security by itself.

🕰️ Eighty-six minutes

On August 20, 2026, the research team at Nextron Systems reported the proc-macro1 crate as malicious. The Rust Security Response Team confirmed it: the crate carried a build script that downloaded a payload. What followed is the part that matters here.

arrayref 0.3.10 was published at 07:15:00 UTC and deleted at 08:41:40 UTC. Online for 86 minutes. internment 0.8.7 lived 90 minutes. append-only-vec 0.1.9 lived 107. All three were published from the account of a maintainer the response team believed was not acting maliciously but whose machine or credentials were likely compromised, and the account was locked as a precaution.

The poisoned crates were clean at first glance. Their library source was the genuine upstream code, and nothing in it referenced the malicious crate. The entire compromise was one injected line in each manifest pointing at a typosquat of proc-macro2. Because the payload lived in that dependency’s build script, compiling a project that resolved it was enough. Nobody had to call anything.

The number I keep coming back to: RustSec advisory RUSTSEC-2026-0260 records that arrayref 0.3.10 was downloaded 2,285 times, which was less than 10% of arrayref download traffic across all versions, because most users already had an older version sitting in their lockfiles. The lockfile did most of the work by accident, simply because people do not update the same hour something ships. What the ecosystem lacked was a rule for the update window itself. Three days would have covered all three crates with room to spare.

I want to be fair to the timeline, though, because the coincidence is tempting. The cooldown was not a response to this attack. The tracking issue opened on May 18, the feature was in nightly Cargo by June 21, and the stabilization PR merged on August 28, eight days after arrayref came down. This is a feature that happened to be ready, not a feature the incident caused. The attack is the illustration, not the origin.

🧱 The rest of the release, briefly

The never type and the cooldown will get the attention, but 1.100 is a large release and some of the rest will change how library code looks.

The core of the Allocator API is stable, giving library authors the Allocator trait and allocator-aware constructors on Vec and Box: Vec::new_in, Vec::with_capacity_in, Vec::from_parts_in, Box::into_non_null_with_allocator. This is the foundation of the Allocators 1.0 goal, so expect the surface to grow across the collections rather than land all at once.

core::mem::DropGuard runs a closure when it drops, which means every exit path including ? and a panic. It implements Deref and DerefMut so you keep using the value normally, and dismiss returns the value without running the destructor, which gives you all-or-nothing rollback in a few lines.

std::mem::conjure_zst formalizes something people were doing badly. If you needed a zero-sized value in unsafe code, you transmuted () or called MaybeUninit::uninit().assume_init(). Now there is a dedicated unsafe function, and the release notes pick a good cautionary example: PyO3’s Python<'py> token is zero-sized and its existence proves you hold the GIL, so conjuring one from nothing would let safe code violate that precondition.

Cargo also reports unused dependencies now, through a new [lints.cargo] table with cargo::unused_dependencies set to warn by default. It only fires when you build every target that might use a dependency, so cargo check --bin foo stays quiet.

One change deserves a flag next to the cooldown: cargo install now uses the lockfile inside the .crate tarball by default, which makes --locked a no-op. So installing a binary crate now respects the publisher’s pinning, while the cooldown does not apply to that path at all. Two different trust decisions on the same command, and only one of them is yours to configure.

🧠 Final Thoughts

Rust shipped two theories of safety in one release. For the never type, the theory is that you spend a decade getting a detail right, absorb a breaking change across three editions, and warn everyone in advance. For dependency cooldowns, the theory is that you spend nothing, wait three days, and let other people do the detecting.

The second one is not a compromise. It is an accurate description of where the detection happens: someone else’s scanners, on someone else’s clock. Your build pipeline is never going to win that race, so the only thing you control is whether you are in the blast radius while it is being run.

The limit is worth saying out loud: a patient attacker simply waits out your cooldown, and a malicious release that escapes review for three days becomes a malicious release you install on day four. This buys time against the sloppy attacker, who is most of them, and does nothing against the careful one.

If you upgrade to 1.100 this week, put global-min-publish-age in the repository’s .cargo/config.toml rather than your personal dotfiles, so CI inherits it. Then add a scheduled job running cargo update with the override on, in a branch nobody merges, so you find breakage on your terms instead of when you need a patch.

Thanks for reading.

[ SIGNATURE VERIFIED // GPG 0x4EB27DB0 // PROVENANCE SECURE ]
➔ RETURN TO LOGS