| name | rust-modern-apis |
| description | Reference for stable Rust APIs added in versions 1.89 through 1.98 (August 2025 - August 2026). Use this skill whenever writing, reviewing, or refactoring Rust code — especially when you notice patterns that were verbose before newer APIs existed, when MSRV allows it, or when the user mentions modernizing Rust code, upgrading MSRV, or using "the latest Rust features". Also trigger when reviewing Rust code for improvements, migrations, or when a user asks "can this be simpler in modern Rust?" Proactively suggest newer APIs when you see patterns like manual UTF-8 truncation, path extension manipulation, advisory file locking via external crates, ignoring `retain` removal results, verbose `try_into().unwrap()` for fixed arrays, `compare_exchange` loops for atomic update, `cfg_if` crate usage, matching `0`/`1` to `bool`, `assert!(matches!(...))` in tests, hand-rolled bit-manipulation idioms like `1 << (BITS - 1 - x.leading_zeros())`, chained `strip_prefix`/`strip_suffix`, `.map(f).unwrap_or_default()`, the `itoa` crate for integer formatting, or manual UTF-16LE/BE decoding. |
Modern Rust APIs (1.89 – 1.98)
This skill is a lookup table for stable Rust APIs added after 1.88. Use it when writing or reviewing Rust code — replace older verbose patterns with newer concise ones where the project's MSRV allows.
How to use this skill
-
Check the project's MSRV first. Look at Cargo.toml for rust-version = "X.Y". Only suggest APIs available at or below the MSRV. If MSRV is lower than an API's version, either skip the suggestion or note that "raising MSRV to X.Y unlocks this."
-
Scan the code for trigger patterns (see section below). Each trigger maps to a newer API. When you see one, suggest the replacement with a brief before/after.
-
For detailed API info, read the matching reference file in references/. Files are organized by domain, not by version.
-
Don't over-apply. Some patterns are fine as-is. Only suggest a change when the new API is clearly better (shorter, safer, or fixes a subtle bug).
Trigger patterns — fastest lookup
Scan for these code shapes first. Each points to a concrete API that replaces it.
| Code pattern spotted | Modern replacement | Version | Details |
|---|
Duration::from_secs(60 * N) or ad-hoc minute math | Duration::from_mins(N) | 1.91 | time.md |
Duration::from_secs(60 * 60 * N) | Duration::from_hours(N) | 1.91 | time.md |
path.with_extension("X.tmp") (trying to ADD suffix) | path.with_added_extension("tmp") | 1.91 | paths.md |
Manual file_stem + format! to compose meta paths | path.with_added_extension(...) | 1.91 | paths.md |
file_stem() returning wrong thing for .tar.gz | Path::file_prefix() | 1.91 | paths.md |
path.to_string_lossy() == "literal" | path == Path::new("literal") or path == "literal" | 1.91 | paths.md |
Ipv4Addr::new(a, b, c, d) from [u8; 4] | Ipv4Addr::from_octets(bytes) | 1.91 | net.md |
Ipv6Addr::new(a, b, c, d, e, f, g, h) from segments | Ipv6Addr::from_segments(segs) | 1.91 | net.md |
Version → MSRV gate
When suggesting an API, check MSRV first. Quick reference:
- MSRV 1.87+:
Vec::extract_if, LinkedList::extract_if
- MSRV 1.88+:
HashMap::extract_if, HashSet::extract_if
- MSRV 1.89+:
File::lock family, Result::flatten, NonNull::from_ref/from_mut
- MSRV 1.90+: unsigned
checked_sub_signed, CStr cross-comparisons, lld default linker on Linux
- MSRV 1.91+:
Duration::from_mins/from_hours, Path API expansion, strict_* arithmetic, BTreeMap/Set::extract_if, ceil_char_boundary, iter::chain, array::repeat, Ipv*::from_octets
- MSRV 1.92+:
RwLockWriteGuard::downgrade (std only), Box/Arc/Rc::new_zeroed, NonZero::div_ceil
- MSRV 1.93+:
slice::as_array, fmt::from_fn, VecDeque::pop_front_if/pop_back_if, String::into_raw_parts, Vec::into_raw_parts, Duration::from_nanos_u128, char::MAX_LEN_UTF8/MAX_LEN_UTF16, MaybeUninit slice API, asm_cfg
- MSRV 1.94+:
slice::array_windows, LazyCell/Lock::get/get_mut/force_mut, Peekable::next_if_map, TryFrom<char> for usize
- MSRV 1.95+:
bool: TryFrom<{integer}>, atomic update/try_update on AtomicPtr/Bool/Isize/Usize, cfg_select! macro, core::hint::cold_path(), if let guards on match arms, core::range::RangeInclusive/RangeInclusiveIter, MaybeUninit<[T; N]> array conversions (//), , pointer /, const / /
Full changelog by version lives in references/changelog.md if you need to explain a release to the user or find something not in the trigger table.
When a suggestion is NOT appropriate
Don't push the replacement if:
- MSRV forbids it. If
rust-version = "1.88" and the API needs 1.91, either flag the MSRV gap or stay silent — don't produce code that won't compile.
- Unsafe rules forbid it. If the project has
unsafe_code = "deny" (check Cargo.toml [workspace.lints]), skip APIs that require unsafe blocks even if they'd be shorter — e.g., Box::new_zeroed returns Box<MaybeUninit<T>> and requires unsafe { assume_init() } afterward.
- The old pattern is load-bearing. Sometimes
retain with a side effect inside the closure is intentional for atomicity under a lock. Read the surrounding code before proposing extract_if.
- The types don't match.
tokio::sync::RwLock does not have downgrade — that's std::sync::RwLock only. Similarly, parking_lot::RwLock has its own upgrade/downgrade API, not the std one.
- Context is a test or benchmark. Low value to change test code for style alone unless the test is flaky because of the old pattern.
Migration mode: how to present changes
When you find a replacement candidate in code, present it as a before/after diff with a one-line "why":
let tmp = path.with_extension("tmp");
let tmp = path.with_added_extension("tmp");
Don't produce walls of diffs for trivial cosmetic changes. Batch suggestions logically (all time/duration in one section, all path handling in another) if there are many.
Reference files
Read these only when you need the details. Each file covers one domain across all versions:
- references/changelog.md — full release notes by version (1.89-1.98) — use when the user asks about a specific release
- references/paths.md —
Path/PathBuf API additions (1.91 mainly)
- references/strings.md —
str and char additions
- references/time.md —
Duration additions
- references/arithmetic.md — integer arithmetic (
strict_*, unchecked_*, carrying_*, bit-manipulation like isolate_highest_one/bit_width, etc.)
- references/iterators.md — iterator/chain/array helpers
- references/collections.md — Vec/VecDeque/BTree/HashMap additions
- references/slices.md —
[T] and [MaybeUninit<T>] APIs
- references/io-files.md —
File::lock, pipe, seek additions
- references/sync.md —
LazyLock, RwLock, Pin<Box<T>> Default, Box::new_zeroed
- references/net.md — IP address constructors and network APIs
- references/formatting.md —
fmt::from_fn, debug formatting changes