metaprogramming - "Registering"特征实现+特征对象的工厂方法

标签 metaprogramming rust traits

假设我们想要在运行时切换对象实现,我们会做这样的事情:

pub trait Methods {
    fn func(&self);
}

pub struct Methods_0;
impl Methods for Methods_0 {
    fn func(&self) {
        println!("foo");
    }
}

pub struct Methods_1;
impl Methods for Methods_1 {
    fn func(&self) {
        println!("bar");
    }
}

pub struct Object<'a> { //'
    methods: &'a (Methods + 'a),
}

fn main() {
    let methods: [&Methods; 2] = [&Methods_0, &Methods_1];
    let mut obj = Object { methods: methods[0] };
    obj.methods.func();
    obj.methods = methods[1];
    obj.methods.func();
}

现在,如果有数百个这样的实现怎么办?例如。想象一下收集卡牌游戏的卡牌实现,其中每张卡牌都做着完全不同的事情并且很难一概而论;或者想象一个巨大的状态机的操作码实现。当然,您可以争辩说可以使用不同的设计模式——但这不是这个问题的重点……

想知道是否有任何方法可以让这些 Impl 结构以某种方式“注册”它们自己,以便以后可以通过工厂方法查找它们?我很乐意最终得到一个神奇的宏,甚至是一个插件来完成它。

例如,在 D 中,您可以使用模板来注册实现——如果您出于某种原因不能,您始终可以在编译时检查模块并通过混入生成新代码;还有一些用户定义的属性可以帮助解决这个问题。在 Python 中,您通常会使用元类,这样每次创建新的子类时,它的引用都会存储在元类的注册表中,这样您就可以按名称或参数查找实现;如果实现是简单的函数,这也可以通过装饰器来完成。

理想情况下,在上面的示例中,您可以将对象创建为

Object::new(0)

其中值 0 仅在运行时 已知,它会神奇地返回一个对象 { methods: &Methods_0 },而 new() 的主体将 不要像“方法:[&Methods; 2] = [&Methods_0, &Methods_1]”那样硬编码实现,而应该以某种方式自动推断。

最佳答案

所以,这可能是非常错误的,但它可以作为概念证明。

可以使用 Cargo 的 code generation support在编译时进行自省(introspection),通过解析(在这种情况下不完全解析,但你明白了)当前的实现,并生成使 Object::new() 工作所需的样板.

代码非常复杂,没有任何错误处理,但可以工作。

rustc 1.0.0-dev (2c0535421 2015-02-05 15:22:48 +0000) 上测试 ( See on github )

src/main.rs:

pub mod implementations;
mod generated_glue {
    include!(concat!(env!("OUT_DIR"), "/generated_glue.rs"));
}

use generated_glue::Object;

pub trait Methods {
    fn func(&self);
}

pub struct Methods_2;
impl Methods for Methods_2 {
    fn func(&self) {
        println!("baz");
    }
}

fn main() {
    Object::new(2).func();
}

src/implementations.rs:

use super::Methods;

pub struct Methods_0;
impl Methods for Methods_0 {
    fn func(&self) {
        println!("foo");
    }
}

pub struct Methods_1;
impl Methods for Methods_1 {
    fn func(&self) {
        println!("bar");
    }

}

build.rs:

#![feature(core, unicode, path, io, env)]

use std::env;
use std::old_io::{fs, File, BufferedReader};
use std::collections::HashMap;

fn main() {
    let target_dir      = Path::new(env::var_string("OUT_DIR").unwrap());
    let mut target_file = File::create(&target_dir.join("generated_glue.rs")).unwrap();

    let source_code_path = Path::new(file!()).join_many(&["..", "src/"]);

    let source_files = fs::readdir(&source_code_path).unwrap().into_iter()
        .filter(|path| {
            match path.str_components().last() {
                Some(Some(filename))  => filename.split('.').last() == Some("rs"),
                _                     => false
            }
        });

    let mut implementations = HashMap::new();

    for source_file_path in source_files {
        let relative_path = source_file_path.path_relative_from(&source_code_path).unwrap();
        let source_file_name = relative_path.as_str().unwrap();

        implementations.insert(source_file_name.to_string(), vec![]);
        let mut file_implementations = &mut implementations[*source_file_name];

        let mut source_file = BufferedReader::new(File::open(&source_file_path).unwrap());

        for line in source_file.lines() {
            let line_str = match line {
                Ok(line_str) => line_str,
                Err(_)       => break,
            };

            if line_str.starts_with("impl Methods for Methods_") {
                const PREFIX_LEN: usize = 25;

                let number_len = line_str[PREFIX_LEN..].chars().take_while(|chr| {
                    chr.is_digit(10)
                }).count();

                let number: i32 = line_str[PREFIX_LEN..(PREFIX_LEN + number_len)].parse().unwrap();
                file_implementations.push(number);
            }
        }
    }

    writeln!(&mut target_file, "use super::Methods;").unwrap();

    for (source_file_name, impls) in &implementations {
        let module_name = match source_file_name.split('.').next() {
            Some("main") => "super",
            Some(name)   => name,
            None         => panic!(),
        };

        for impl_number in impls {
            writeln!(&mut target_file, "use {}::Methods_{};", module_name, impl_number).unwrap();
        }
    }

    let all_impls = implementations.values().flat_map(|impls| impls.iter());

    writeln!(&mut target_file, "
pub struct Object;

impl Object {{
    pub fn new(impl_number: i32) -> Box<Methods + 'static> {{
        match impl_number {{
    ").unwrap();

    for impl_number in all_impls {
        writeln!(&mut target_file,
"           {} => Box::new(Methods_{}),", impl_number, impl_number).unwrap();
    }

    writeln!(&mut target_file, "
           _ => panic!(\"Unknown impl number: {{}}\", impl_number),
        }}
    }}
}}").unwrap();
}

生成的代码:

use super::Methods;
use super::Methods_2;
use implementations::Methods_0;
use implementations::Methods_1;

pub struct Object;

impl Object {
    pub fn new(impl_number: i32) -> Box<Methods + 'static> {
        match impl_number {

           2 => Box::new(Methods_2),
           0 => Box::new(Methods_0),
           1 => Box::new(Methods_1),

           _ => panic!("Unknown impl number: {}", impl_number),
        }
    }
}

关于metaprogramming - "Registering"特征实现+特征对象的工厂方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28351778/

相关文章:

scala - 使用附加隐式参数实现特征方法

rust - 为什么编译器不推断 impl 特征返回值的关联类型的具体类型?

C++ Boost::MPL fold 示例 - 参数数量错误

c++ - 你能在 C++ 元编程中做文件 IO 吗?

macros - 如何动态创建具有功能的模块

rust - 在 Rust 中为什么需要类型注释,即使它们在通用特征中明确指定

rust - 无法推断返回包含引用的盒装特征的闭包的生命周期

scala - 如何解决 "Implementation restriction: trait ... accesses protected method ... inside a concrete trait method."

rust - Rust:特征中的类型引用

C++ 类型是地址的抽象? - C++ 模板