如何通过原始指针进行哈希?



我想创建一个提供两步写入和提交的函数,如下所示:

// Omitting locking for brevity 
struct States {
commited_state: u64,
// By reference is just a placeholder - I don't know how to do this
pending_states: HashSet<i64>
}
impl States {
fn read_dirty(&self) ->  {
// Sum committed state and all non committed states
self.commited_state + 
pending_states.into_iter().fold(sum_all_values).unwrap_or(0)
}
fn read_committed(&self) {
self.commited_state
}
}
let state_container = States::default();

async fn update_state(state_container: States, new_state: i64) -> Future {
// This is just pseudo code missing locking and such

// I'd like to add a reference to new_state
state_container.pending_states.insert(
new_state
)
async move {
// I would like to defer the commit
// I add the state to the commited state
state_container.commited_state =+ new_state;
// Then remove it *by reference* from the pending states
state_container.remove(new_state)
}
}

我希望我可以这样称呼它

let commit_handler = update_state(state_container, 3).await;
// Do some external transactional stuff
third_party_transactional_service(...)?
// Commit if the above line does not error
commit_handler.await;

我遇到的问题是hashmap和hashset,基于它们的值而不是它们的实际引用的哈希值-所以我不能通过引用来删除它们。

我很欣赏这个问题有点长,但我只是想给我想做的事情更多的背景。我知道,在一个典型的数据库中,您通常会使用一个原子计数器来生成事务ID,但是当指针引用足够时,这感觉有点多余。

但是,我不想使用不安全的方法获取指针值,因为做一些相对简单的事情似乎有点偏离。

rust中的值不像在其他语言中那样有标识。你需要以某种方式赋予他们一个身份。在你的问题中,你已经找到了两种方法来做到这一点:一个包含在值中的ID,或者作为指针的值的地址。

选项1:包含在值

中的ID拥有一个静态的AtomicUsizeusizeID是微不足道的(原子具有内部可变性)。

use std::sync::atomic::{AtomicUsize, Ordering};
// No impl of clone/copy as we want these IDs to be unique.
#[derive(Debug, Hash, PartialEq, Eq)]
#[repr(transparent)]
pub struct OpaqueIdentifier(usize);
impl OpaqueIdentifier {
pub fn new() -> Self {
static COUNTER: AtomicUsize = AtomicUsize::new(0);

Self(COUNTER.fetch_add(1, Ordering::Relaxed))
}

pub fn id(&self) -> usize {
self.0
}
}

现在你的地图键变成了usize,你就完成了。

让这是一个单独的类型,不实现CopyClone允许你有一个"拥有的唯一id"的概念;然后每个具有这些ID之一的类型都被强制不为Copy,并且Cloneimpl将需要获得一个新的ID。

(您可以使用与usize不同的整数类型。)

选项2:指向值的指针这在Rust中更具挑战性,因为Rust中的值默认情况下是可移动的。为了使这种方法可行,您必须通过绑定来删除此功能。

要使此工作,以下两个必须为真:

  • 你pin你用来提供身份的值,和
  • 固定值是!Unpin(否则固定仍然允许移动!),可以通过在值的类型中添加PhantomPinned成员来强制。

请注意,只有当对象在其整个生命周期中保持固定状态时,才维持固定契约。为了加强这一点,此类对象的工厂应该只分配固定的盒子。

这可能会使您的API复杂化,因为如果没有unsafe,您无法获得对固定值的可变引用。pin文档中有如何正确操作的示例。

假设您已经完成了所有这些,那么您可以使用*const T作为映射中的键(其中T是固定类型)。注意,转换为指针是安全的——转换回引用是不安全的。因此,您可以使用some_pin_box.get_ref() as *const _来获取您将用于查找的指针。


固定框方法有相当明显的缺点:

  • 所有用于提供身份的值都必须在堆上分配(除非使用本地固定,这不太可能符合人体工程学-pin!宏使此更简单是实验性的)。
  • 提供身份的类型的实现必须接受self作为&Pin&mut Pin,要求不安全代码改变内容。

在我看来,它甚至不是一个很好的语义适合这个问题。内存中的位置"one_answers";identity"是不同的东西,只是偶然的,前者有时可以用来实现后者。在内存中移动一个值会改变它的身份,这有点傻,不是吗?

我只是去添加一个ID的值。这是一个非常明显的模式,并且没有严重的缺点。

最新更新