B.1Rust editions in detail
Lesson 0.12 introduced editions: the mechanism that lets Rust make occasional breaking changes to its surface syntax without breaking the code already written. Each crate declares its edition in Cargo.toml, the compiler holds every crate to the rules of its own edition, and crates of different editions link together freely. That lesson promised a catalog of what the editions actually changed, deferred to here because most of it only makes sense once you know the language. Now you do.
This is a tour, not the full specification. Each edition's complete change list lives in the official Rust Edition Guide, which also documents the automatic migration (cargo fix --edition) for each one. What follows is the highlights, the changes you're most likely to notice or to read about.
Editions are not language versions
Worth restating before the list: there's one Rust compiler, always current, and it understands all editions. A new compiler release brings new features (new standard library functions, say) to every edition at once. An edition boundary is reserved for the rare change that would alter the meaning of code that already compiles. So almost everything Rust has added over the years works regardless of your edition; only the items below were edition-gated.
Rust 2015: the baseline
The 2015 edition is Rust as it stood at the 1.0 release, the language's first stable form. There's nothing to "change" here; it's the starting point every later edition is measured against, and it still compiles unchanged today. A few things look dated in 2015 code if you come across it. Dependencies had to be pulled into scope with an explicit extern crate serde; line at the top of the crate. Trait objects were written as a bare trait name (Box<Error>) rather than with today's dyn keyword. And the module system had some rough corners around how paths in use statements were resolved, which is exactly what the next edition set out to fix.
Rust 2018: the module system, mostly
Shipped with Rust 1.31 in late 2018, this was the largest edition change, and most of it was about modules and paths, the material of chapter 13. If you learned modules in this course, you learned the 2018 rules, so these will read as "the way it works" rather than "the change."
The headline was path clarity. The extern crate line went away: a dependency listed in Cargo.toml is simply usable, and paths in use statements now start unambiguously from a crate name, or from crate:: for your own crate root, self::, and super:: (lesson 13.1). The old requirement that every module directory contain a mod.rs file was relaxed too, so you can write foo.rs beside a foo/ directory instead (lesson 13.4).
Alongside the module work, 2018 reserved the async and await keywords, clearing the way for async/await to stabilize shortly after in compiler version 1.39 (chapter 23). It also made dyn Trait the standard way to write a trait object (chapter 16), with the bare-trait-name form deprecated. And the borrow checker quietly got much smarter around this time with non-lexical lifetimes, which let a borrow end as soon as it's last used rather than at the end of its block, accepting a lot of obviously-correct code the old checker rejected. You've been relying on that the whole course without knowing it had a name.
Rust 2021: smaller, sharper
Rust 1.56, in late 2021, brought a tighter set of changes. The one most likely to affect everyday code was disjoint closure captures. Before 2021, a closure that used point.x captured the entire point; from 2021 on it captures just the field point.x, which means the rest of point stays available to other code. This removes a class of borrow-checker annoyances around closures (chapter 19) where you previously had to manually destructure a struct to satisfy the compiler.
Two more changes you've benefited from directly. Arrays became iterable by value: for x in [1, 2, 3] now hands you each value, where before it gave references (a consequence of arrays implementing IntoIterator, lesson 19.6). And the prelude (the set of names available without importing, lesson 13.1) gained TryFrom, TryInto, and FromIterator, so the fallible conversions from chapter 16 work without a use line.
The 2021 edition also made the panic! macro consistent with println!, so panic!("value: {}", x) formats its arguments the way you'd expect, and reserved some new syntax for the future, including the prefix#, prefix"...", and prefix'...' forms. That reservation is what later allowed C string literals, written c"...", to exist.
Rust 2024: tightening the unsafe edges
The newest edition shipped with Rust 1.85 in early 2025. Its theme is precision: several changes tighten defaults that were a little too loose, especially around unsafe code (chapter 25) and the exact moment temporary values are dropped.
On the unsafe side, external function declarations now go in an unsafe extern block rather than a plain extern block, making the unsafety visible at the declaration (chapter 25). Certain attributes that affect linking, such as #[no_mangle], must now be written as #[unsafe(no_mangle)], since getting them wrong can break safety. And std::env::set_var and remove_var became unsafe functions, because changing environment variables while other threads are running can cause real undefined behavior (lesson 20.4). References to a static mut are also now disallowed by default, steering you toward the safer interior-mutability tools from chapter 21.
The other cluster of changes is about temporary scopes, the precise point at which a short-lived value gets dropped. The rules for temporaries created in an if let scrutinee and in a function's tail expression were adjusted so that things are dropped sooner and more predictably, which occasionally changes the order destructors run in. It's the kind of refinement you'll almost never notice but that closes some genuine footguns. Rounding out the edition: the return-position impl Trait lifetime-capture rules were made more uniform, the gen keyword was reserved for a future generators feature, and on the tooling side Cargo gained a dependency resolver that pays attention to each crate's declared minimum Rust version when choosing dependency versions.
What you actually do about it
Nothing changes the advice from lesson 0.12: start new projects on the latest edition, which cargo new does for you, and leave the edition line alone until you deliberately migrate. When you do migrate an older project, cargo fix --edition rewrites the mechanical parts for you and the compiler flags anything it can't handle automatically. The reason this appendix can exist at all, a clean per-edition list rather than a migration horror story, is the whole point of the design: Rust gets to keep improving its surface syntax, and your old code keeps compiling regardless.
The final appendix is shorter and not technical at all. It's about where you go from here.