Sling Academy
Home/Rust/E0373 in Rust: Closure May Outlive the Current Function Due to Borrowed References

E0373 in Rust: Closure May Outlive the Current Function Due to Borrowed References

Last updated: January 06, 2025

When working with Rust, a systems programming language that ensures memory safety, developers may encounter several compile-time checks that prevent unsafe code from executing. One such compile-time error is E0373, which is raised when a closure may outlive the current function due to borrowed references.

Understanding the E0373 Error in Rust

The E0373 error occurs when a closure captures a reference from its environment, but the compiler determines that the closure's lifetime may exceed the lifetime of that reference. The concern here is with Rust's borrow checker, which ensures that references do not outlive the data they point to.

Consider the Following Code:

fn main() {
    let s = String::from("Hello, world!");
    let closure = || {
        println!("{}", s);
    };
    call_closure(closure);
}

fn call_closure(f: F)
where
    F: FnOnce(), {
    f();
}

In this code, we define a string s and a closure that borrows s. We then call the call_closure function, passing the closure as an argument. However, if we tried to modify the code to return the closure from the main function, the Rust compiler would raise an E0373 error.

Why Does E0373 Occur?

The problem arises because the closure holds a reference to s, which is created within the main function. If the closure is returned or stored somewhere with a longer lifetime than the local scope of main, there's a risk that s could be dropped while the closure is still alive, leaving a dangling reference behind.

How to Fix E0373

To prevent the E0373 error, you can use one of these approaches:

1. Use std::sync::Arc for Shared Ownership

You can use the Arc (Atomic Reference Counted) pointer to share ownership of a value across multiple threads or function calls safely without copying the data, thus avoiding borrowing issues.

use std::sync::Arc;

fn main() {
    let s = Arc::new(String::from("Hello, world!"));
    let closure = {
        let s = Arc::clone(&s); // Cloning Arc's pointer to be used in closure
        move || {
            println!("{}", s);
        }
    };
    call_closure(closure);
}

fn call_closure(f: F)
where
    F: FnOnce(), {
    f();
}

By using Arc::clone inside the closure, each operation takes ownership of its own Arc instance, which is fine because Arc takes care of reference counting.

2. Box Up s into Closure State

Transforming the closure to store ownership of s instead of a reference to it!

fn main() {
    let s = String::from("Hello, world!");
    let closure = move || {
        // Move s into closure
        println!("{}", s);
    };
    call_closure(closure);
}

By adding the move keyword before the closure, we ensure that s is moved into the closure's state (captured by value), because String implements the Send trait which allows its ownership to be transferred easily.

3. Return the Data Alongside the Closure

In some cases, you might prefer to return both the closure and the data it requires:

fn main() {
    let (closure, s) = generate_closure();
    call_closure(closure);
    println!("Returned string: {}", s); // Continue using `s` after closure execution
}

fn generate_closure() -> (impl FnOnce(), String) {
    let s = String::from("Hello, world!");
    let closure = move || {
        println!("Inside closure: {}", s);
    };
    (closure, s)
}

By structuring your code like this, you decouple the data from the closure while ensuring the data is still available after closure execution.

Conclusion

Understanding and correctly handling error E0373 in Rust ensures safer code when using closures. By working with lifetimes judiciously and leveraging features like Arc and the move keyword, you can efficiently manage data ownership and borrowing in your programs.

Next Article: E0004 in Rust: Non-exhaustive pattern matching leads to a missing arm error

Previous Article: E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel

Series: Common Errors in Rust and How to Fix Them

Rust

You May Also Like

  • E0557 in Rust: Feature Has Been Removed or Is Unavailable in the Stable Channel
  • Network Protocol Handling Concurrency in Rust with async/await
  • Using the anyhow and thiserror Crates for Better Rust Error Tests
  • Rust - Investigating partial moves when pattern matching on vector or HashMap elements
  • Rust - Handling nested or hierarchical HashMaps for complex data relationships
  • Rust - Combining multiple HashMaps by merging keys and values
  • Composing Functionality in Rust Through Multiple Trait Bounds
  • E0437 in Rust: Unexpected `#` in macro invocation or attribute
  • Integrating I/O and Networking in Rust’s Async Concurrency
  • E0178 in Rust: Conflicting implementations of the same trait for a type
  • Utilizing a Reactor Pattern in Rust for Event-Driven Architectures
  • Parallelizing CPU-Intensive Work with Rust’s rayon Crate
  • Managing WebSocket Connections in Rust for Real-Time Apps
  • Downloading Files in Rust via HTTP for CLI Tools
  • Mocking Network Calls in Rust Tests with the surf or reqwest Crates
  • Rust - Designing advanced concurrency abstractions using generic channels or locks
  • Managing code expansion in debug builds with heavy usage of generics in Rust
  • Implementing parse-from-string logic for generic numeric types in Rust
  • Rust.- Refining trait bounds at implementation time for more specialized behavior