Skip to content

Tracking Issue for #[track_caller] on async fn #110011

Description

@bryangarza

This is a tracking issue for #[track_caller] on async fn.
The feature gate #[feature(async_fn_track_caller)] for the issue is not implemented yet (#110009)

About tracking issues

Tracking issues are used to record the overall progress of implementation.
They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions.
A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature.
Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.

Steps

Unresolved Questions

(Copied over from: #87417 (comment))

It's not 100% clear that tracking the poller, rather than the original caller, is the ideal behavior. That said, we may still be able to resolve the question without an RFC.

Arguments in favor of tracking the poller (current behavior):

  • The user might expect to see the call that's happening now rather than one that happened in the past.
  • Prevents us from having to store the caller in the Future type.
  • Usually poller and caller are the same function, anyway, so we should do the simpler and more performant thing.

Arguments in favor of tracking the original caller instead of the poller:

  • User might expect to see the actual caller; especially given the name track_caller, showing the poller can confusing.
  • A &'static Location is only one pointer; storing it is not so bad.
  • Combinators won't need to add #[track_caller] to "thread through" the caller information.
  • If we tracked the poller, #[track_caller] on a spawned task would show the executor.
    • There's no way today for the executor to save off caller location information (e.g. in spawn) and re-provide it to the polled function, even if it wanted to. We could fix this with a set_caller API.
    • Tracking won't work for &dyn Future. Executors can easily work around this if they have an API with access to the underlying type before coercing to dyn, as most do (playground). But if you happen to already have a dyn Future, you'll be stuck.

Implementation history

Activity

  1. added
    C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFC
    on Apr 6, 2023
  2. bryangarza commented on Apr 6, 2023

    @bryangarza
    ContributorAuthor
  3. bryangarza commented on Apr 6, 2023

    @bryangarza
    ContributorAuthor

    @rustbot label +T-lang

  4. tmandry commented on Apr 10, 2023

    @tmandry
    Member

    It's not 100% clear that tracking the poller, rather than the original caller, is the ideal behavior. That said, we may still be able to resolve the question without an RFC.

    @rust-lang/wg-async discussed in this zulip thread. We were leaning toward tracking the original caller rather than the poller, based on the use case we knew about (assertion helpers). If there are other use cases or points of agreement/disagreement with this, please leave them in the linked zulip thread.

  5. added
    I-async-nominatedNominated for discussion during an async working group meeting.
    on Apr 10, 2023
  6. removed
    I-async-nominatedNominated for discussion during an async working group meeting.
    on Apr 24, 2023
  7. Swatinem commented on May 31, 2023

    @Swatinem
    Contributor

    Another idea worth revisiting here may be:
    In #104833, I also added #[track_caller] support for async blocks. Pretty much because it was trivial to do so, and actually supporting it separately would have been more work.

    It might still be worth doing that, specifically to split that into its own track_async_block_caller or similar feature.

  8. CryZe commented on Aug 26, 2025

    @CryZe
    Contributor

    It probably makes sense to simply allow the user to choose:

    #[track_caller(poll)]

    or

    #[track_caller(original)]

    (+- bikeshedding on the names)

  9. theemathas commented on Jul 15, 2026

    @theemathas
    Contributor

    Lang-nominating to request stabilization, as the one unresolved question seems relatively easy to decide on. See discussion at #wg-async > async_fn_track_caller stablization status?

  10. traviscross commented on Jul 15, 2026

    @traviscross
    Contributor

    We do our stabilization decisions on PRs and on the basis of the stabilization report and accompanying Reference PR. So the best we could do here, in the meeting, would be to invite a stabilization PR and offer any thoughts we have about things we might want to see there.

  11. 18 remaining items

  12. added a commit that references this issue on Sep 2, 2026
  13. added a commit that references this issue on Sep 3, 2026
  14. added a commit that references this issue on Sep 3, 2026
  15. added a commit that references this issue on Sep 3, 2026
  16. self-assigned this
    on Sep 20, 2026
  17. removed
    E-help-wantedCall for participation: Help is requested to fix this issue.
    on Sep 27, 2026
  18. theemathas commented on Sep 27, 2026

    @theemathas
    Contributor

    I've discovered the following edge case, and I would like T-lang decision on how this code should behave.

    struct Thing;
    
    trait Trait1 {
        #[track_caller]
        fn foo(&self) -> impl Future<Output = ()>;
    }
    impl Trait1 for Thing {
        async fn foo(&self) {
            panic!();
        }
    }
    
    trait Trait2 {
        #[track_caller]
        async fn foo(&self);
    }
    impl Trait2 for Thing {
        fn foo(&self) -> impl Future<Output = ()> {
            async {
                panic!();
            }
        }
    }

    According to the reference, #[track_caller] on a method in the trait definition also applies to that method in all implementations. (Thanks to an LLM for pointing this out as a gap that my draft implementation didn't support.)

    The problem is, #[track_caller] on a function that returns an async block doesn't apply to the async block at all. Meanwhile, as per previous T-lang decision, #[track_caller] on an async fn causes the async block to track the caller of the function. That is, #[track_caller] on these two things have very different effects. This makes the the desired behavior for "#[track_caller] inheritance" rather unclear.

    We need to decide what the behavior of the above code snippet is. For each of Trait1 and Trait2, there are four possible behaviors:

    • The panic points to the line the panic is written.
    • The panic points to the caller of the function.
    • The panic points to the poller/awaiter of the returned future.
    • The program does not compile. (This would be a breaking change. This code snippet compiles on stable without any FCW.)

    I think that the behavior that makes the most sense is for Trait1 to have the panic point to the caller of the function, while Trait2 have the panic point to the line where the panic is written. This is a bit surprising, but it makes sense to me with the mental model of "#[track_caller] gets copy-pasted onto the method on every impl block of the trait". I am asking what T-lang thinks is the correct behavior.

  19. added
    P-lang-drag-1Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang
    on Sep 28, 2026
  20. theemathas commented on Sep 29, 2026

    @theemathas
    Contributor

    Another question: does there need to be a new FCW for the case in #163406 (plus some waiting time) before we stabilize this feature?

  21. tmandry commented on Oct 28, 2026

    @tmandry
    Member

    My inclination is to adopt the following model:

    You should be able to write #[track_caller] on async blocks, async closures, and regular closures. Closures do not propagate caller information from the defining function, and they propagate caller location from the caller only with #[track_caller].

    #[track_caller] async fn foo() -> T { expr } is equivalent to
    #[track_caller] fn foo() -> impl Future<Output = T> { #[track_caller] async move { expr } }.

    #[track_caller] async {} is equivalent to
    (#[track_caller] async || {})().

    Is there a reason we can't do this? I proposed it in today's lang meeting and people thought it sounded right to them.

    Of course I can see it being useful in some cases to track the poller/awaiter, but that can be a separate knob, and this model seems consistent.

  22. theemathas commented on Oct 30, 2026

    @theemathas
    Contributor

    @tmandry If we go with that idea, then this has a side effect where, in the following code, the Location::caller() call returns the location of the caller of foo(). Is this intended?

    #[track_caller]
    fn foo() {
        tokio::spawn(#[track_caller] async {
            let location = Location::caller();
        });
    }

    Also, there's an unanswered question in #110011 (comment)

  23. theemathas commented on Oct 31, 2026

    @theemathas
    Contributor

    @tmandry Also, I don't think you've answered my question of how my code snippet with Trait1 and Trait2 should behave.

  24. theemathas commented on Nov 8, 2026

    @theemathas
    Contributor

    I have a writeup at https://hackmd.io/@2zGWDWY_QTaaY-pDimKARg/Hy1QajnpGx, summarizing the status of this, and has questions I have for T-lang.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

C-tracking-issueCategory: An issue tracking the progress of sth. like the implementation of an RFCI-lang-nominatedNominated for discussion during a lang team meeting.I-lang-radarItems that are on lang's radar and will need eventual work or consideration.P-lang-drag-1Lang team prioritization drag level 1. https://rust-lang.zulipchat.com/#narrow/channel/410516-t-langT-langRelevant to the language team

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions