从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
相关产品推荐
相关产品推荐

