soupa

Crates.iosoupa
lib.rssoupa
version1.0.2
created_at2025-11-11 21:47:40.194797+00
updated_at2025-11-12 22:31:18.746818+00
descriptionProvides a macro allowing expressions to be eagerly evaluated before a scope.
homepage
repositoryhttps://github.com/bushrat011899/soupa
max_upload_size
id1928237
size31,581
Zachary Harrold (bushrat011899)

documentation

README

Soupa

Provides a single macro, [soupa], which implements a hypothetical block transformation where super { ... } expressions are eagerly evaluated prior to the current scope.

This allows closures and async blocks to borrow from their parent scope without affecting the lifetime of said block, provided the borrow is within a super { ... } expression and the result of said expression does not borrow from the parent scope.

This can be thought of as the dual of defer in languages like Zig.

Example

As an example, consider an Arc you want to use inside a closure.

let foo = Arc::new(/* Some expensive resource */);

let func = move || {
    super_expensive_computation(foo.clone())
};

some_more_operations(foo); // ERROR: `foo` was moved!

While you're only ever using a clone of foo within the closure func, you lose access to the original because it was moved.

The typical way to avoid this is to simply clone items before the closure is created.

let foo = Arc::new(/* Some expensive resource */);

let func = {
    let foo = foo.clone();
    move || {
        super_expensive_computation(foo)
    }
};

some_more_operations(foo); // Ok!

This crate automates this process by allowing expressions within super { ... } blocks to be automatically lifted to the parent scope and assigned to a variable.

let foo = Arc::new(/* Some expensive resource */);

let func = soupa! {
    move || {
        super_expensive_computation(super { foo.clone() })
    }
};

some_more_operations(foo); // Ok!

But Why?

It's strange to support out-of-order execution like this! Suddenly a piece of code halfway through a function body is lifted all the way to the top of the scope and evaluated before everything else.

However, it is my belief that this better reflects how an author thinks about writing these kinds of statements. You write some business logic, and then partway through need access to a value in scope. That's fine, you're writing a move closure so it'll automatically be included.

Except oh no!: I need that value later too, or I drop it before I drop the closure. Now I need to scroll to the top of my business logic and add some initialization code to allow using a value which would otherwise be automatically available.

With [soupa], the error can be addressed within the business logic as you encounter it by simply wrapping the troublesome value in super { ... }.

I would also argue that super { ... } behaves like a granular version of const { ... }. Currently, you can write a const { ... } block to guarantee that an expression is evaluated at the outermost scope possible: compile time. From that perspective, I'd say super { ... } fits neatly between const { ... } and { ... }.

  • Now: { ... }
  • Earlier: super { ... }
  • Earliest: const { ... }
Commit count: 0

cargo fmt