特征是否可以返回由特征上的通用类型指定的特征对象?

时间:2019-05-13 09:32:48

标签: rust

我想定义一个这样的特征:

pub trait Provider<T> {
    fn load(&self, name: &str) -> Box<dyn T>;
}

但这是不可能的:

error[E0404]: expected trait, found type parameter `T`
 --> src/lib.rs:2:47
  |
2 |         fn load(&self, name: &str) -> Box<dyn T>;
  |                                               ^ not a trait

可能的解决方案是删除dyn关键字,但是我希望Provider的实现者返回特征对象,如下所示:

pub struct MaterialProvider {}

trait Material {}

impl Provider<Material> for MaterialProvider {
    fn load(&self, name: &str) -> Box<dyn Material> { /*...*/ }
}

可以表达这样的概念吗?

我想用它来创建一个单一的“资源库”,该库可用于创建各种类型的对象。在编译时可能不知道应支持的确切类型,并且它们不一定需要实现相同的接口。

在C ++中,解决上述问题的一种方法如下:

#include <iostream>
#include <map>

class A {
  public:
    static std::string identifier()  {
        return "A";
    }
};

class B {
  public:
    static std::string identifier()  {
        return "B";
    }
};

class ProviderBase {};

template <typename T>
class Provider : public ProviderBase {
  public:
    virtual T* load() = 0;
};

class ProviderA : public Provider<A> {
  public:
    A* load() {
        return new A;
    }
};

class Manager {
    std::map<std::string, ProviderBase*> providers;

  public:
    template<typename T>
    void register_provider(Provider<T>* provider) {
        providers[T::identifier()] = provider;
    }

    template<typename T>
    T* load() {
        auto p = providers.find(T::identifier());

        if (providers.end() != p) {
            return static_cast<Provider<T>*>(p->second)->load();
        }

        return nullptr;
    }
};

int main(int argc, char* argv[]) {
    Manager m;

    ProviderA provider_a;

    m.register_provider(&provider_a);

    if (m.load<A>()) {
        std::cout << "Loaded A" << std::endl;
    } else {
        std::cout << "Could not load A" << std::endl;
    }

    if (m.load<B>()) {
        std::cout << "Loaded B" << std::endl;
    } else {
        std::cout << "Could not load B" << std::endl;
    }

    return 0;
}

1 个答案:

答案 0 :(得分:1)

通用特征可以在类型上参数化,但不能在另一个特征上参数化。因此,在trait Provider<T>中,T是一种类型,而不是特征,这意味着没有dyn T这样的东西。

但是,如果类型参数本身被允许作为特征对象类型,则这里需要的只是类型参数。为此,您只需要取消默认的Sized边界,因为特征对象没有大小:

pub trait Provider<T: ?Sized> {
    fn load(&self, name: &str) -> Box<T>;
}

pub struct MaterialProvider {}

trait Material {}

impl Provider<dyn Material> for MaterialProvider {
    fn load(&self, name: &str) -> Box<dyn Material> { /* ... */ }
}