rust 如何将所有模块添加到array/vector

o75abkj4  于 2023-05-18  发布在  其他
关注(0)|答案(2)|浏览(128)

我有一个Rust项目,其中有以下结构:

|- src
|  |- main.rs
|  |- handler.rs
|  |- modules
|  |  |- handler1.rs
|  |  |- handler2.rs
|  |  |  ...

使用如下伪代码:

//handler.rs
trait Handler {
    fn handle(message: str) -> bool;
}

// handler1.rs
struct Handler1;
impl Handler for Handler1 {
    fn handle(message: str) -> bool {
        //...
    }
}

//handler2.rs
struct Handler2;
impl Handler for Handler2 {
    fn handle(message: str) -> bool {
        //...
    }
}

//main.rs
const handlers: Vec<Handler> = vec![Box::new(Handler1 {}), Box::new(Handler2 {})];

fn handleMessage(myMessage: str) {
    for handler in handlers.iter() {
        if (handler.handle(myMessage)) {
            break;
        };
    }
}

我想从项目的结构中自动创建这个handlers常量。我认为有两种方法可以奏效:
1.在每个模块中使用宏,类似于(?):

//handler2.rs
struct Handler2;
impl Handler for Handler2 {
    fn handle(message: str) -> bool {
        //...
    }
}
registerHandler!{Handler2}

1.也许有一种方法可以在构建时扫描所有模块中的Handler trait实现?(或者扫描一些属性标记的实现?)
我也不知道该怎么做,有没有一个标准的方法来解决这类问题?
如果这是显而易见的,新的生 rust ,所以道歉的任何大错误!我怀疑这是不平凡的,但任何关于问题/标准方法的资源都将非常有帮助。

n8ghc7c1

n8ghc7c11#

到目前为止,最好的解决方案与最低的维护要求,我最终解决了如下:

|- build.rs
|- cargo.toml
|- src
|  |- main.rs
|  |- handler.rs
|  |- handlers.rs  //generated
|  |- handlers
|  |  |- handler1.rs
|  |  |- handler2.rs

handler.rs包含Handler trait:

trait Handler {
    fn handle(message: str) -> bool;
}

handlers.rs在构建时通过build.rs生成:

mod handler1; mod handler2; //...
use crate::handler::*;
use handler1::Handler1; use handler2::Handler2; //...
pub fn get_handlers() -> Vec<Box<dyn Handler>> {
  return vec![Box::new(Handler1), Box::new(Handler2)];
}

cargo.toml必须提供构建条目:

[package]
build = "build.rs"
edition = "2021"
name = "distributed_handlers"
#...

最后build.rs看起来像这样:

use std::{env, fs};
macro_rules! print {
    ($($tokens: tt)*) => {
        println!("cargo:warning={}", format!($($tokens)*))
    }
}

const TEMPLATE: &str = r#"
// Generated bindings to modules
$[mods]
use crate::handler::*;
$[uses]
pub fn get_handlers() -> Vec<Box<dyn Handler>> {
    $[inits]
}
"#;

fn main() {
    let cur = env::current_dir().unwrap();
    let path = String::from(cur.to_string_lossy());
    let mut mods = String::from("");
    let mut uses = String::from("");
    let mut inits: Vec<String> = vec![];
    for entry in fs::read_dir(path.clone() + "/src/handlers").unwrap() {
        let entry: fs::DirEntry = entry.unwrap();
        let name: String = entry.file_name().into_string().unwrap();
        let name: String = String::from(name.split_at(name.len() - 3).0);
        let mut proper: String = name.clone();
        let proper: String = format!("{}{proper}", proper.remove(0).to_uppercase());
        mods.push_str(&String::from(format!("mod {};", name)));
        uses.push_str(&String::from(format!("use {}::{};", name, proper)));
        inits.push(format!("Box::new({})", proper));
    }
    let inits = format!("return vec![{}];", inits.join(","));
    let mut template = String::from(TEMPLATE);
    template = template.replace("$[mods]", &mods);
    template = template.replace("$[uses]", &uses);
    template = template.replace("$[inits]", &inits);
    let _ = fs::write(path.clone() + "/src/handlers.rs", template.clone());
    for s in template.split("\n") {
        print!("{s}");
    }
    print!("Written to {path}/src/handlers.rs")
}

上面的代码假设你的模块与结构体/处理程序本身的名字相同(只是大小写不同)。A full example is on github
编辑:我重构了构建脚本,现在可能可读性更好了:

let current_dir = env::current_dir().unwrap();
let path = current_dir.to_string_lossy().to_string() + "/src/people";
let (mods, uses, inits) = fs::read_dir(path.clone())
    .unwrap()
    .map(|entry| {
        let entry = entry.unwrap();
        let name = entry.file_name().into_string().unwrap();
        let name = String::from(name.split_at(name.len() - 3).0);
        let proper = name.chars().next().unwrap().to_uppercase().to_string() + &name[1..];
        (
            format!("mod {name};\n"),
            format!("use {name}::{proper};\n"),
            format!("Box::new({proper})"),
        )
    })
    .fold(
        (String::new(), String::new(), Vec::new()),
        |(mut mods, mut uses, mut inits), (m, u, i)| {
            mods.push_str(&m);
            uses.push_str(&u);
            inits.push(i);
            (mods, uses, inits)
        },
    );
let inits = inits.join(",");
let template = TEMPLATE
    .replace("$[mods]", &mods)
    .replace("$[uses]", &uses)
    .replace("$[inits]", &inits);
let _ = fs::write(
    current_dir.to_string_lossy().to_string() + "/src/people.rs",
    template.clone(),
);
nlejzf6q

nlejzf6q2#

这是一种很容易通过虚拟调用解决的情况。
你只需要添加dyn来创建一个可以进行虚拟调用的box/reference。

let handlers: Vec<Box<dyn Handler>> = vec![Box::new(Handler1 {}), Box::new(Handler2 {})];

请注意,您不需要存放盒子。您可能需要存储引用:

let h1 = Handler1 {};
let h2 = Handler2 {};
let handlers: &[&dyn Handler] = &[&h1, &h2];

如果你想避免虚调用,有一个递归数据结构的选项。此选项可能提供最佳运行时性能(因为它允许编译器内联处理程序实现并展开循环),但可能导致更长的编译时间。

struct HandlerSet<H1, H2>{
   h1: H1,
   h2: H2,
}

impl<H1: Handler, H2: Handler> Handler for HandlerSet<H1, H2> {
   fn handle(&self, message: &str) -> bool {
      // Chaining handlers happens here.
      self.h1.handle(message) || self.h2.handle(message)
   }
}

impl<H1, H2> HandlerSet<H1, H2> {
   const fn new(h1: H1, h2: H2)->Self{
      Self{h1, h2}
   }
   const fn join(self, h3: H3)->HandlerSet<Self, H3>{
      HandlerSet{h1: self, h2: h3}
   }
}

const HANDLERS: HandlerSet = HandlerSet::new(handler1, handler2).join(handler3).join(handler4);

fn handleMessage(myMessage: &str) {
    HANDLERS.handle(myMessage);
}

如果你的处理程序比较慢或者这个代码的性能不是很关键,我会建议使用动态分派,否则HandlerSet方法会更好。

相关问题