我想创建一个提供两步写入和提交的函数,如下所示:
// 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拥有一个静态的AtomicUsize
的usize
ID是微不足道的(原子具有内部可变性)。
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
,你就完成了。
让这是一个单独的类型,不实现Copy
或Clone
允许你有一个"拥有的唯一id"的概念;然后每个具有这些ID之一的类型都被强制不为Copy
,并且Clone
impl将需要获得一个新的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的值。这是一个非常明显的模式,并且没有严重的缺点。