微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

锈能保证我用正确的对象池释放对象吗? 品牌仿射,不是线性的只要放下它!

如何解决锈能保证我用正确的对象池释放对象吗? 品牌仿射,不是线性的只要放下它!

说我已经定义了自己的对象池结构。在内部,它保留所有对象的Vec和一些数据结构,以使其知道向量中当前分发了哪些项目以及哪些是免费的。它具有一个分配方法,该方法返回向量中未使用项目的索引,并且可以使用自由方法再次告知向量中索引处的池。

我是否可以通过类型系统和借位检查器保证我将对象释放回正确的池的方式来定义对象池的API?这是在假设我可能有多个相同类型的池实例的情况。在我看来,对于常规的全局分配器,铁锈不必担心这个问题,因为只有一个全局分配器。

用法示例:

fn foo() {
    let new_obj1 = global_pool1.allocate();
    let new_obj2 = global_pool2.allocate();

    // do stuff with objects

    global_pool1.free(new_obj2); // oops!,giving back to the wrong pool
    global_pool2.free(new_obj1); // oops!,giving back to the wrong pool
}

解决方法

首先,要考虑的事情是,将某个项目插入Vec有时会导致其重新分配和更改地址,这意味着Vec中所有对项目的现有引用均无效。我以为您打算让用户保留对Vec中项目的引用并同时插入新项目,但是可惜这是不可能的。

解决此问题的一种方法是generational_arena使用的方法。插入对象将返回索引。您可以调用arena.remove(index)释放对象,并调用arena.get[_mut](index)获取对象的引用,借用整个舞台。

但是,为了便于讨论,我们假设您有一种方法可以在插入新项目并执行其他可能需要的操作时保留对竞技场的引用。考虑到引用本质上是一个指针,答案是否定的,所以没有办法自动记住它的来源。但是,您可以创建类似于BoxRc等的“智能指针”,以保留对竞技场的引用,以便在放置对象时释放对象。

例如(非常粗糙的伪代码):

struct Arena<T>(Vec<UnsafeCell<T>>);

struct ArenaMutPointer<'a,T> {
    arena: &'a Arena,index: usize,}

impl<T> DerefMut for ArenaPointer<'_,T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        unsafe { self.arena[self.index].get() as &mut T }
    }
}

impl<T> Drop for ArenaPointer<'_,T> {
    fn drop(&mut self) {
        self.arena.free(self.index);
    }
}
,

品牌

使用生命周期作为 brand 品牌,将特定变量与一个其他变量绑定,而没有其他变量。

为了获得可确保在范围之内的索引,人们进行了显着的探索:创建时检查一次,以后再使用。

不幸的是,这需要创建不变的生存期,以防止编译器将多个生存期“合并”在一起,而且可能还没有看到任何引人注目的API。

仿射,不是线性的。

同样重要的是要注意Rust没有线性类型系统,而是仿射系统。

线性类型系统是每个值精确使用一次 的系统,而Affine类型系统是每个值最多使用 使用一次的系统。 / p>

在这里的结果是,很容易意外忘记将对象返回到池中。尽管在Rust中泄漏对象始终是安全的-并且mem::forget是这样做的一种简便方法-但这些情况通常像拇指酸痛一样突出,因此相对容易审核。另一方面,仅忘记将值返回给池会导致意外泄漏,这可能会花费相当长的时间。

只要放下它!

因此,解决方案是只让值返回其在其Drop实现中来自的池:

  • 这使我们无法忘记意外返回。
  • 它要求对象保留对它来自的池的引用,从而不可能将它意外地返回到错误的池。

当然要付出一定的代价,即与对象一起存储额外的8个字节。

这里有两种潜在的解决方案:

  • 细指针:struct Thin<'a,T>(&'a Pooled<'a,T>);,其中struct Pooled<'a,T>(&'a Pool<T>,T);
  • 胖指针:struct Fat<'a,&'a T);

为简单起见,我建议从Fat替代开始:它更简单。

然后,DropThin的{​​{1}}实现只将指针返回到池中。

,

您可以使用零大小类型(简称ZST)来获取所需的API,而无需其他指针的开销。

这里是2个池的实现,可以使用宏生成“标记”结构(P1P2等)进行扩展,以支持任意数量的池。 一个主要的缺点是忘记使用池来free会“泄漏”内存。

Ferrous Systems blog post提供了许多您可能会感兴趣的改进,尤其是在您静态分配池的情况下,并且它们还具有许多技巧来发挥P1的可见性,以便用户将无法滥用API。


use std::marker::PhantomData;
use std::{cell::RefCell,mem::size_of};

struct Index<D>(usize,PhantomData<D>);
struct Pool<D> {
    data: Vec<[u8; 4]>,free_list: RefCell<Vec<bool>>,marker: PhantomData<D>,}

impl<D> Pool<D> {
    fn new() -> Pool<D> {
        Pool {
            data: vec![[0,0]],free_list: vec![true].into(),marker: PhantomData::default(),}
    }
    
    fn allocate(&self) -> Index<D> {
        self.free_list.borrow_mut()[0] = false;
        
        Index(0,self.marker)
    }
    
    fn free<'a>(&self,item: Index<D>) {
        self.free_list.borrow_mut()[item.0] = true;
    }
}

struct P1;
fn create_pool1() -> Pool<P1> {
    assert_eq!(size_of::<Index<P1>>(),size_of::<usize>());
    Pool::new()
}

struct P2;
fn create_pool2() -> Pool<P2> {
    Pool::new()
}


fn main() {
    
    let global_pool1 = create_pool1();
    let global_pool2 = create_pool2();
    
    let new_obj1 = global_pool1.allocate();
    let new_obj2 = global_pool2.allocate();

    // do stuff with objects

    global_pool1.free(new_obj1);
    global_pool2.free(new_obj2);

    global_pool1.free(new_obj2); // oops!,giving back to the wrong pool
    global_pool2.free(new_obj1); // oops!,giving back to the wrong pool
}

尝试使用错误的池进行释放会导致:

error[E0308]: mismatched types
  --> zpool\src\main.rs:57:23
   |
57 |     global_pool1.free(new_obj2); // oops!,giving back to the wrong pool
   |                       ^^^^^^^^ expected struct `P1`,found struct `P2`
   |
   = note: expected struct `Index<P1>`
              found struct `Index<P2>`

Link to playground

可以对此进行一些改进,以便借用检查器使用以下命令强制Index不会过期Pool

fn allocate(&self) -> Index<&'_ D> {
    self.free_list.borrow_mut()[0] = false;
        
    Index(0,Default::default())
}

因此,如果Index处于活动状态时删除了池,则会出现此错误:

error[E0505]: cannot move out of `global_pool1` because it is borrowed
  --> zpool\src\main.rs:54:10
   |
49 |     let new_obj1 = global_pool1.allocate();
   |                    ------------ borrow of `global_pool1` occurs here
...
54 |     drop(global_pool1);
   |          ^^^^^^^^^^^^ move out of `global_pool1` occurs here
...
58 |     println!("{}",new_obj1.0);
   |                    ---------- borrow later used here

Link to playground

另外,a link to playground with Item API(返回Item,仅返回Index

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。