具有关联 return 类型和动态调度的异步特征方法
Async trait methods with associated return type and dynamic dispatch
我需要处理来自多个不同源的异步数据流,这些数据流都应该被解析为某种规范化格式。
理想情况下我想:
为实现某些 Handler
特性的每个源编写一个处理程序,以便下游调用者可以与数据流交互并且与底层实现无关。
有一些函数可以 return 一个 Box<dyn Handler>
给定一个键,因为有 limited/predictable 个源实现了 Handler
.
我已经使用 #[async_trait]
尝试了类似下面的操作,但我无法编译它,主要是错误“无法将特征 Handler
制作成对象”。
是否有 better/more 惯用的方法来解决这个问题?
use async_trait; // 0.1.52
use tokio; // 1.15.0
#[async_trait::async_trait]
trait Handler{
type Output;
async fn connect()->Self::Output;
async fn read_parse(&self)->Vec<i32>;
async fn run(&self) {
for _ in 0..5 {
self.read_parse().await;
}
}
}
struct FooHandler;
#[async_trait::async_trait]
impl Handler for FooHandler {
type Output = FooHandler;
async fn connect() -> Self::Output {
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
FooHandler{}
}
async fn read_parse(&self)->Vec<i32> {
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
vec![1, 2, 3]
}
}
struct BarHandler;
#[async_trait::async_trait]
impl Handler for BarHandler {
type Output = BarHandler;
async fn connect() -> Self::Output {
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
BarHandler{}
}
async fn read_parse(&self)->Vec<i32> {
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
vec![1, 2, 3]
}
}
async fn get_handler(name:&str)->Option<Box<dyn Handler>> {
match name {
"FOO"=>Some(Box::new(FooHandler::connect().await)),
"BAR"=>Some(Box::new(BarHandler::connect().await)),
_=>None
}
}
#[tokio::main]
async fn main() {
let handler = get_handler("FOO").await.unwrap();
handler.run().await;
}
编辑:
根据 cameron1024 的回答,我可以使用以下方法:
use async_trait; // 0.1.52
use tokio; // 1.15.0
#[async_trait::async_trait]
trait Handler: Sync {
async fn connect()->Self
where
Self: Sized;
async fn read_parse(&self)->Vec<i32>;
async fn run(&self) {
for _ in 0..5 {
let res = self.read_parse().await;
println!("{:?}", res);
}
}
}
struct FooHandler;
#[async_trait::async_trait]
impl Handler for FooHandler {
async fn connect() -> Self {
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
println!("connected");
FooHandler{}
}
async fn read_parse(&self)->Vec<i32> {
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
vec![1, 2, 3]
}
}
async fn get_handler(name: &str)->Option<Box<dyn Handler>> {
match name {
"FOO"=>Some(Box::new(FooHandler::connect().await)),
_=>None
}
}
#[tokio::main]
async fn main() {
// let handler = FooHandler::connect().await;
let handler = get_handler("FOO").await.unwrap();
handler.run().await;
}
这里有 2 个问题。
首先,要使异步特征具有默认实现,它要求特征本身具有 Send
或 Sync
(取决于接收者类型)作为超特征(有一个简短的解释在板条箱自述文件的“dyn traits”部分:https://docs.rs/async-trait/latest/async_trait/)
其次,connect
函数不是对象安全的,因为它的 return 类型是关联函数。这与异步无关,这是一个对象安全问题。这个简化的例子有同样的问题:
fn main() {
let x: Box<dyn Foo<Bar = ()>> = Box::new(());
}
trait Foo {
type Bar;
fn connect() -> Self::Bar;
}
impl Foo for () {
type Bar = ();
fn new() -> Self::Bar {
todo!()
}
}
但是,您不太可能希望在特征对象上调用 connect
,因为它没有 self
参数。相反,您可以通过将 Self: Sized
约束添加到 connect
.
来从特征对象中选择该特定函数
然后您可以创建特征对象,但 connect
将不可用。
我需要处理来自多个不同源的异步数据流,这些数据流都应该被解析为某种规范化格式。
理想情况下我想:
为实现某些
Handler
特性的每个源编写一个处理程序,以便下游调用者可以与数据流交互并且与底层实现无关。有一些函数可以 return 一个
Box<dyn Handler>
给定一个键,因为有 limited/predictable 个源实现了Handler
.
我已经使用 #[async_trait]
尝试了类似下面的操作,但我无法编译它,主要是错误“无法将特征 Handler
制作成对象”。
是否有 better/more 惯用的方法来解决这个问题?
use async_trait; // 0.1.52
use tokio; // 1.15.0
#[async_trait::async_trait]
trait Handler{
type Output;
async fn connect()->Self::Output;
async fn read_parse(&self)->Vec<i32>;
async fn run(&self) {
for _ in 0..5 {
self.read_parse().await;
}
}
}
struct FooHandler;
#[async_trait::async_trait]
impl Handler for FooHandler {
type Output = FooHandler;
async fn connect() -> Self::Output {
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
FooHandler{}
}
async fn read_parse(&self)->Vec<i32> {
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
vec![1, 2, 3]
}
}
struct BarHandler;
#[async_trait::async_trait]
impl Handler for BarHandler {
type Output = BarHandler;
async fn connect() -> Self::Output {
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
BarHandler{}
}
async fn read_parse(&self)->Vec<i32> {
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
vec![1, 2, 3]
}
}
async fn get_handler(name:&str)->Option<Box<dyn Handler>> {
match name {
"FOO"=>Some(Box::new(FooHandler::connect().await)),
"BAR"=>Some(Box::new(BarHandler::connect().await)),
_=>None
}
}
#[tokio::main]
async fn main() {
let handler = get_handler("FOO").await.unwrap();
handler.run().await;
}
编辑:
根据 cameron1024 的回答,我可以使用以下方法:
use async_trait; // 0.1.52
use tokio; // 1.15.0
#[async_trait::async_trait]
trait Handler: Sync {
async fn connect()->Self
where
Self: Sized;
async fn read_parse(&self)->Vec<i32>;
async fn run(&self) {
for _ in 0..5 {
let res = self.read_parse().await;
println!("{:?}", res);
}
}
}
struct FooHandler;
#[async_trait::async_trait]
impl Handler for FooHandler {
async fn connect() -> Self {
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
println!("connected");
FooHandler{}
}
async fn read_parse(&self)->Vec<i32> {
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
vec![1, 2, 3]
}
}
async fn get_handler(name: &str)->Option<Box<dyn Handler>> {
match name {
"FOO"=>Some(Box::new(FooHandler::connect().await)),
_=>None
}
}
#[tokio::main]
async fn main() {
// let handler = FooHandler::connect().await;
let handler = get_handler("FOO").await.unwrap();
handler.run().await;
}
这里有 2 个问题。
首先,要使异步特征具有默认实现,它要求特征本身具有 Send
或 Sync
(取决于接收者类型)作为超特征(有一个简短的解释在板条箱自述文件的“dyn traits”部分:https://docs.rs/async-trait/latest/async_trait/)
其次,connect
函数不是对象安全的,因为它的 return 类型是关联函数。这与异步无关,这是一个对象安全问题。这个简化的例子有同样的问题:
fn main() {
let x: Box<dyn Foo<Bar = ()>> = Box::new(());
}
trait Foo {
type Bar;
fn connect() -> Self::Bar;
}
impl Foo for () {
type Bar = ();
fn new() -> Self::Bar {
todo!()
}
}
但是,您不太可能希望在特征对象上调用 connect
,因为它没有 self
参数。相反,您可以通过将 Self: Sized
约束添加到 connect
.
然后您可以创建特征对象,但 connect
将不可用。