Rust异步构建器模式实现:无动态分派实现IntoFuture
为Rust构建器实现
IntoFuture以避免动态分派 问题描述
我需要给配置请求的构建器实现Future能力,目前通过IntoFuture trait实现了直接await构建器的功能,但只能依赖Pin<Box<dyn Future>>做动态分派,希望能避免额外内存分配,使用具体的Future类型。
当前实现代码:
use std::time::Duration; use tokio; #[derive(Default)] struct FooFluentBuilder { value: i32, } impl FooFluentBuilder { fn value(mut self, value: i32) -> Self { self.value = value; self } async fn send(self) -> Result<i32, &'static str> { tokio::time::sleep(Duration::from_millis(50)).await; Ok(self.value) } } impl std::future::IntoFuture for FooFluentBuilder { type Output = Result<i32, &'static str>; type IntoFuture = std::pin::Pin<Box<dyn std::future::Future<Output = Self::Output>>>; fn into_future(self) -> Self::IntoFuture { Box::pin(self.send()) } } #[tokio::main] async fn main() { let _response = FooFluentBuilder::default().value(5).send().await; let _response = FooFluentBuilder::default().value(5).await; }
核心问题是send()返回的是Rust异步函数生成的不透明匿名Future,无法直接作为IntoFuture的关联类型,只能用动态分派包装。
解决方案
有两种可靠方式可以避免动态分派,使用具体Future类型:
1. 手动实现命名Future结构体
将异步逻辑转换成手动实现的Future结构体,直接用这个结构体作为IntoFuture的关联类型,完全消除动态分派和堆分配。
修改后的代码:
use std::pin::Pin; use std::task::{Context, Poll}; use std::time::Duration; use tokio::time::Sleep; #[derive(Default)] struct FooFluentBuilder { value: i32, } impl FooFluentBuilder { fn value(mut self, value: i32) -> Self { self.value = value; self } // 改为返回自定义Future类型 fn send(self) -> FooSendFuture { FooSendFuture { inner: tokio::time::sleep(Duration::from_millis(50)), value: self.value, } } } // 自定义Future结构体,包含异步逻辑的状态 struct FooSendFuture { inner: Sleep, value: i32, } impl std::future::Future for FooSendFuture { type Output = Result<i32, &'static str>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { let this = self.get_mut(); // 委托给内部Sleep Future的poll方法 match Pin::new(&mut this.inner).poll(cx) { Poll::Ready(_) => Poll::Ready(Ok(this.value)), Poll::Pending => Poll::Pending, } } } impl std::future::IntoFuture for FooFluentBuilder { type Output = Result<i32, &'static str>; // 使用具体的自定义Future类型 type IntoFuture = FooSendFuture; fn into_future(self) -> Self::IntoFuture { self.send() } } #[tokio::main] async fn main() { let _response = FooFluentBuilder::default().value(5).send().await; let _response = FooFluentBuilder::default().value(5).await; }
这种方式性能最优,但需要手动编写poll方法,逻辑复杂时会增加代码量。
2. 使用type_alias_impl_trait稳定特性
从Rust 1.65开始,稳定版支持在类型别名中使用impl Trait,可以给异步函数返回的不透明Future定义别名,然后作为IntoFuture的关联类型使用。
首先在 crate 根启用特性:
#![feature(type_alias_impl_trait)]
修改后的实现代码:
#![feature(type_alias_impl_trait)] use std::time::Duration; use tokio; #[derive(Default)] struct FooFluentBuilder { value: i32, } impl FooFluentBuilder { fn value(mut self, value: i32) -> Self { self.value = value; self } async fn send(self) -> Result<i32, &'static str> { tokio::time::sleep(Duration::from_millis(50)).await; Ok(self.value) } // 定义不透明Future的类型别名 type SendFuture = impl std::future::Future<Output = Result<i32, &'static str>>; } impl std::future::IntoFuture for FooFluentBuilder { type Output = Result<i32, &'static str>; // 使用类型别名作为关联类型 type IntoFuture = FooFluentBuilder::SendFuture; fn into_future(self) -> Self::IntoFuture { self.send() } } #[tokio::main] async fn main() { let _response = FooFluentBuilder::default().value(5).send().await; let _response = FooFluentBuilder::default().value(5).await; }
这种方式保留了异步函数的简洁性,同时避免了动态分派和堆分配,是平衡代码简洁性和性能的最优选择。
内容的提问来源于stack exchange,提问作者Jinyoung Choi
相关产品推荐
相关产品推荐

