与 Vec 相比,为什么 SmallVec 在存储具有生命周期的类型时具有不同的行为?
Why does SmallVec have different behaviour when storing types with lifetimes compared to Vec?
我有三个示例,一个使用 Vec
,一个使用 SmallVec
,另一个使用我自己实现的 SmallVec
。使用 Vec
和我自己的 SmallVec
编译但使用真正 SmallVec
的不编译。
使用 Vec
的工作示例
use std::borrow::Cow;
use std::collections::HashMap;
pub trait MyTrait {
fn get_by_id(&self, id: usize) -> &ItemTraitReturns;
}
/// IMPORTANT PART IS HERE: `Vec<Cow<'a, str>>`
pub struct ItemTraitReturns<'a>(Vec<Cow<'a, str>>);
/// this implementation only takes items with static lifetime (but other implementations also might have different lifetimes)
pub struct MyTraitStruct {
map: HashMap<usize, ItemTraitReturns<'static>>,
}
impl MyTrait for MyTraitStruct {
fn get_by_id(&self, id: usize) -> &ItemTraitReturns {
let temp: &ItemTraitReturns<'static> = self.map.get(&id).unwrap();
// Works as expected: I expect that I can return `&ItemTraitReturns<'_>`
// when I have `&ItemTraitReturns<'static>` (since 'static outlives everything).
temp
// Will return `&ItemTraitReturns<'_>`
}
}
SmallVec 的失败示例
使用 SmallVec
而不是 Vec
,没有其他更改。
use smallvec::SmallVec;
use std::borrow::Cow;
use std::collections::HashMap;
pub trait MyTrait {
fn get_by_id(&self, id: usize) -> &ItemTraitReturns;
}
/// IMPORTANT PART IS HERE: Uses SmallVec instead of Vec
pub struct ItemTraitReturns<'a>(SmallVec<[Cow<'a, str>; 2]>);
/// this implementation only takes items with static lifetime (but other implementations also might have different lifetimes)
pub struct MyTraitStruct {
map: HashMap<usize, ItemTraitReturns<'static>>,
}
impl MyTrait for MyTraitStruct {
fn get_by_id(&self, id: usize) -> &ItemTraitReturns {
let temp: &ItemTraitReturns<'static> = self.map.get(&id).unwrap();
temp
}
}
error[E0308]: mismatched types
--> src/lib.rs:23:9
|
23 | temp
| ^^^^ lifetime mismatch
|
= note: expected type `&ItemTraitReturns<'_>`
found type `&ItemTraitReturns<'static>`
note: the anonymous lifetime #1 defined on the method body at 18:5...
--> src/lib.rs:18:5
|
18 | / fn get_by_id(&self, id: usize) -> &ItemTraitReturns {
19 | | let temp: &ItemTraitReturns<'static> = self.map.get(&id).unwrap();
20 | | // Error:
21 | | // = note: expected type `&demo2::ItemTraitReturns<'_>`
22 | | // found type `&demo2::ItemTraitReturns<'static>`
23 | | temp
24 | | }
| |_____^
= note: ...does not necessarily outlive the static lifetime
我自己的工作示例SmallVec
当我实现自己的(非常幼稚)SmallVec<[T; 2]>
(称为NaiveSmallVec2<T>
)时,代码也会编译...很奇怪!
use std::borrow::Cow;
use std::collections::HashMap;
/// This is a very naive implementation of a SmallVec<[T; 2]>
pub struct NaiveSmallVec2<T> {
item1: Option<T>,
item2: Option<T>,
more: Vec<T>,
}
impl<T> NaiveSmallVec2<T> {
pub fn push(&mut self, item: T) {
if self.item1.is_none() {
self.item1 = Some(item);
} else if self.item2.is_none() {
self.item2 = Some(item);
} else {
self.more.push(item);
}
}
pub fn element_by_index(&self, index: usize) -> Option<&T> {
match index {
0 => self.item1.as_ref(),
1 => self.item2.as_ref(),
_ => self.more.get(index - 2),
}
}
}
pub trait MyTrait {
fn get_by_id(&self, id: usize) -> &ItemTraitReturns;
}
/// IMPORTANT PART IS HERE: Uses NaiveSmallVec2
pub struct ItemTraitReturns<'a>(NaiveSmallVec2<Cow<'a, str>>);
/// only takes items with static lifetime
pub struct MyTraitStruct {
map: HashMap<usize, ItemTraitReturns<'static>>,
}
impl MyTrait for MyTraitStruct {
fn get_by_id(&self, id: usize) -> &ItemTraitReturns {
let temp: &ItemTraitReturns<'static> = self.map.get(&id).unwrap();
// astonishingly this works!
temp
}
}
我希望 SmallVec
版本像 Vec
版本那样编译。我不明白为什么在某些情况下(在 Vec
的情况下)&ItemTraitReturns<'static>
可以转换为 &ItemTraitReturns<'_>
而在某些情况下(SmallVec
)这是不可能的(我看不到 Vec
/ SmallVec
的影响)。
我不想改变这个特征的生命周期:
pub trait MyTrait {
fn get_by_id(&self, id: usize) -> &ItemTraitReturns;
}
...因为在使用特征时我不关心生命周期(这应该是一个实现细节)...但仍然想使用 SmallVec
.
这种差异似乎是由 Vec
和 SmallVec
之间的 方差 差异引起的。虽然 Vec<T>
在 T
中是 协变的 ,但 SmallVec
似乎是 不变的 。因此,即使生命周期 'a
超过 'b
并且转换应该是安全的,SmallVec<[&'a T; 1]>
也无法转换为 SmallVec<[&'b T; 1]>
。这是触发编译器错误的最小示例:
fn foo<'a, T>(x: SmallVec<[&'static T; 1]>) -> SmallVec<[&'a T; 1]> {
x // Compiler error here: lifetime mismatch
}
fn bar<'a, T>(x: Vec<&'static T>) -> Vec<&'a T> {
x // Compiles fine
}
这似乎是 SmallVec
的缺点。变体由编译器自动确定,有时很难说服编译器假定类型是协变的是安全的。
有个open Github issue for this problem.
不幸的是,我不知道有什么方法可以根据 public 接口来计算类型的变化。差异不包含在文档中,并且取决于类型的私有实现细节。您可以阅读有关方差的更多信息 in the language reference or in the Nomicon.
我有三个示例,一个使用 Vec
,一个使用 SmallVec
,另一个使用我自己实现的 SmallVec
。使用 Vec
和我自己的 SmallVec
编译但使用真正 SmallVec
的不编译。
使用 Vec
的工作示例
use std::borrow::Cow;
use std::collections::HashMap;
pub trait MyTrait {
fn get_by_id(&self, id: usize) -> &ItemTraitReturns;
}
/// IMPORTANT PART IS HERE: `Vec<Cow<'a, str>>`
pub struct ItemTraitReturns<'a>(Vec<Cow<'a, str>>);
/// this implementation only takes items with static lifetime (but other implementations also might have different lifetimes)
pub struct MyTraitStruct {
map: HashMap<usize, ItemTraitReturns<'static>>,
}
impl MyTrait for MyTraitStruct {
fn get_by_id(&self, id: usize) -> &ItemTraitReturns {
let temp: &ItemTraitReturns<'static> = self.map.get(&id).unwrap();
// Works as expected: I expect that I can return `&ItemTraitReturns<'_>`
// when I have `&ItemTraitReturns<'static>` (since 'static outlives everything).
temp
// Will return `&ItemTraitReturns<'_>`
}
}
SmallVec 的失败示例
使用 SmallVec
而不是 Vec
,没有其他更改。
use smallvec::SmallVec;
use std::borrow::Cow;
use std::collections::HashMap;
pub trait MyTrait {
fn get_by_id(&self, id: usize) -> &ItemTraitReturns;
}
/// IMPORTANT PART IS HERE: Uses SmallVec instead of Vec
pub struct ItemTraitReturns<'a>(SmallVec<[Cow<'a, str>; 2]>);
/// this implementation only takes items with static lifetime (but other implementations also might have different lifetimes)
pub struct MyTraitStruct {
map: HashMap<usize, ItemTraitReturns<'static>>,
}
impl MyTrait for MyTraitStruct {
fn get_by_id(&self, id: usize) -> &ItemTraitReturns {
let temp: &ItemTraitReturns<'static> = self.map.get(&id).unwrap();
temp
}
}
error[E0308]: mismatched types
--> src/lib.rs:23:9
|
23 | temp
| ^^^^ lifetime mismatch
|
= note: expected type `&ItemTraitReturns<'_>`
found type `&ItemTraitReturns<'static>`
note: the anonymous lifetime #1 defined on the method body at 18:5...
--> src/lib.rs:18:5
|
18 | / fn get_by_id(&self, id: usize) -> &ItemTraitReturns {
19 | | let temp: &ItemTraitReturns<'static> = self.map.get(&id).unwrap();
20 | | // Error:
21 | | // = note: expected type `&demo2::ItemTraitReturns<'_>`
22 | | // found type `&demo2::ItemTraitReturns<'static>`
23 | | temp
24 | | }
| |_____^
= note: ...does not necessarily outlive the static lifetime
我自己的工作示例SmallVec
当我实现自己的(非常幼稚)SmallVec<[T; 2]>
(称为NaiveSmallVec2<T>
)时,代码也会编译...很奇怪!
use std::borrow::Cow;
use std::collections::HashMap;
/// This is a very naive implementation of a SmallVec<[T; 2]>
pub struct NaiveSmallVec2<T> {
item1: Option<T>,
item2: Option<T>,
more: Vec<T>,
}
impl<T> NaiveSmallVec2<T> {
pub fn push(&mut self, item: T) {
if self.item1.is_none() {
self.item1 = Some(item);
} else if self.item2.is_none() {
self.item2 = Some(item);
} else {
self.more.push(item);
}
}
pub fn element_by_index(&self, index: usize) -> Option<&T> {
match index {
0 => self.item1.as_ref(),
1 => self.item2.as_ref(),
_ => self.more.get(index - 2),
}
}
}
pub trait MyTrait {
fn get_by_id(&self, id: usize) -> &ItemTraitReturns;
}
/// IMPORTANT PART IS HERE: Uses NaiveSmallVec2
pub struct ItemTraitReturns<'a>(NaiveSmallVec2<Cow<'a, str>>);
/// only takes items with static lifetime
pub struct MyTraitStruct {
map: HashMap<usize, ItemTraitReturns<'static>>,
}
impl MyTrait for MyTraitStruct {
fn get_by_id(&self, id: usize) -> &ItemTraitReturns {
let temp: &ItemTraitReturns<'static> = self.map.get(&id).unwrap();
// astonishingly this works!
temp
}
}
我希望 SmallVec
版本像 Vec
版本那样编译。我不明白为什么在某些情况下(在 Vec
的情况下)&ItemTraitReturns<'static>
可以转换为 &ItemTraitReturns<'_>
而在某些情况下(SmallVec
)这是不可能的(我看不到 Vec
/ SmallVec
的影响)。
我不想改变这个特征的生命周期:
pub trait MyTrait {
fn get_by_id(&self, id: usize) -> &ItemTraitReturns;
}
...因为在使用特征时我不关心生命周期(这应该是一个实现细节)...但仍然想使用 SmallVec
.
这种差异似乎是由 Vec
和 SmallVec
之间的 方差 差异引起的。虽然 Vec<T>
在 T
中是 协变的 ,但 SmallVec
似乎是 不变的 。因此,即使生命周期 'a
超过 'b
并且转换应该是安全的,SmallVec<[&'a T; 1]>
也无法转换为 SmallVec<[&'b T; 1]>
。这是触发编译器错误的最小示例:
fn foo<'a, T>(x: SmallVec<[&'static T; 1]>) -> SmallVec<[&'a T; 1]> {
x // Compiler error here: lifetime mismatch
}
fn bar<'a, T>(x: Vec<&'static T>) -> Vec<&'a T> {
x // Compiles fine
}
这似乎是 SmallVec
的缺点。变体由编译器自动确定,有时很难说服编译器假定类型是协变的是安全的。
有个open Github issue for this problem.
不幸的是,我不知道有什么方法可以根据 public 接口来计算类型的变化。差异不包含在文档中,并且取决于类型的私有实现细节。您可以阅读有关方差的更多信息 in the language reference or in the Nomicon.