Hacktoberfest 2026 : les issues que les mainteneurs ont marquées pour octobre, ouvertes et accessibles aux débutants. Parcourir les issues Hacktoberfest

#[tool] async method fails to compile (FnOnce not general enough) when its call tree uses |x: &T| async move {} closures (e.g. stream::iter(..).map(|x| async move{..}))

Ouverte
#1,092 3 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Les mainteneurs répondent en général sous 2 jours

Personne n'a encore pris cette issue.

Évaluation

Difficulté
4/5
Temps estimé
3-5 jours
Accessibilité débutants
48/100
Type d'issue
Bug
Clarté
Plutôt claire
Activité
Calme
Stack technique
rust
Domaine
tooling

Piste de recherche

Commencez par la reproduction minimale dans src/main.rs et exécutez cargo check pour confirmer l’erreur #[tool]. Examinez ensuite le comportement du dispatch généré décrit dans l’issue et la manière dont la macro d’outil impose sa contrainte de rang supérieur. C’est terminé lorsque le modèle de flux asynchrone compile via #[tool] ou que la limitation est documentée à l’endroit où les utilisateurs la rencontreront.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

P2 Stale

Summary

An async #[tool] method fails to compile with a higher-ranked-trait-bound error if the transitive call tree of the tool method contains a closure |x: &T| async move { .. } — most commonly futures::stream::iter(coll.iter()).map(|x| async move { .. }).buffer_unordered(..). The same code compiles fine outside the #[tool] macro.

Environment

  • rmcp 3.0.1 (crates.io)
  • rustc 1.94.0 stable
  • macOS (reproduced locally)

Minimal reproduction

Cargo.toml

[package]
name = "rmcp-async-repro"
version = "0.0.0"
edition = "2021"

[dependencies]
rmcp = { version = "3", features = ["server", "macros"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
futures = "0.3"

src/main.rs

use rmcp::{
    handler::server::{router::tool::ToolRouter, wrapper::Parameters},
    model::{CallToolResult, ServerCapabilities, ServerInfo},
    schemars, tool, tool_handler, tool_router, ServerHandler,
};
use serde_json::Value;

#[derive(serde::Deserialize, serde::Serialize, schemars::JsonSchema)]
struct Req {
    msg: String,
}

#[derive(Clone, Default)]
struct S {
    tool_router: ToolRouter<S>,
}

impl S {
    async fn execute(&self, _name: &str, _args: &Value) -> CallToolResult {
        use futures::stream::{self, StreamExt};
        let ips = vec!["1.1.1.1".to_string()];
        let _out: Vec<String> = stream::iter(ips.iter())
            .map(|ip| async move { ip.to_string() }) // <-- trigger
            .buffer_unordered(4)
            .collect()
            .await;
        CallToolResult::default()
    }
}

#[tool_router]
impl S {
    #[tool(name = "echo_tool", description = "echo")]
    async fn echo(&self, Parameters(req): Parameters<Req>) -> CallToolResult {
        let args = serde_json::to_value(&req).unwrap_or_default();
        self.execute("echo", &args).await
    }
}

#[tool_handler]
impl ServerHandler for S {
    fn get_info(&self) -> ServerInfo {
        let mut info = ServerInfo::default();
        info.capabilities = ServerCapabilities::builder().enable_tools().build();
        info
    }
}

fn main() {}

Error

error: implementation of `FnOnce` is not general enough
  --> src/main.rs (originates in the `#[tool]` attribute macro)
   = note: closure with signature `fn(&'0 String) -> {async block@src/main.rs:26:23: 26:33}`
           must implement `FnOnce<(&'1 String,)>`, for any two lifetimes `'0` and `'1`...
   = note: ...but it actually implements `FnOnce<(&String,)>`

Analysis

The #[tool] macro generates dispatch code carrying a higher-ranked trait bound. A |x: &T| async move { .. } closure (e.g. produced by futures::stream::iter(..).map(..)) cannot satisfy a for<'a> Fn(&'a T) -> Future<..>-style bound — the long-standing rustc "async closure + HRTB" limitation. The pattern compiles in ordinary code because it is never placed under such a bound; the macro forces it into one.

Removing the stream::iter(..).map(|x| async move{..}) from the tool's call tree (or replacing it with an async fn helper + .then(..), or owning the items before the stream) makes the #[tool] macro compile. Implementing ServerHandler manually (without #[tool]) also avoids it.

Workaround

  • Refactor |x: &T| async move { .. } in the tool's call tree (use an async fn helper + .then(..), or own items before the stream), or
  • Implement ServerHandler manually instead of using #[tool].

Ask

If #[tool] could avoid forcing the user's call tree into an HRTB that async-move closures can't satisfy (e.g. boxing the future / restructuring the generated handler), that would unblock a fairly common futures::stream pattern. At minimum, documenting the limitation would save significant diagnosis time — the error is very hard to map back to the trigger.

Langage dominant
Rust
Étoiles
4k
Forks
688
Merge moyen
4 j 10 h
PR mergées (30 j)
37

Préparer son environnement

Ouvrir dans Codespaces

Lance le conteneur de développement du projet dans votre navigateur, avec votre propre compte GitHub.

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de modelcontextprotocol/rust-sdk

Toutes les issues de modelcontextprotocol/rust-sdk

Issues similaires

Plus d'issues Rust

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.