如何定义适配器特征,其中某些实现需要 &self 的生命周期?
How to define an adapter trait where some implementations need a lifetime on &self?
我正在为不同的键值存储编写一组基准测试,并希望有一个适配器特性可以在所有基准测试中使用,然后为每个键值存储实现它。
这对他们中的两个人来说效果很好。但是第三个要求我在trait上加lifetime,和borrow checker折腾了一整天,我好像还是做不对
我已经将它提炼成这个最小的复制品:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=54fec74cb70c63c03f25ec7a9dfc7e60
我不明白的是为什么 txn
上的借用比 benchmark()
的范围长。在我看来,它应该只为那一行而活。
如何定义 AdapterTransaction
特征来解决这个问题,它仍然允许实现选择自己的 return 类型?
编辑
补充说我需要能够使用具有工厂特性的 AdapterTransaction
实现
你 first playground 的主要问题是 &self
的生命周期与特性的通用生命周期相同。
pub trait AdapterTransaction<'a, T: AsRef<[u8]>> {
fn get(&'a self, key: &[u8]) -> Option<T>;
}
因为它们是相同的,所以它要求底层类型的借用至少与类型本身一样长。这是不正确的,因为即使类型被拥有,借用也只会在函数调用期间持续。在 benchmark<'a,...>()
中,生命周期 'a
由调用者选择,并且该函数内的借用不可能足够长。删除 benchmark
上的 'a
参数并将其替换为排名更高的特征边界 (playground)。
fn benchmark<U: AsRef<[u8]>, T: for<'a> AdapterTransaction<'a, U>>(txn: T)
在此示例中,调用者不再选择 'a
,因此编译器可以自由使用调用的有效生命周期。
至于你问题的第二部分,特征可以定义关联类型,这些类型可以根据实现而改变。您可以拥有一个具有关联 Output
的特征,它可以针对每个已实现的类型进行更改。泛型参数与关联类型有很大的不同,因为在前一种情况下,您可以为同一类型实现特征的多个泛型变体。 (例如,From<T>
就是这样工作的)。
pub trait AdapterTransaction<'a> {
type Output;
fn get(&'a self, key: &[u8]) -> Option<Self::Output>;
}
impl<'a> AdapterTransaction<'a> for AdapterImpl {
type Output = &'a [u8];
fn get(&'a self, key: &[u8]) -> Option<Self::Output> {
Some(self.txn.get(&key))
}
}
fn benchmark<T>(txn: T)
where
for<'a> T: AdapterTransaction<'a>,
{
let _ = txn.get(&[]).unwrap();
}
编辑: 我最初的一些假设并不准确,如果以非冲突方式使用特征生命周期,则没有必要在 &'a 类型上实施。
我正在为不同的键值存储编写一组基准测试,并希望有一个适配器特性可以在所有基准测试中使用,然后为每个键值存储实现它。
这对他们中的两个人来说效果很好。但是第三个要求我在trait上加lifetime,和borrow checker折腾了一整天,我好像还是做不对
我已经将它提炼成这个最小的复制品:https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=54fec74cb70c63c03f25ec7a9dfc7e60
我不明白的是为什么 txn
上的借用比 benchmark()
的范围长。在我看来,它应该只为那一行而活。
如何定义 AdapterTransaction
特征来解决这个问题,它仍然允许实现选择自己的 return 类型?
编辑
补充说我需要能够使用具有工厂特性的 AdapterTransaction
实现
你 first playground 的主要问题是 &self
的生命周期与特性的通用生命周期相同。
pub trait AdapterTransaction<'a, T: AsRef<[u8]>> {
fn get(&'a self, key: &[u8]) -> Option<T>;
}
因为它们是相同的,所以它要求底层类型的借用至少与类型本身一样长。这是不正确的,因为即使类型被拥有,借用也只会在函数调用期间持续。在 benchmark<'a,...>()
中,生命周期 'a
由调用者选择,并且该函数内的借用不可能足够长。删除 benchmark
上的 'a
参数并将其替换为排名更高的特征边界 (playground)。
fn benchmark<U: AsRef<[u8]>, T: for<'a> AdapterTransaction<'a, U>>(txn: T)
在此示例中,调用者不再选择 'a
,因此编译器可以自由使用调用的有效生命周期。
至于你问题的第二部分,特征可以定义关联类型,这些类型可以根据实现而改变。您可以拥有一个具有关联 Output
的特征,它可以针对每个已实现的类型进行更改。泛型参数与关联类型有很大的不同,因为在前一种情况下,您可以为同一类型实现特征的多个泛型变体。 (例如,From<T>
就是这样工作的)。
pub trait AdapterTransaction<'a> {
type Output;
fn get(&'a self, key: &[u8]) -> Option<Self::Output>;
}
impl<'a> AdapterTransaction<'a> for AdapterImpl {
type Output = &'a [u8];
fn get(&'a self, key: &[u8]) -> Option<Self::Output> {
Some(self.txn.get(&key))
}
}
fn benchmark<T>(txn: T)
where
for<'a> T: AdapterTransaction<'a>,
{
let _ = txn.get(&[]).unwrap();
}
编辑: 我最初的一些假设并不准确,如果以非冲突方式使用特征生命周期,则没有必要在 &'a 类型上实施。