我遇到了一個有趣的問題。這是一個操場鏈接,我正在嘗試撰寫一個靈活的任務執行器,該執行器由提供 JSON 物件(如下所示)驅動。
{
"task_type": "foobar",
"payload": {}
}
任務執行器將使用task_type來確定呼叫哪個執行器,然后傳遞關聯的有效負載。我的大多數型別都支持泛型,因為不同的task_types 會支持不同的payloads。我選擇解決這個問題的方法是創建一個特征Task和一個呼叫的單一實作者(目前)RegisteredTask,它本質上只是呼叫一個函式指標。
我唯一的編譯問題是以下問題,這表明我需要將我的注冊表限制為支持采用Task相同輸入并回傳相同輸出的實作(再見泛型)。
Compiling playground v0.0.1 (/playground)
error[E0191]: the value of the associated types `Input` (from trait `Task`), `Output` (from trait `Task`) must be specified
--> src/lib.rs:30:36
|
5 | type Input;
| ----------- `Input` defined here
6 | type Output;
| ------------ `Output` defined here
...
30 | tasks: HashMap<String, Box<dyn Task>>,
| ^^^^ help: specify the associated types: `Task<Input = Type, Output = Type>`
觀看此視頻后,我明白編譯器需要在編譯時知道所有內容的大小,但我認為通過將 trait 放在 a 中,Box我將能夠避免這種情況。
我的問題的根本原因是什么,解決這個問題的慣用方法是什么?
use std::collections::HashMap;
/// Task defines something that can be executed with an input and respond with an output
trait Task {
type Input;
type Output;
fn execute(&self, input: Self::Input) -> Self::Output;
}
/// RegisteredTask is an implementation of Task that simply executes a predefined function and
/// returns it's output. It is generic so that there can be many different types of RegisteredTasks
/// that do different things.
struct RegisteredTask<T, K> {
action: fn(T) -> K,
}
impl<T, K> Task for RegisteredTask<T, K> {
type Input = T;
type Output = K;
/// Executes the registered task's action function and returns the output
fn execute(&self, input: Self::Input) -> Self::Output {
(self.action)(input)
}
}
/// Maintains a collection of anything that implements Task and associates them with a name. This
/// allows us to easily get at a specific task by name.
struct Registry {
tasks: HashMap<String, Box<dyn Task>>,
}
impl Registry {
/// Registers an implementation of Task by name
fn register<T, K>(&mut self, name: &str, task: Box<dyn Task<Input = T, Output = K>>) {
self.tasks.insert(name.to_string(), task);
}
/// Gets an implementation of task by name if it exists, over a generic input and output
fn get<T, K>(&self, name: &str) -> Option<&Box<dyn Task<Input = T, Output = K>>> {
self.tasks.get(name)
}
}
/// An example input for a registered task
#[derive(Debug)]
pub struct FooPayload {}
/// An example output for a registered task
#[derive(Debug)]
pub struct FooOutput {}
/// Executes the named task from the registry and decodes the required payload from a JSON string. The
/// decoding is not shown here for simplicity.
fn execute_task(registry: &Registry, name: &str, json_payload: String) {
match name {
"foobar" => {
let task = registry.get::<FooPayload, FooOutput>(name).unwrap();
let output = task.execute(FooPayload {});
println!("{:?}", output)
}
_ => {}
}
}
#[test]
fn test_execute_task() {
// Create a new empty registry
let mut registry = Registry {
tasks: HashMap::new(),
};
// Register an implementation of Task (RegisteredTask) with the name "foobar"
registry.register::<FooPayload, FooOutput>(
"foobar",
Box::new(RegisteredTask::<FooPayload, FooOutput> {
// This is the function that should be called when this implementation of Task is invoked
action: |payload| {
println!("Got payload {:?}", payload);
FooOutput {}
},
}),
);
// Attempt to invoke whatever implementation of Task (in this case a RegisteredTask) is named 'foobar'.
execute_task(®istry, "foobar", String::from("{}"));
}
uj5u.com熱心網友回復:
編譯器對你不滿的原因是具有不同泛型值的泛型型別實際上是不同的型別。這意味著,如果您有一個trait Foo<T>, 并與T == Baror一起使用它T == Barcode,那么您在內部有兩個不同的特征,而不是一個。關聯型別也是如此。一個實作的結構Foo<InputType = Bar>和Foo<InputType = Barcode>實作不同特征的不同結構。
這就是為什么dyn Task在你的情況下寫作沒有意義。編譯器需要知道Task您要存盤的trait 物件的確切變體。
將泛型引數(或關聯型別)作為方法引數也可能在以后被證明是一個挑戰,因為具有此類特征的特征通常不是物件安全的,因此您不能使用dyn Task它們。
您將如何使用任務的結果也不太清楚,因為您不知道注冊表中的 trait 物件將回傳的特定型別。
更好的設計應該是只有一個非通用特征。然后,每個實作此特征的結構都可以使用Payload型別作為輸入構造并將其存盤在內部。然后.execute()可以不帶引數呼叫。
作為結果回傳的型別.execute()取決于您將使用它的方式。它可以是列舉或序列化值。
對于您想要實作的目標,可能有更好的設計,但這就是我想到的。
uj5u.com熱心網友回復:
感謝@Maxim,我重新設計了我的實作,而不是使用通用的注冊任務型別,我創建了非通用的任務特定型別。例如,這里有一個執行 shell 命令的任務。
pub struct Exec {
command: String,
args: Vec<String>,
}
我繼續Task為此任務實作了trait,并且因為有效負載現在是專門存盤在Task實作上的型別,所以我不必擔心 trait 的任何通用實作。
impl Task for Exec {
async fn execute(&self) -> Result<Value> {
// ....
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/339401.html
