Skip to content

[Breaking] Remove Async trait and Send bounds in async functions#149

Merged
soareschen merged 8 commits intomainfrom
remove-async
Sep 12, 2025
Merged

[Breaking] Remove Async trait and Send bounds in async functions#149
soareschen merged 8 commits intomainfrom
remove-async

Conversation

@soareschen
Copy link
Collaborator

@soareschen soareschen commented Sep 12, 2025

Summary

This PR removes the Send bound in the Future returned from the async functions desugared from the #[async_trait] macro. And along with it, the cgp-async trait and its constructs including Async, MaybeSend, and MaybeSync.

Background

The original motivation of the cgp-async crate was two fold. First, it was belived that Send and Sync bounds need to be added everywhere in the code, in order to make the Future returned from async functions to implement Send. This requirement is important, because it is common that users want to call some async functions within spawners like tokio::spawn, which requires the Future to implement Send.

Another motivation was to experiment on supporting async-generic on context-generic code, so that they could be used inside sync code by stripping away all use of the async and .await keywords. This was done by adding an async feature flag in cgp-async, which would turn into sync mode and strip all use of async when the feature flag is disabled.

Problems

The main problem with the current approach is that it adds a lot of noise and complexity to async code. In order to use an abstract type in an async function, it often has to require the Send and Sync bounds to be present.

Furthermore, the feature flag approach for async-generic don't really work that well, as the async feature flag can easily be activated by default within nested dependencies. This means that almost every async-generic crate would have to add default-features = false and an additional async feature in order to propagate the control.

New Approach

With this PR, a new approach is adopted. Instead of annotating Send and Sync bounds everywhere, we just stop requiring the futures returned to implement Send.

Despite this, a workaround is available to gain back the Send bound of a top-level future, in case if we need to use it within tokio::spawn. This is done by writing context-specific implementation of the SendRunner trait on the concrete context, which forwards the actual implementation to Runner. This works because when the concrete types are accessible, Rust would be able to infer that the Send bound is satisfied on the concrete future. An example code that demonstrates this workaround is available in the test code.

Furthermore, the Send bound will soon become unnecessary altogether once the return type notation (RTN) feature becomes stabilized in Rust rust-lang/rust#109417. After that, we won't need to implement wrappers on the concrete context to resurface the Send bound.

As for async-generic, the feature is not really used, and so it will be removed altogether. Instead, we will rely on runtime assertion via the call to futures::block_on to assert that an async function don't really perform any async operation and could return immediately.

In the future, it would also be possible to develop type-level DSLs similar to Hypershell, together with handler traits like Computer and AsyncComputer to define async-generic programs in teh DSL.

Breaking Change

The removal of cgp-async is a major breaking change. It would affect all existing CGP code that uses traits like Async. However, this breaking change is necessary in order to bring a simpler foundation of CGP for future code. Furthermore, it is better to make this breaking change early while there are still not too much CGP code written.

Here is a summary of the breaking changes:

  • The cgp-async crate is removed.
  • The traits Async, MaybeSend, MaybeSync are removed.
  • The alias traits HasAsyncErrorType, CanRaiseAsyncError, CanWrapAsyncError are removed.
  • A Code parameter is added to the CanRun trait.

In most cases, you should be able to migrate to the new version by removing all bounds to to Async. Alternatively, you could copy over the original definitions of alias traits like Async and HasAsyncErrorType, so that you can continue using them locally in your project.

@soareschen soareschen changed the title Remove Async trait and Send bounds in async functions [Breaking] Remove Async trait and Send bounds in async functions Sep 12, 2025
@soareschen soareschen merged commit f52f70e into main Sep 12, 2025
5 checks passed
@soareschen soareschen deleted the remove-async branch September 12, 2025 13:48
@soareschen soareschen added the breaking Breaking changes label Oct 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking Breaking changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant