Rust 1.75 and 1.76: Improvements Noticeable Daily
Table of contents
- Key takeaways
- Rust 1.75: async fn in traits
- Return-position impl Trait in traits
- Rust 1.75 also stabilises: byte-level pointer arithmetic
- Rust 1.75 also brings: automatic cross-crate inlining
- Rust 1.76: ABI compatibility and standard library conveniences
- Compatibility and upgrading
- Conclusion
- Sources
Updated: 2026-07-12
Rust 1.75 stabilises async fn in traits, return-position impl Trait, and several byte-level pointer methods such as byte_add. Rust 1.76 adds an ABI guarantee between char and u32, plus convenience utilities like Result::inspect and type_name_of_val. Two releases that add real ergonomics without flashy gestures.
Rust 1.75 (December 2023) and Rust 1.76 (February 2024) are the kind of releases developers appreciate in retrospect: no flashy headlines, but improvements felt in real code. The most significant change lands in 1.75: the stabilisation of async fn in traits, a limitation that had been forcing use of the async-trait crate for years, together with return-position impl Trait in traits and a batch of byte-level pointer methods. Rust 1.76 is a more modest release that closes ergonomics gaps in the standard library and formalises an ABI compatibility guarantee. Together, they leave async code and low-level systems code somewhat cleaner.
Key takeaways
-
async fnin traits is now stable in Rust 1.75: it removes theasync-traitcrate dependency for the non-dyncase. -
Return-position
impl Traitin traits completes support for opaque return types without boxing. -
The same 1.75 release stabilises
pointer::byte_addand related methods for byte-level pointer arithmetic. -
Rust 1.76 guarantees that
charandu32are ABI-compatible, and adds convenience utilities such asResult::inspectandtype_name_of_val. -
Both releases are additive: existing code keeps compiling unchanged, and migration is optional and gradual.
Rust 1.75: async fn in traits
The inability to declare async fn directly in a trait was one of the most frustrating limitations in the Rust ecosystem for those coming from other languages. The technical cause imposed the compromise: async functions return an opaque type (impl Future) that can vary in size depending on the implementation, which breaks the compiler’s trait model for statically-sized types.
The solution stabilised in 1.75 uses implicit desugaring: the compiler automatically converts async fn in the trait into an associated future type. For the non-dyn case, this works without boxing:
trait AsyncDatabase {
async fn query(&self, sql: &str) -> Result<Vec<Row>, DbError>;
async fn execute(&self, sql: &str) -> Result<u64, DbError>;
}
struct Postgres { /* ... */ }
impl AsyncDatabase for Postgres {
async fn query(&self, sql: &str) -> Result<Vec<Row>, DbError> {
todo!()
}
async fn execute(&self, sql: &str) -> Result<u64, DbError> {
todo!()
}
}
Before 1.75, this required #[async_trait] from the eponymous crate, which boxes futures and adds heap allocation overhead on each call. For high-performance code, precisely the use case where Rust is most used, that overhead was unacceptable.
The remaining limitation: async fn in a trait does not work directly with dyn Trait. For dynamic dispatch with async traits, explicit boxing or the async-trait crate is still needed. This covers most practical cases, but not all. The full design, including this limitation, is documented in the official async fn and RPIT in traits announcement[1].
Return-position impl Trait in traits
The same release completes another puzzle piece: return-position impl Trait (RPIT) in trait definitions. This allows declaring functions in traits that return types implementing a trait, without needing boxing or explicit associated types:
trait Container {
fn iter(&self) -> impl Iterator<Item = i32>;
fn into_sorted(self) -> impl Iterator<Item = i32>
where
Self: Sized;
}
struct MyVec(Vec<i32>);
impl Container for MyVec {
fn iter(&self) -> impl Iterator<Item = i32> {
self.0.iter().copied()
}
fn into_sorted(self) -> impl Iterator<Item = i32>
where
Self: Sized,
{
let mut v = self.0;
v.sort();
v.into_iter()
}
}
Before 1.75, this pattern required either an explicit associated type (more verbose) or boxing with Box<dyn Iterator<Item = i32>> (with heap overhead and loss of zero-cost abstractions). For systems code producing iterators, parsers, or adapters, this change eliminates an entire category of boilerplate.
Rust 1.75 also stabilises: byte-level pointer arithmetic
The same release stabilises pointer::byte_add[2], pointer::byte_sub, pointer::byte_offset, and their wrapping_* variants. The difference from existing methods (add, sub) is that the new ones work in bytes, not in multiples of the pointed type’s size:
let arr: [u32; 4] = [1, 2, 3, 4];
let ptr: *const u32 = arr.as_ptr();
// Before: required explicit cast to *const u8 and back
let second_byte = unsafe {
(ptr as *const u8).add(1).read()
};
// With 1.75: more direct and expressive
let second_byte = unsafe {
ptr.byte_add(1).cast::<u8>().read()
};
For code implementing binary protocols, low-level parsers, or untyped memory manipulation, this change reduces verbosity and the intermediate casts that obscure intent.
Rust 1.75 also brings: automatic cross-crate inlining
1.75 also stabilises a quiet compiler improvement: automatic cross-crate inlining for small functions. Previously, a function could only be inlined across crates if its author had explicitly marked it with #[inline]. Now the compiler can decide on its own for small functions, without that manual annotation. The effect is not a faster build, but more efficient generated code at runtime for dependencies that had not annotated their functions. The technical detail is documented in the rustc changelog[3].
Rust 1.76: ABI compatibility and standard library conveniences
Rust 1.76 does not have language changes as visible as 1.75: the official announcement[4] itself describes it as a relatively minor release. Still, it includes useful day-to-day pieces.
ABI compatibility guarantee: it is formalised that char and u32 have the same size and alignment, and that both are ABI-compatible in function signatures. This is a guarantee that already held in practice, but is now documented and stable.
Convenience APIs in the standard library: Arc::unwrap_or_clone and Rc::unwrap_or_clone are stabilised (extract the inner value or clone it if more references exist), along with Result::inspect / Result::inspect_err and Option::inspect (side effects without consuming the value), and std::any::type_name_of_val (the type name of a value at runtime, useful with closures or opaque types that cannot be named). ptr::from_ref, ptr::from_mut, and ptr::addr_eq also land, and std::hash::{DefaultHasher, RandomState} become available outside std::collections::hash_map.
One small but practical detail: the dbg!() macro now adds the column number in addition to the line, which helps pinpoint the exact call when several appear on the same line.
Ferris, the orange crab mascot of the Rust community, symbol of the language whose 1.75 and 1.76 versions complete async fn support in traits and add convenience utilities to the standard library
Compatibility and upgrading
Upgrading to 1.75 or 1.76 is smooth if the project follows the 2021 edition, which is virtually all new projects. Changes to async fn in traits are additive: existing code using async-trait keeps compiling, and migration can happen gradually. The only relevant warnings are for very complex traits with dyn that already used manual workarounds. For anyone following the language’s roadmap closely, what’s new in the 2024 edition is the natural next milestone after these two releases.
Conclusion
Rust 1.75 and 1.76 are an example of language maturity: they do not add new ideas but complete pending commitments. Stabilising async fn in traits eliminates a technical debt the ecosystem had been paying with the async-trait crate. Pointer byte methods simplify systems code that previously required manual casts. Rust 1.76, more modest, closes ergonomics gaps in the standard library and formalises an ABI guarantee that already existed in practice. Updating to both together leaves Rust’s async and low-level code somewhat cleaner, with no compatibility surprises.
Spanish version: Rust 1.75 y 1.76: mejoras que se notan en el día a día.