You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从Go转Rust:如何实现无预定义类型的通用重试函数?

问题:从Go重试函数转Rust的通用实现问题

我是刚从Go转向Rust的新手,尝试将以下Go重试函数翻译成Rust:

func Retry(attempts int, seconds int, f func() error) (err error) {
    for i := 0; ; i++ {
        err = f()
        if err == nil {
            return nil
        }

        if i > 0 {
            println("retried")
        }

        if i >= (attempts - 1) {
            break
        }

        time.Sleep(time.Duration(seconds) * time.Second)
    }

  return fmt.Errorf("error after %v attempts", attempts)
}

该Go函数的调用方式如下:

if err := Retry(3, 5, func() error { // this is an immediate function that calls the other one
  err := maybeErroringFunction()
  return err
}); err != nil {
  println("it doesn't respond after all retries")
}

我编写了如下Rust代码,但存在逻辑错误,且调用时出现类型不匹配问题:

use std::{thread, time::Duration};

pub async fn retry(attempts: u8, delay: u64, f: fn() -> Result<(), ()>) -> Result<(), ()> {
    for n in 1..attempts {
        if n > 1 {
            println!("retried {} times", n);
        }

        match f() {
            Ok(()) => break, // this is wrong here because I should return Ok!
            Err(()) => thread::sleep(Duration::from_secs(delay)),
        }
    }

    println!("error after {} attempts", attempts);

    Err(())
}

调用示例及错误信息如下:

use hyper::{client::Client, Body, Request};

pub async fn trying_it() {
    let hyper_client = Client::new();

    let req = Request::builder().uri("http://localhost").body(Body::empty()).unwrap();

    // I need to retry this call:
    // let resp = hyper_client.request(req).await.unwrap();

    let resp = retry(3, 2, hyper_client.request(req).await.unwrap());

    println!("{:?}", resp);
}

错误信息:

mismatched types
expected fn pointer `fn() -> std::result::Result<(), ()>`
       found struct `hyper::Response<hyper::Body>` rustc E0308

main.rs(26, 16): arguments to this function are incorrect

请问是否可以使用闭包?还需要做哪些改进来实现无预定义类型的通用重试函数?


解决方案

核心问题分析

  • 类型不匹配:调用retry时直接传入了请求执行的结果(Response),而非一个可重复执行的闭包。retry需要接收的是能返回Result的函数/闭包,而非直接的执行结果。
  • 逻辑错误:原Rust代码中,Ok(())时仅break而非返回Ok,导致后续会走到错误返回逻辑;循环范围1..attempts会少执行一次尝试。
  • 缺乏通用性:硬编码了Result<(), ()>,无法适配不同的成功/错误类型(比如Hyper的Response<Body>和其对应的错误类型)。
  • 异步支持缺失:Hyper的request是异步方法,重试函数需要支持异步闭包。

改进后的通用异步重试函数

使用泛型和闭包实现支持任意Result类型的异步重试:

use std::time::Duration;
use tokio::time::sleep; // 异步场景推荐用Tokio的sleep,避免阻塞执行器

pub async fn retry<F, T, E>(attempts: u8, delay_secs: u64, mut f: F) -> Result<T, E>
where
    F: FnMut() -> impl std::future::Future<Output = Result<T, E>>,
{
    for attempt in 0..attempts {
        match f().await {
            Ok(result) => return Ok(result), // 成功则直接返回结果
            Err(err) => {
                if attempt < attempts - 1 {
                    // 非最后一次失败,打印重试提示并等待
                    if attempt > 0 {
                        println!("retried {} times", attempt);
                    }
                    sleep(Duration::from_secs(delay_secs)).await;
                } else {
                    // 最后一次失败,返回错误
                    return Err(err);
                }
            }
        }
    }
    unreachable!("retry loop completed without returning");
}

代码说明

  • 泛型设计:通过T(成功类型)、E(错误类型)和F(闭包类型)实现完全通用,适配任意返回Result的操作。
  • 异步兼容:闭包返回异步Future,通过.await执行,搭配Tokio的异步sleep,避免阻塞异步执行器。
  • 逻辑修正:
    • 循环范围0..attempts确保执行指定次数的尝试。
    • 成功时立即返回结果,失败时判断是否还有重试机会,符合原Go函数的逻辑。
    • 重试提示仅在第二次及以后失败时打印,和原Go行为一致。

正确调用方式

针对Hyper请求的调用示例:

use hyper::{client::Client, Body, Request, Error};

pub async fn trying_it() -> Result<(), Error> {
    let hyper_client = Client::new();
    // Request默认不可克隆,若需多次重试,可通过try_clone或Arc包裹
    let req = Request::builder()
        .uri("http://localhost")
        .body(Body::empty())
        .unwrap();

    // 用异步闭包包装请求,move捕获变量,每次重试重新克隆请求
    let resp = retry(3, 2, || async {
        let cloned_req = req.try_clone().expect("request cannot be cloned");
        hyper_client.request(cloned_req).await
    }).await;

    match resp {
        Ok(response) => println!("Success: {:?}", response.status()),
        Err(err) => println!("it doesn't respond after all retries: {}", err),
    }

    Ok(())
}

调用要点

  • 闭包传递:用|| async { ... }包装异步操作,确保每次重试都会重新执行请求。
  • 所有权处理:Hyper的Request不可直接复用,通过try_clone()(仅适用于可克隆的Body)或Arc<Request<Body>>解决多次重试的所有权问题。
  • 异步等待:retry是异步函数,调用时必须使用.await。

同步场景适配(可选)

如果需要支持同步函数的重试,可实现同步版本:

use std::thread::sleep;
use std::time::Duration;

pub fn retry_sync<F, T, E>(attempts: u8, delay_secs: u64, mut f: F) -> Result<T, E>
where
    F: FnMut() -> Result<T, E>,
{
    for attempt in 0..attempts {
        match f() {
            Ok(result) => return Ok(result),
            Err(err) => {
                if attempt < attempts - 1 {
                    if attempt > 0 {
                        println!("retried {} times", attempt);
                    }
                    sleep(Duration::from_secs(delay_secs));
                } else {
                    return Err(err);
                }
            }
        }
    }
    unreachable!()
}

内容的提问来源于stack exchange,提问作者Fred Hors

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 23:57:27