与 Vec 相比,为啥 SmallVec 在存储具有生命周期的类型时会有不同的行为?

Posted

技术标签:

【中文标题】与 Vec 相比,为啥 SmallVec 在存储具有生命周期的类型时会有不同的行为?【英文标题】:Why does SmallVec have different behaviour when storing types with lifetimes compared to Vec?与 Vec 相比,为什么 SmallVec 在存储具有生命周期的类型时会有不同的行为? 【发布时间】:2019-05-21 12:40:37 【问题描述】:

我有三个示例,一个使用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&lt;[T; 2]&gt;(称为NaiveSmallVec2&lt;T&gt;)时,代码也会编译...非常奇怪!

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 的情况下)&amp;ItemTraitReturns&lt;'static&gt; 可以转换为&amp;ItemTraitReturns&lt;'_&gt; 而在某些情况下(SmallVec)这是不可能的(我看不到@ 的影响987654345@/SmallVec)。

我不想改变这个特质的生命周期:

pub trait MyTrait 
    fn get_by_id(&self, id: usize) -> &ItemTraitReturns;

...因为在使用 trait 时我不关心生命周期(这应该是一个实现细节)...但仍然想使用 SmallVec

【问题讨论】:

看来How do I add references to a container when the borrowed values are created after the container?的答案可能会回答您的问题;特别是关于may_dangle的部分。如果没有,请edit您的问题来解释差异。否则,我们可以将此问题标记为已回答。 【参考方案1】:

这种差异似乎是由VecSmallVec 之间的方差 差异引起的。虽然Vec&lt;T&gt;T 中是协变的,但SmallVec 似乎是不变的。因此,SmallVec&lt;[&amp;'a T; 1]&gt; 不能转换为 SmallVec&lt;[&amp;'b T; 1]&gt;,即使生命周期 'a 的寿命超过 'b,并且转换应该是安全的。这是触发编译器错误的最小示例:

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。

不幸的是,我不知道如何根据公共接口找出类型的差异。方差不包含在文档中,并且取决于类型的私有实现细节。您可以阅读更多关于方差in the language reference 或in the Nomicon 的信息。

【讨论】:

还询问了有关终身方差的其他问题:Covariance of Box type in Rust; What is an example of contravariant use in Rust? 非常感谢!这正是我正在寻找的:* github.com/bluss/arrayvec/issues/96 * doc.rust-lang.org/nomicon/subtyping.html#variance 不幸的是,我似乎必须在我的项目中摆脱 SmallVec(直到它像 Vec 一样协变):我项目中的 ItemTraitReturns 类型更多复杂,并且这种不变性继承了整个层次结构。

以上是关于与 Vec 相比,为啥 SmallVec 在存储具有生命周期的类型时会有不同的行为?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 pbkdf2 实现这么慢(与 SQLCipher 相比)?

为啥 glm::vec 将 vec 值表示为联合?

与 ObjectContext 相比,为啥在 EF 4.1 中插入实体如此缓慢?

为啥将图像(网址)存储在数据库中 [网站]

为啥 NegativeBinomialP 与 R 相比给出不同的系数?

与实际提供的相比,为啥在 SVM 中排名的特征数量更少?