Representative interview topic

Coding Interview: Migrating to Rust 1.96 Range Types and assert_matches

CodingMedium
Offer.cc Editorial TeamPublished Updated

Question

A Rust library must store copyable slice ranges and show actual values when tests fail to match a pattern. How would you adopt Rust 1.96 range types and assert_matches while avoiding old-range and compatibility traps?

Prompt and context

You maintain a parser library whose index ranges are copied by several lightweight handles, and whose tests should print values when a pattern fails. The project currently uses legacy core::ops ranges and matches!, and is upgrading to Rust 1.96. Explain the new range types, iteration semantics, public APIs, assertion macros, and release plan.

What the interviewer evaluates

  • Whether you know that legacy Range implements Iterator, while new core::range types use IntoIterator and can be Copy.
  • Whether you avoid assuming the 0..n syntax has already switched to the new type.
  • Whether you know assert_matches! is not in the prelude and must be imported explicitly for diagnostics.
  • Whether you handle MSRV, documentation, macro expansion, Wasm linking changes, and rollback.

Clarifying questions to ask

  1. Is the library’s MSRV Rust 1.96, or must older compilers remain supported?
  2. Must a range be stored and iterated later, or consumed in one loop?
  3. Should the public API accept any RangeBounds, or expose a concrete range type?
  4. Does the Wasm build intentionally rely on undefined imports, and does Rust 1.96 require an explicit linker argument?

30-second answer framework

I would store copyable interval handles with core::range types and iterate through an explicit IntoIterator conversion. Generic APIs would prefer RangeBounds to avoid locking callers to one implementation. Tests would explicitly import core::assert_matches to retain pattern checks while printing actual values. Before migration I would pin the MSRV, verify that 0..n still creates a legacy type, add type and behavior tests, and separately validate Rust 1.96’s stricter handling of undefined symbols when linking Wasm.

Step-by-step deep dive

1. Separate the two range semantics

Rust 1.96 stabilizes core::range::Range, RangeFrom, RangeInclusive, and their associated iterators. The new types implement IntoIterator, so they can be stored in Copy structures; existing range syntax still creates legacy types for now and will change in a future edition. Code review must inspect type signatures rather than infer a type from 0..n.

2. Design library APIs and lifetimes

If an API only reads bounds, accept RangeBounds to support legacy and new ranges. If it stores and copies a range, use the new type and convert to an iterator at the boundary. Slice access still validates positions and character boundaries; Copy does not remove bounds checks. Document the MSRV so downstream users do not fail unexpectedly on an older toolchain.

3. Use pattern assertions for diagnostics

assert_matches! and debug_assert_matches! are pattern assertions that show the value on failure. They are not in the prelude, so import them in the test module. Do not use debug_assert_matches! as a production safety check because release builds remove debug assertions. Match narrow fields of error enums to avoid printing sensitive payloads.

4. Assess upgrade risk

Rust 1.96 also tightens linking for Wasm targets: --allow-undefined is no longer passed by default. If the project intentionally relies on imports, configure the linker argument explicitly or annotate the import module, then build Wasm in CI. Run the old-toolchain matrix, documentation examples, tests, and binary artifact checks; keep the compiler and lockfile rollback path ready.

High-quality sample answer

I would write the MSRV and supported targets into the release contract. A stored handle uses Rust 1.96’s core::range::Range because it is copyable and iterates through IntoIterator; generic functions accept RangeBounds so callers are not tied to one concrete type. The code does not assume that 0..n is already the new type; type checks and behavior tests prove the intended semantics. Test modules explicitly import core::assert_matches for narrow pattern diagnostics while retaining matches! for simple booleans. The migration also validates Wasm undefined symbols: if default linking now fails, only intentional imports receive explicit --allow-undefined and an import-module annotation. CI runs the MSRV, latest stable, Wasm build, and documentation examples, and any failure blocks release.

Common mistakes

  • Assuming 0..n automatically produces a core::range type in Rust 1.96.
  • Treating a new range as an Iterator and calling it directly despite its IntoIterator design.
  • Relying on a prelude import for assert_matches!.
  • Using debug assertions as production safety checks.
  • Skipping bounds, overflow, or character-boundary validation because a range is Copy.
  • Ignoring the Rust 1.96 change in Wasm undefined-symbol linking.

Follow-up questions and answers

Why not change every API to the new Range type?

A concrete type increases MSRV and compatibility pressure. RangeBounds keeps callers flexible; use the new type only where an internal structure must store and copy it.

When should assert_matches differ from matches?

Use matches! for a boolean result. Use assert_matches! when a test failure should show the actual value and pattern. Neither replaces business error handling or security validation.

What if a Wasm import must remain?

First prove that the import is an intentional contract rather than a missing linker configuration. Then restore it with explicit --allow-undefined and wasm_import_module annotations, and test the symbol list and runtime behavior in CI.

Public sources

Related questions

Related interview tool

Use Screenshot for a coding prompt

Capture the problem, then work through the constraints, solution, code, edge cases, and complexity in order.

View the tool