如何避免将具体结构更改为泛型而产生连锁反应?

mus*_*hin 2 generics traits rust

我有一个看起来像这样的配置结构:

struct Conf {
    list: Vec<String>,
}
Run Code Online (Sandbox Code Playgroud)

该实现是在内部填充该list成员的,但是现在我决定将该任务委托给另一个对象。所以我有:

trait ListBuilder {
    fn build(&self, list: &mut Vec<String>);
}

struct Conf<T: Sized + ListBuilder> {
    list: Vec<String>,
    builder: T,
}

impl<T> Conf<T>
where
    T: Sized + ListBuilder,
{
    fn init(&mut self) {
        self.builder.build(&mut self.list);
    }
}

impl<T> Conf<T>
where
    T: Sized + ListBuilder,
{
    pub fn new(lb: T) -> Self {
        let mut c = Conf {
            list: vec![],
            builder: lb,
        };
        c.init();
        c
    }
}
Run Code Online (Sandbox Code Playgroud)

这似乎工作正常,但是现在我在所有使用的地方Conf都必须更改它:

fn do_something(c: &Conf) {
    // ...
}
Run Code Online (Sandbox Code Playgroud)

变成

fn do_something<T>(c: &Conf<T>)
where
    T: ListBuilder,
{
    // ...
}
Run Code Online (Sandbox Code Playgroud)

由于我有很多这样的功能,所以这种转换很麻烦,尤其是因为Conf该类的大多数用法都不在乎ListBuilder-这是一个实现细节。我担心如果我向中添加了另一个泛型类型Conf,现在我必须返回并在各处添加另一个泛型参数。有什么办法可以避免这种情况?

我知道我可以对列表构建器使用闭包代替,但是我的Conf结构需要附加的约束Clone,并且实际的构建器实现更加复杂,并且在构建器中具有多个功能和某些状态,这使得闭包成为可能难以处理。

red*_*ime 6

您可以使用trait 对象 Box<dyn ListBuilder>来隐藏构建器的类型。一些后果是动态分派(对build方法的调用将通过虚函数表)、额外的内存分配(装箱的 trait 对象)以及对 trait 的 一些限制ListBuilder

trait ListBuilder {
    fn build(&self, list: &mut Vec<String>);
}

struct Conf {
    list: Vec<String>,
    builder: Box<dyn ListBuilder>,
}

impl Conf {
    fn init(&mut self) {
        self.builder.build(&mut self.list);
    }
}

impl Conf {
    pub fn new<T: ListBuilder + 'static>(lb: T) -> Self {
        let mut c = Conf {
            list: vec![],
            builder: Box::new(lb),
        };
        c.init();
        c
    }
}
Run Code Online (Sandbox Code Playgroud)


She*_*ter 5

尽管泛型类型似乎可以“感染”您的其余代码,但这正是它们有益的原因!编译器有关使用的大小以及具体使用哪种类型的知识使它可以做出更好的优化决策。

话虽如此,这可能很烦人!如果实现特征的类型很少,那么还可以构造这些类型的枚举并委托给子实现:

struct FromUser;
impl ListBuilder for FromUser { /**/ }

struct FromFile;
impl ListBuilder for FromFile { /**/ }

enum MyBuilders {
    User(FromUser),
    File(FromFile),
}

impl ListBuilder for MyBuilders {
    fn build(&self, list: &mut Vec<String>) {
        use MyBuilders::*;
        match *self {
            User(ref u) => u.build(list),
            File(ref f) => f.build(list),
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在的具体类型是 Conf<MyBuilders>,您可以使用类型别名来隐藏它。

当我希望能够在测试过程中将测试实现注入代码中时,使用此方法效果很好,但是在生产代码中使用了一组固定的实现。