Niko Matsakis
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    4
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    # Async fns in traits **This doc now lives here: https://rust-lang.github.io/async-fundamentals-initiative/explainer/user_guide_future.html** *A user's guide from the future.* This document explains the proposed design for async functions in traits, from the point of view of a "knowledgeable Rust user". It is meant to cover all aspects of the design we expect people to have to understand to leverage this feature. It doesn't go into every detail of how it will be implemented. ## Intro This guide explains how traits work when they contain async functions. For the most part, a trait that contains an async functions works like any other trait -- but there are a few interesting questions and interactions that come up with async functions that don't arise with ordinary functions. We'll use a series of examples to explain how everything works. ## Traits and impls with async fn syntax Defining a trait with an async function is straightforward ("just do it"): ```rust use std::io::Result; struct ResourceId { } struct ResourceData { } trait Fetch { async fn fetch(&mut self, request: ResourceId) -> Result<ResourceData>; } ``` Similarly, implementing such a trait is straightforward ("just do it"): ```rust struct HttpFetch { } impl Fetch for HttpFetch { async fn fetch(&mut self, request: ResourceId) -> Result<ResourceData> { ... /* something that may await */ ... } } ``` ## Writing generic functions using async traits Generic functions, types, etc that reference async traits work just like you would expect from other traits: ```rust async fn fetch_and_process(f: impl Fetch, r: ResourceId) -> Result<()> { let data: ResourceData = f.fetch(r).await?; process(data)?; Ok(()) } ``` That example used [`impl Trait`](https://rust-lang.github.io/impl-trait-initiative/explainer/apit.html), but of course one could expand it to the (mostly equivalent) version with an explicit generic type: ```rust async fn fetch_and_process<F>(f: F, r: ResourceId) -> Result<()> where F: Fetch, { let data: ResourceData = f.fetch(r).await?; process(data)?; Ok(()) } ``` ## Bounding with Send, Sync, and other bounds Suppose that we wanted to create a version of `fetch_and_process` in which the "fetching and processing" took place in another task? We can write a variant of `fetch_and_process`, `fetch_and_process_in_task`, that does this, but we have to add new where-clauses: ```rust async fn fetch_and_process_in_task<F>( f: F, r: ResourceId, ) where F: Fetch, F: Send + 'static, // 👈 Added so `F` can be sent to new task F::fetch(..): Send // 👈 Added, this syntax is new! // We will be explaining it shortly. { tokio::spawn(async move { let data: ResourceData = f.fetch(r).await?; process(data)?; Ok(()) }); } ``` If you prefer, you could write it with `impl Trait` syntax like so: ```rust async fn fetch_and_process_in_task( f: impl Fetch<fetch(..): Send> + Send + 'static, // ~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~ // Added! Leverages the new `fetch(..)` // syntax we are about to explain, but // also associated type bounds (RFC 2289). r: ResourceId, ) { tokio::spawn(async move { let data: ResourceData = f.fetch(r).await?; process(data)?; Ok(()) }); } ``` Let's walk through those where-clauses in detail: * `F: Send` and `F: 'static` -- together, these indicate that it is safe to move `F` over to another task. The `Send` bound means that `F` doesn't contain any thread-local types (e.g., `Rc`), and the `'static` means that `F` doesn't contain any borrowed data from the current stack. * `F::fetch(..): Send` -- this is new syntax. It says that "the type returned by a call to `fetch()` is `Send`". In this case, the type being returned is a future, so this is saying "the future returned by `F::fetch()` will be `Send`". ## Function call bounds in more detail The `F::fetch(..): Send` bound is actually an example of a much more general feature, return type notation. This notation lets you refer to the type that will be returned by a function call in all kinds of places. As a simple example, you could now write a type like `identity_fn(u32)` to mean "the type returned by `identity_fn` when invoked with a `u32`": ```rust let f: identity_fn(u32) = identity_fn(22_u32); ``` Knowing the types of arguments can be important for figuring out the return type! For example, if `identity_fn` is defined like so... ```rust fn identity_fn<T>(t: T) -> T { t } ``` ...then `identity_fn(u32)` is equivalent to `u32`, `identity_fn(i32)` is equivalent to `i32`, and so on. When you are using return type notation in a bound, you can use `..` to mean "for all the types accepted by the function". So when we write `F::fetch(..): Send`, that is in fact equivalent to this much more explicit bound: ```rust where for<'a> F::fetch(&'a mut F, ResourceData): Send // --> equivalent to `F::fetch(..): Send` ``` The more explicit version spells out explicitly that we want "the type returned by `Fetch` when given some `&'a mut F` and a `ResourceData`". The `..` notation can only be used in bounds because it refers to a whole range of types. If you were to write `let f: F::fetch(..) = ..`, that would mean that `f` has multiple types, and that is not currently permitted. ### Turbofish and return type notation In some cases, specifying the types of the arguments to the function are not enough to uniquely specify its behavior: ```rust fn make_default<T: Default>() -> Option<T> { Some(T::default()) } ``` Just as in an expression, you can use turbofish to handle scenarios like this. The following function references `make_default::<T>()`, for example, as a (rather convoluted) synonym for `Option<T>`: ```rust fn foo<T>(t: T) where make_default::<T>(): Send, ``` ## Dynamic dispatch and async functions The other major way to use traits is with `dyn Trait` notation. This mostly works normally with async functions, but there are a few differences to be aware of. Let's start with a variant of the `fetch_and_process` function that uses `dyn Trait` dispatch: ```rust async fn fetch_and_process_dyn(f: &mut dyn Fetch, r: ResourceId) -> Result<()> { let data: ResourceData = f.fetch(r).await?; process(data)?; Ok(()) } ``` Looks simple! But what, what about the code that calls `fetch_and_process_dyn`, how does that look? This is where things with async functions get a bit more complicated. The obvious code to call `fetch_and_process_dyn`, for example, will not compile: ```rust async fn caller() { let mut f = HttpFetch { .. }; fetch_and_process_dyn(&mut f); // 👈 ERROR! } ``` Compiling this we get the following message: ``` error[E0277]: the type `HttpFetch` cannot be converted to a `dyn Fetch` without an adapter --> src/lib.rs:3:23 | 3 | fetch_and_process_dyn(&mut f); | --------------------- ^^^^^^ the trait `Foo` is not implemented for `HttpFetch` | | | required by a bound introduced by this call | = help: consider introducing the `Boxing` adapter, which will box the futures returned by each async fn 3 | fetch_and_process_dyn(Boxing::new(&mut f)); | ++++++++++++ + ``` ### The `Boxing` adapter What is going on here? The `Boxing` adapter indicates that, each time you invoke an `async fn` through the `dyn Fetch`, the future that is returned will be boxed. The reasons that boxing are required here are rather subtle. The problem is that the amount of memory a future requires depends on the details of the function it results from (i.e., it is based on how much state is preserved across each `await` call). Normally, we store the data for futures on the stack, but this requires knowing precisely which function is called so that we can know exactly how much space to allocate. But when invoking an async function through a `dyn` value, we can't know what type is behind the `dyn`, so we can't know how much stack space to allocate. To solve this, Rust only permits you to create a `dyn` value if the size of each future that is returned is the same size as a pointer. The `Boxing` adapter ensures this is the case by boxing each of the futures into the heap instead of storing their data on the stack. :::info **What if I don't want to box my futures?** For the vast majority of applications, `Boxing` works great. In fact, the `#[async_trait]` procedural macro that most folks are using today boxes *every* future (the built-in design only boxes when using dynamic dispatch). However, there are times when boxing isn't the right choice. Particularly tight loops, for example, or in `#[no_std]` scenarios like kernel modules or IoT devices that lack an operating system. For those cases, there are other adapters available that allocate futures on the stack or in other places. See [Appendix B](#Appendix-B-Other-adapters) for the details. ::: ### Specifying that methods return `Send` Circling back to our `fetch_and_process` example, there is another potential problem. When we write this... ```rust async fn fetch_and_process(f: &mut dyn Fetch, r: ResourceId) -> Result<()> { let data: ResourceData = f.fetch(r).await?; process(data)?; Ok(()) } ``` ...this code will compile just fine, but if you are using a "work stealing runtime" (e.g., tokio by default), you may find that it doesn't work for you. The problem is that `f.fetch(r)` returns "some kind of future", but we don't know that the future is `Send`, and hence the `fetch_and_process()` future is itself not `Send`. When using a `F: Fetch` argument, this worked out ok because we knew the type `F` exactly, and the future we returned would be `Send` as long as `F` was. With `dyn Fetch`, we don't know exactly what kind of type we will have, and we can only go based on what the type says -- and the type doesn't promise `Send` futures. There are a few ways to write `fetch_and_process` such that the returned future is known to be `Send`. One of them is to make use of the return type bounds. Instead of writing `dyn Fetch`, we can write `dyn Fetch<fetch(..): Send>`, indicating that we need more than just "some `Fetch` instance" but "some `Fetch` instance that returns `Send` futures": ```rust async fn fetch_and_process( f: &mut dyn Fetch<fetch(..): Send>, // 👈 New! r: ResourceId, ) -> Result<()> { let data: ResourceData = f.fetch(r).await?; process(data)?; Ok(()) } ``` Writing `dyn Fetch<fetch(..): Send>` is ok if you don't have to do it a lot, but if you do, you'll probably find yourself wishing for a shorter version. One option is to define a type alias: ```rust type DynFetch<'bound> = dyn Fetch<fetch(..): Send> + 'bound; async fn fetch_and_process( f: &mut DynFetch<'_>, // 👈 Shorter! r: ResourceId, ) -> Result<()> { let data: ResourceData = f.fetch(r).await?; process(data)?; Ok(()) } ``` ## Appendix A: manual desugaring In general, an async function in Rust can be desugared into a sync function that returns an `impl Future`. The same is true of traits. We could substitute the following definitions for the traits or impls and they would still compile: ```rust trait Fetch { fn fetch( &mut self, request: ResourceId ) -> impl Future<Output = Result<ResourceData>> + '_; } impl Fetch for HttpFetch { fn fetch( &mut self, request: ResourceId ) -> impl Future<Output = Result<ResourceData>> + '_ { async move { ... /* something that may await */ ... } } } ``` In fact, we can even "mix and match", using an async function in the trait and an `impl Trait` in the impl or vice versa. It is also possible to write an impl with a specific future type: ```rust impl Fetch for HttpFetch { #[refine] fn fetch( &mut self, request: ResourceId ) -> Box<dyn Future<Output = Result<ResourceData>> { Box::pin(async move { /* something that may await */ }) } } ``` This last case represents a *refinement* on the trait -- the trait promises only that you will return "some future type". The impl is then promising to return a very specific future type, `Box<dyn Future>`. The `#[refine]` attribute indicates that detail is important, and that when people are referencing the return type of the `fetch()` method for the type `HttpFetch`, they can rely on the fact that it is `Box<dyn Future>` and not some other future type. (See [RFC 3245](https://github.com/rust-lang/rfcs/pull/3245) for more information.) ## Appendix B: Other adapters ### Inline adapter The "inline" adapter allocates stack space in advance for each possible `async fn` in the trait. Unlike `Boxing`, it has a few sharp edges, and doesn't work with every trait. For this reason, it is not built-in to the compiler but rather shipped through a library. To use the inline adapter, you first add the `dyner` crate to your package. ... TBW ... ## Appendix Z: Internal notes *These are some notes and design questions that arose in reading and thinking through this document. They may be addressed in future revisions.* Are there semver considerations in terms of when a `dyn` can be created? We should only permit creating a dyn when the impl has a `#[refine]` and the type implements `IntoDyn` or whatever trait we decide to use for the [`dyn*`](https://smallcultfollowing.com/babysteps/blog/2022/03/29/dyn-can-we-make-dyn-sized/) implementation.

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully