Rust 1.80.0: Exclusive Ranges in Patterns, Stabilized APIs, and More

Rust 1.80.0: Exclusive Ranges in Patterns, Stabilized APIs, and More
Estimated reading time: approx. 8 minutes
- Lazy Initialization with LazyCell/LazyLock: Rust 1.80.0 introduces `LazyCell` (single-threaded) and `LazyLock` (thread-safe) for efficient, on-demand data initialization, ideal for expensive resources, offering a more idiomatic approach than previous methods.
- Enhanced Configuration Safety: Cargo 1.80, with the `unexpected_cfgs` lint enabled by default, proactively checks `cfg` names and values, preventing typos and misconfigurations in conditional compilation directives.
- Exclusive Ranges in Pattern Matching: The new `a..b` and `..b` syntax in `match` patterns allows for clearer, more natural expression of numerical boundaries, reducing off-by-one errors and improving code readability.
- Extensive Stabilized APIs: This release significantly expands the standard library with numerous APIs, including `Default` implementations for `Rc`/`Arc` types, `Duration` division, `Option::take_if`, and `NonNull` methods, enhancing the developer’s toolkit.
- Actionable Steps: Developers are encouraged to update their Rust toolchain, implement lazy initialization with `LazyCell` or `LazyLock` for costly resources, and refactor pattern matching with exclusive ranges to leverage these new features effectively.
- Revolutionizing Data Initialization with LazyCell and LazyLock
- Enhanced Configuration Safety with Checked cfg Names and Values
- Precise Pattern Matching with Exclusive Ranges
- A Plethora of Stabilized APIs for Enhanced Development
- Get Started: Actionable Steps for Rust 1.80.0
- Conclusion: A Stepping Stone to Even Better Rust Development
- Frequently Asked Questions (FAQ)
The Rust team is happy to announce a new version of Rust, 1.80.0. Rust is a programming language empowering everyone to build reliable and efficient software. This release marks another significant stride in Rust’s journey, introducing features that streamline development, enhance code safety, and boost overall productivity. From advanced lazy initialization types to more expressive pattern matching and improved build-time configuration checks, Rust 1.80.0 brings a suite of powerful tools for developers.
For those eager to dive into the latest capabilities, updating is straightforward. If you have a previous version of Rust installed via rustup
, you can get 1.80.0 with:
$ rustup update stable
If you don’t have it already, you can get rustup
from the appropriate page on our website, and check out the detailed release notes for 1.80.0. This guide will walk you through the most impactful changes, demonstrating how they can elevate your Rust projects.
Revolutionizing Data Initialization with LazyCell and LazyLock
A standout feature in Rust 1.80.0 is the stabilization of LazyCell
and LazyLock
. These innovative “lazy” types offer a sophisticated approach to data initialization, delaying the creation of their data until it’s first accessed. This approach is invaluable for optimizing performance, especially when dealing with computationally expensive setups or resources that might not be used in every execution path.
LazyCell
and LazyLock
are evolutions of the OnceCell
and OnceLock
types stabilized in 1.70, with a key difference: they encapsulate the initialization function directly within the cell. This completes the seamless integration of functionality previously adopted into the standard library from popular community crates like lazy_static
and once_cell
, offering a more idiomatic and convenient way to handle delayed initialization.
LazyLock
stands out as the thread-safe option, making it perfectly suited for scenarios involving shared static values. Consider its application for initializing a global timestamp:
use std::sync::LazyLock;
use std::time::Instant; static LAZY_TIME: LazyLock<Instant> = LazyLock::new(Instant::now); fn main() { let start = Instant::now(); std::thread::scope(|s| { s.spawn(|| { println!("Thread lazy time is {:?}", LAZY_TIME.duration_since(start)); }); println!("Main lazy time is {:?}", LAZY_TIME.duration_since(start)); });
}
In this example, LAZY_TIME
is initialized only once, by whichever thread or scope accesses it first. Both the spawned thread and the main scope will subsequently read the exact same Instant
, ensuring consistency without explicit synchronization logic in their access patterns. This design simplifies concurrent code by abstracting away the initialization mechanism, contrasting with the more manual OnceLock::get_or_init()
.
Conversely, LazyCell
provides similar deferred initialization benefits but without the overhead of thread synchronization. While it doesn’t implement Sync
(and thus isn’t suitable for global statics), it thrives in contexts like thread_local!
statics, where each thread maintains its own distinct initialization. Both types can be flexibly integrated into various data structures, bringing the power of lazy evaluation wherever thread-safety requirements dictate.
Enhanced Configuration Safety with Checked cfg Names and Values
Building on the --check-cfg
flag stabilized in Rust 1.79, Cargo 1.80 now natively enables these checks for all cfg
names and values it recognizes. This includes feature names declared in Cargo.toml
and dynamic outputs from build scripts via cargo::rustc-check-cfg
. This enhancement significantly boosts the reliability of conditional compilation directives within your projects.
The core of this safety improvement lies in the unexpected_cfgs
lint, which is now warned by default. This lint diligently reports any unexpected cfg
conditions, acting as an early warning system for typos or misconfigurations that could lead to subtle bugs. For instance, if your project relies on an optional rayon
dependency, a simple typo in a #[cfg]
attribute is immediately flagged:
fn main() { println!("Hello, world!"); #[cfg(feature = "crayon")] rayon::join( || println!("Hello, Thing One!"), || println!("Hello, Thing Two!"), );
}
Attempting to compile this code with the typo would yield a clear warning:
warning: unexpected `cfg` condition value: `crayon` --> src/main.rs:4:11 |
4 | #[cfg(feature = "crayon")] | ^^^^^^^^^^-------- | | | help: there is a expected value with a similar name: `"rayon"` | = note: expected values for `feature` are: `rayon` = help: consider adding `crayon` as a feature in `Cargo.toml` = note: see <https://doc.rust-lang.org/nightly/rustc/check-cfg/cargo-specifics.html> for more information about checking conditional configuration = note: `#[warn(unexpected_cfgs)]` on by default
Crucially, this warning is emitted regardless of whether the actual rayon
feature is enabled, preventing issues before they can manifest at runtime. Developers gain increased confidence that their conditional compilation logic behaves as intended. Furthermore, the [lints]
table within Cargo.toml
provides flexibility to extend the list of known names and values for custom cfg
attributes, allowing you to tailor this powerful checking mechanism to your specific project needs:
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(foo, values("bar"))'] }
This proactive linting reduces debugging time and ensures that your codebase’s configuration remains robust and error-free.
Precise Pattern Matching with Exclusive Ranges
Rust’s pattern matching capabilities have become even more expressive with the introduction of exclusive endpoints in range patterns. Developers can now utilize the familiar a..b
or ..b
syntax, akin to Range
and RangeTo
expression types, directly within match
statements and other pattern contexts. This seemingly small addition significantly enhances the clarity and conciseness of range-based logic.
Previously, patterns were limited to inclusive ranges (a..=b
or ..=b
) or open-ended ranges (a..
). The new exclusive range pattern allows for a more natural expression of numerical boundaries, especially when defining contiguous ranges:
pub fn size_prefix(n: u32) -> &'static str { const K: u32 = 10u32.pow(3); // 1000 const M: u32 = 10u32.pow(6); // 1_000_000 const G: u32 = 10u32.pow(9); // 1_000_000_000 match n { ..K => "", // 0 to 999 K..M => "k", // 1000 to 999_999 M..G => "M", // 1_000_000 to 999_999_999 G.. => "G", // 1_000_000_000 and up }
}
This example beautifully illustrates how constant values can now serve as both the exclusive end of one pattern and the inclusive start of the next, eliminating the need for awkward adjustments like K - 1
in previous versions. Such a pattern is not only more readable but also less prone to off-by-one errors.
To ensure this new flexibility doesn’t introduce new pitfalls, exhaustiveness checking has been further fortified to better identify gaps in pattern matching. Complementing this, new lints such as non_contiguous_range_endpoints
and overlapping_range_endpoints
will actively assist developers in detecting and resolving cases where patterns might inadvertently create gaps or overlaps, guiding them towards correct usage of exclusive versus inclusive ranges.
A Plethora of Stabilized APIs for Enhanced Development
Rust 1.80.0 also delivers a substantial list of newly stabilized APIs, enriching the standard library and expanding the toolkit available to developers. These additions streamline common tasks, improve performance, and enhance the overall expressiveness of the language. While the full list is extensive, here are some notable highlights:
impl Default for Rc<CStr>
,Rc<str>
,Rc<[T]>
,Arc<str>
,Arc<CStr>
,Arc<[T]>
: Providing default implementations for these common reference-counted types simplifies initialization, making them more ergonomic to use.impl IntoIterator for Box<[T]>
andimpl FromIterator<String> for Box<str>
/FromIterator<char> for Box<str>
: These additions make working with boxed slices and strings more intuitive, aligning their behavior with other collection types.LazyCell
andLazyLock
: As discussed, these types bring robust lazy initialization to the standard library.Duration::div_duration_f32
andDuration::div_duration_f64
: Facilitate precise floating-point division ofDuration
values.Option::take_if
: Offers a concise way to take a value out of anOption
only if a predicate is met, simplifying conditional logic.- Numerous
NonNull
methods (e.g.,offset
,byte_offset
,read
,write
,copy_to
): These additions enhance the capabilities for interacting with raw pointers in a safer, more controlled manner, crucial for low-level programming and FFI. - Slice and string utility functions (e.g.,
<[T]>::split_at_checked
,str::trim_ascii
,<[u8]>::trim_ascii_start
): These provide checked bounds for splitting and efficient ASCII trimming, improving code safety and common string/slice manipulation tasks. Ipv4Addr::BITS
,Ipv4Addr::to_bits
,Ipv4Addr::from_bits
, and theirIpv6Addr
counterparts: Offer direct bit manipulation for IP addresses, useful in network programming.Vec::<[T; N]>::into_flattened
and related slice methods: Provide efficient ways to flatten vectors of arrays into a single, contiguous slice.
Additionally, several APIs, including <[T]>::last_chunk
and BinaryHeap::new
, are now stable in const
contexts, expanding the possibilities for compile-time computations and initialization.
Get Started: Actionable Steps for Rust 1.80.0
Leveraging the new features in Rust 1.80.0 can significantly improve your development workflow and the quality of your applications. Here are three immediate steps you can take:
- Update Your Rust Toolchain: The first step is always to ensure you’re running the latest stable version. Open your terminal and execute
rustup update stable
. This simple command will bring your environment up to date, giving you access to all the features and bug fixes discussed. - Implement Lazy Initialization for Expensive Resources: Identify parts of your application where resource initialization (e.g., loading large configuration files, establishing database connections, complex calculations) is costly and might not be immediately necessary. Replace manual lazy patterns or
OnceCell
where initialization logic is part of the cell withLazyCell
orLazyLock
. This will defer resource allocation until truly needed, potentially improving startup times and reducing memory footprint. - Refactor Pattern Matching with Exclusive Ranges: Review your existing
match
statements that use numerical ranges. If you have patterns likeX..=(Y-1)
followed byY..=Z
, consider refactoring them to use the new exclusive range syntax, e.g.,X..Y
followed byY..Z
. This will make your code more readable, more aligned with mathematical notation, and less prone to off-by-one errors.
Conclusion: A Stepping Stone to Even Better Rust Development
Rust 1.80.0 reinforces the language’s commitment to reliability, efficiency, and developer experience. The introduction of LazyCell
and LazyLock
provides elegant solutions for deferred initialization, while the enhanced --check-cfg
flag, coupled with the unexpected_cfgs
lint, significantly strengthens build configuration safety. The stabilization of exclusive ranges in patterns brings a new level of clarity and expressiveness to control flow, making complex logic easier to write and understand. Coupled with a broad array of other stabilized APIs, this release collectively empowers developers to write more robust, maintainable, and performant Rust applications.
As Rust continues to evolve, each stable release introduces carefully considered improvements that push the boundaries of what’s possible in systems programming. We encourage you to explore these new features, experiment with them in your projects, and experience the tangible benefits they offer. If you’d like to help us out by testing future releases, you might consider updating locally to use the beta channel (rustup default beta
) or the nightly channel (rustup default nightly
). Please report any bugs you might come across!
Check out everything that changed in Rust, Cargo, and Clippy for a comprehensive overview.
Many people came together to create Rust 1.80.0. We couldn’t have done it without all of you. Thanks!
Photo by Lay Naik on Unsplash
Frequently Asked Questions (FAQ)
Q: What are the main benefits of Rust 1.80.0?
A: Rust 1.80.0 primarily introduces LazyCell
and LazyLock
for efficient deferred initialization, enhances build-time configuration safety with the unexpected_cfgs
lint, and adds exclusive ranges to pattern matching for clearer code. It also stabilizes numerous other APIs.
Q: How do LazyCell and LazyLock differ from OnceCell and OnceLock?
A: While both sets of types provide one-time initialization, LazyCell
and LazyLock
encapsulate the initialization function directly within the cell, making them more convenient and idiomatic for lazy evaluation. LazyLock
is thread-safe, suitable for shared statics, while LazyCell
is for single-threaded or thread_local!
contexts.
Q: What problem does “checked cfg” solve in Rust 1.80.0?
A: The “checked cfg” feature, enabled by Cargo 1.80 and the unexpected_cfgs
lint, proactively identifies typos or misconfigurations in conditional compilation directives (#[cfg]
). This prevents subtle bugs by warning developers about unexpected cfg
names or values, even if the feature isn’t currently enabled.
Q: How do exclusive ranges improve pattern matching?
A: Exclusive ranges (a..b
, ..b
) allow developers to define numerical boundaries in match
statements more precisely and naturally. This eliminates the need for (Y-1)
adjustments in inclusive ranges, making code more readable and reducing the likelihood of off-by-one errors.
Q: What should I do first to start using Rust 1.80.0 features?
A: Begin by updating your Rust toolchain using rustup update stable
. Then, consider refactoring parts of your code to use LazyCell
or LazyLock
for expensive, deferred initializations and update match
statements to utilize the new exclusive range patterns for improved clarity.