2

语境

我有一个DataStore<Key,Value>特征可以抽象出数据存储。(例如,我可以为包装VecsHashMaps. 编辑self:在特征定义中添加了引用,如下。)

// Stores data of type V indexed by K
trait DataStore<K, V> {
    fn new() -> Self;
    fn get(&self, k: K) -> Option<&V>;
    fn insert(&mut self, v: V) -> Option<K>;
}

我现在想定义一个Thing包含两个数据存储的结构:一种类型Apple<T>的存储, 和另一种类型的存储,Banana<T>。这是我的第一次尝试,

// some objects I'd like to keep in DataStores
struct Apple<T> { shine: T }
struct Banana<T> { spottedness: T }

// Attempt #1: cumbersome, have to always specify generic constraints when 
//             using Thing elsewhere
pub struct Thing<K, T, AppleStore, BananaStore>
    where AppleStore: DataStore<K, Apple<T>>,
          BananaStore: DataStore<K, Banana<T>>
{
    apple_store: AppleStore,
    banana_store: BananaStore,
}

这种方法使用起来很麻烦,因为<K, T, AppleStore, BananaStore> where ...每当我想传递Thing给一个函数或实现一个特征时,我必须总是输入,Thing即使所述函数或特征不关心这两个商店中的任何一个。例如,如果我想实现一个特征,它对Thing其他类型的属性执行一些不相关的操作,T我仍然必须告诉它关于K,AppleStoreBananaStore.

我了解了类型别名并尝试了以下方法:


// Attempt #2: looks easier to use. only two generics on Thing: the type of
//             the indexes and the type of the internal parameters. not sure
//             about the role of dyn, though, since this should be checkable
//             at compile time
type AppleStore<K, T> = dyn DataStore<K, Apple<T>>;
type BananaStore<K, T> = dyn DataStore<K, Banana<T>>;


pub struct Thing<K, T> {
    apple_store: AppleStore<K, T>,
    banana_store: BananaStore<K, T>,
}

当我尝试BananaStoreThing的构造函数中创建一个新的时,出现了一个新问题。这在尝试 #1 中是允许的,因为特征允许实现 (1) 不&self作为参数和 (2) 返回类型的函数Self。但这在尝试 #2 中是不允许的,因为动态特征需要事物是Sized 并且返回是不允许的Self。(或者其他的东西?)


impl<K, T> Thing<K, T> {
    pub fn new(apple_store: AppleStore<K, T>) {
        Thing {
            apple_store: apple_store,
            banana_store: BananaStore::new() // not allowed to do this with
                                             // dynamic type aliases?
    }
}

问题

我是否需要创建一个BananaStore外部Thing并将其作为参数传递,或者有没有办法BananaStore从外部隐藏构造?ThingBuilder如果我的目标之一是隐藏不必要的(可选)对象创建,我想类似 a的方法可能是一种有效的方法。但我也不想提供一个默认的实现者:用户应该明确声明使用BananaStore哪种类型。DataStoreBananaStore

我以这种方式提出问题,因为最终我希望Thing'在多个实例AppleStore之间实际共享;Thing也就是说,多个可以在商店Things中引用相同的内容。Apple<T>但每个Thing人都会有自己的BananaStore。我知道这需要使用RcorArc或类似的东西,AppleStore但是当我到达它时我会越过那座桥。

4

1 回答 1

2

DataStore

DataStore有一些问题会给您带来问题。getandset函数需要引用self才能工作。没有参考self意味着他们能够从无到有地产生参考,并且会导致你一生的问题。该get函数还应该接受一个引用以匹配Map函数的方式。通过添加引用删除K实现Copy和防止未来生命周期问题的约束。

trait DataStore<K, V> {
    // This will make it impossible to use this an anonymous trait like in attempt 2
    fn new() -> Self;

    // They need a reference to self to store and retrieve data
    fn get(&self, k: &K) -> Option<&V>;
    fn insert(&mut self, v: V) -> Option<K>;
}

从现在开始,这些小的更改应该有助于使调试更容易一些。

尝试 1

通过对 的调整DataStore,我们已经解决了生命周期的问题。您需要做的就是添加 aPhantomData来补偿额外的类型参数。

我还将删除where示例中的子句,因为我过去在将其添加到结构时遇到过问题。

// AppleStore and DataStore converted to single letters to make it a bit more concise
struct Thing<K, T, A: DataStore<K, Apple<T>>, B: DataStore<K, Banana<T>>> {
    apple_store: A,
    banana_store: B,
    _phantom: PhantomData<(K, T)>,
}

除此之外,这可能应该是您的选择。由于它不涉及尝试 2 中的匿名特征,因此未来的生命周期应该没有任何问题,并且更容易使用。

尝试 2

这种尝试失败的原因是因为动态大小的匿名特征使得编译器不确定一个字段在内存中结束而下一个字段开始。我们可以通过首先装箱匿名特征来解决这个问题。您可以将其视为在结构中存储指针而不是实际数据。

pub struct Thing<K, T> {
    apple_store: Box<dyn DataStore<K, Apple<T>> + 'static>,
    banana_store: Box<dyn DataStore<K, Banana<T>> + 'static>,
}

'static本质上只是意味着DataStore不包含任何可能限制其生命周期的引用。你也可以让生命不受限制,但这几乎会在未来给你带来更多的问题。

pub struct Thing<'a, 'b, K, T> {
    apple_store: Box<dyn DataStore<K, Apple<T>> + 'a>,
    banana_store: Box<dyn DataStore<K, Banana<T>> + 'b>,
}
于 2021-02-18T18:33:28.390 回答