为特征对象“注册”特征实现+工厂方法

时间:2015-02-05 18:51:26

标签: 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结构是否有任何方式以某种方式&#34;注册&#34;他们自己以后可以通过工厂方法查找它们?我很乐意最终得到一个神奇的宏甚至插件来实现它。

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

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

Object::new(0)

其中值0仅在运行时中已知,它会神奇地返回一个Object {methods:&amp; Methods_0},而new()的主体将的实现硬编码如此&#34;方法:[&amp;方法; 2] = [&amp; Methods_0,&amp; Methods_1]&#34;相反,它应该以某种方式自动推断。

1 个答案:

答案 0 :(得分:1)

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

可以使用Cargo的code generation support在编译时进行内省,通过解析(在这种情况下不完全解析,但你明白了)当前的实现,并生成使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),
        }
    }
}
相关问题