Jacar mascot — reading along A laptop whose eyes follow your cursor while you read.
Desarrollo de Software

Rust 1.75 and 1.76: Improvements Noticeable Daily

Rust 1.75 and 1.76: Improvements Noticeable Daily

Actualizado: 2026-05-03

Rust 1.75 (December 2023) and Rust 1.76 (February 2024) are the kind of releases developers appreciate retrospectively: no flashy headlines, but improvements felt in real code. The most significant change in 1.75 is the stabilisation of async fn in traits — a limitation that had been forcing use of the async-trait crate for years — along with return-position impl Trait in traits. Rust 1.76 consolidates with improvements to debug info and pointer type ergonomics. Together, they make async code and low-level systems code considerably cleaner.

Key takeaways

  • async fn in traits is now stable in Rust 1.75: removes the async-trait crate dependency for the basic case.
  • Return-position impl Trait in traits completes support for opaque return types without boxing.
  • Rust 1.76 introduces pointer::byte_add and related methods for more expressive pointer arithmetic.
  • Debug info improvements in 1.76 produce more precise backtraces for inlined and async code.
  • Incremental compile times keep improving release by release, with 5-10% gains on large projects.

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:

rust
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 — exactly 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.

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:

rust
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.76: pointer ergonomics and better debug

Rust 1.76 does not have language changes as visible as 1.75, but includes improvements in two areas relevant to systems developers and debugging.

Byte methods on pointers: pointer::byte_add, pointer::byte_sub, pointer::byte_offset, and offset variants are stabilised. The difference from existing methods (add, sub) is that the new ones work in bytes, not in multiples of the pointed type’s size:

rust
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.76: 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 intermediate casts that obscure intent.

Debug info improvements: 1.76 includes substantial work on the quality of generated debug info, with impact in three areas:

  • More precise inlining: backtraces show the correct frame even when the compiler has inlined the function. Previously, aggressively inlined functions appeared as an opaque caller frame.
  • Async types: debug info for futures generated by async/await better shows which state-machine state corresponds to which source code point.
  • DWARF interoperability: improved compatibility with tools like GDB, LLDB, and perf that read DWARF debug info.
Ferris, the orange crab mascot of the Rust community, symbol of the language whose 1.75 and 1.76 versions complete async trait support and improve the debugging experience

Compile times: incremental improvements

Rust has a reputation for slow compile times, and improvements in this area are welcome even if modest. Releases 1.75 and 1.76 include internal compiler optimisations that, in large projects, translate to:

  • Incremental compilations 5-10% faster in projects with many proc macros.
  • Improvements in rustc frontend internal parallelism.
  • Reduction of check build overhead (type-check-only compilations without code generation).

For projects at the scale of compilers or servers with hundreds of crates, these accumulated improvements have real impact on development cycles.

Compatibility and upgrading

Upgrading to 1.75 or 1.76 is smooth if the project follows the 2021 edition — virtually all new projects. Changes to async fn in traits are additive: existing code using async-trait keeps compiling; you can migrate gradually. The only relevant warnings are for very complex traits with dyn that used manual workarounds.

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. Debug info improvements reduce friction in the bug investigation phase. Pointer byte methods simplify systems code that previously required manual casts. Updated together, these two releases make async and low-level code in Rust considerably cleaner and more direct.

Was this useful?
[Total: 11 · Average: 4.3]

Written by

CEO - Jacar Systems

Passionate about technology, cloud infrastructure and artificial intelligence. Writes about DevOps, AI, platforms and software from Madrid.