AL+

Language: RO (română) EN (English)

Email us: paul@alplustech.com

Rust range bounds deserve a separate life from iterator progress

Separate reusable Rust bounds from iterator progress, use RangeBounds to accept old and new range types, and distinguish stable types from range syntax.

Citiți articolul în română

A range stored in a document model usually describes a region. An iterator describes how far a traversal has reached. Giving both jobs to one value makes copying surprisingly difficult to explain. A reusable description should preserve its bounds; a traversal must preserve the fact that some items have already been consumed. An API becomes easier to reason about when it gives those two meanings separate representations.

Rust's range redesign provides a useful example of that separation. The practical question for a library author is which meaning a parameter needs to carry. A function that selects a slice needs bounds. A function that resumes processing needs progress. Choosing the trait or concrete type after answering that question avoids making callers inherit state that the operation never intended to use.

A region and its next item answer different questions

Consider an illustrative text annotation that marks a region in a document. Two independent operations may need the same annotation: one displays the selected text and another checks its contents. Neither operation should change the annotation merely by reading it. Copying the annotation should therefore copy the selection, with no implicit relationship to how much work either operation has completed.

A traversal has a different contract. After an operation consumes several items, the next operation may be expected to continue from that point. Copying its state produces another traversal at a particular position. That can be intentional, but it is an action with consequences. If the copy happens implicitly when a method takes its receiver by value, a reader can mistake work on the copy for progress on the original.

The motivation in RFC 3550 identifies that interaction between Copy and Iterator. The useful design lesson is to make the start of traversal visible. A value that implements IntoIterator can describe what to traverse; conversion creates the object that tracks consumption. The copyable description and the resulting stateful iterator then have different responsibilities.

This does not make every copy of an iterator wrong. It makes an implicit copy a poor default when readers normally expect advancement. Explicit duplication can still express a deliberate branch in processing. The API should let a reviewer distinguish that decision from ordinary reuse of a region description.

Stable types and syntax have separate timelines

The Rust 1.96 announcement, dated 28 May 2026, stabilizes new core::range types that separate these roles. Their copyability depends on copyable bounds. Crucially, that release still has range expressions such as 0..1 produce the legacy types. A stable library type does not, by itself, change the type produced by familiar syntax.

That distinction matters when evaluating examples. A demonstration written around the proposal's intended syntax cannot establish the behavior of a particular released compiler. The RFC carries a start date of 18 December 2023 and proposed an Edition 2024 transition. That start date is neither acceptance nor delivery, and the proposed timing did not occur as written. For the historical release boundary, the release announcement controls the claim.

For a team maintaining a library, this means treating migration as an interface decision rather than a search and replace exercise. A parameter may intentionally accept old and new callers together. A stored field may need a concrete representation. Those choices can be made independently; adopting a representation internally need not imply that every caller already constructs it through range syntax.

Accept the information the operation needs

The release points library authors toward RangeBounds for interfaces that can accept both representations. That is a useful fit when the operation only asks where a region begins and ends. It communicates that the caller supplies a description, without requiring one particular implementation of an iterable range.

The trait does not settle the operation's entire contract. An API still needs to define how it interprets included, excluded or unbounded ends. It also needs to establish whether the supplied bounds make sense for the data being accessed. Accepting a more general bounds representation does not turn an invalid region into a valid one.

For an operation that actually consumes items, IntoIterator or Iterator expresses a different requirement. The former can accept something from which traversal begins; the latter can deliberately accept a traversal already in progress. Treating these as distinct questions gives useful names to behavior that otherwise hides behind a convenient parameter type.

One pass can remain simple

The strongest case for a stateful range is an ordinary one pass algorithm. A local iterator whose progress is used immediately is convenient, and a separate reusable descriptor may add little. Code that never stores or shares a range description need not acquire another abstraction merely to follow a broader design preference.

The distinction earns its place when a value crosses an interface or survives beyond one traversal. Review stored annotations, reusable selections and parameters passed to several operations first. Ask whether their meaning should change after iteration. If it should, document the continuation point. If it should not, store bounds separately from the consuming iterator.

For custom software development, that is a concrete interface requirement worth agreeing before implementation: which values describe work, and which values record work already done. Keep the reusable region intact, and make the creation of each traversal an explicit part of the design.