Quick "blog post" or note mainly for future myself.
I have often wanted to have "Arc projections," which would be like Arc<V>
but holding up the same reference count as the original
Arc<T>
, like:
struct Shared {
answer: u32
}
let original = Arc::new(Shared { answer: 42u32 });
let answer: ArcMapped<u32> = original.clone().map(|x| &x.answer);
assert_eq!(Arc::strong_count(original), 2);
The nice thing about this imaginary ArcMapped<V>
(name open for bike-shedding) is that you can pass it around, and the receiver does not need to know what is the outermost type for Arc<T>
.
In the above example, it is completely hidden.
Recently I (re-)discovered I can get quite close to the dream without any unsafe and offset calculations 1 or function pointers by expecting impl AsRef<V>
instead of &V
or AsRef<V> + Clone
instead of an Arc<V>
.
There are more boring details left not described that you might need to express in bounds, but a T: AsRef<V>
will get you far.
See the AsRef<V>
in action, adding support for different wrappers (or just the DeltaLayerInner
): https://github.com/neondatabase/neon/pull/4937/commits/9543dbd1c28040ed445010dcb91ad2fd77c61e17
It seems the Rust book no longer covers AsRef<T>
, but it used to, possibly.
Next day update: I just realized the unsafe and offset calculations 1 would have an unintended side-effect if the example's Shared::answer
was a std::sync::Mutex<u32>
instead and an offset was calculated from the borrow returned by the closure doing the "mapping", then we would end up in std::sync::Mutex::<T>::data: UnsafeCell<T>
.
Having an ArcMapped
provide unsynchronized access (undefined behavior) would not be a good result, so this might explain why we do not have this in std
.
Footnotes
-
Tempting "obvious" answer would be to rely on the
std
librariesArcInner
layout, calculate an offset to the wanted field and expressArcMapped<V>
as a tuple of a strong reference and an offset. But we don't want to do that for several reasons, the most important I realized in the "Next day update." ↩ ↩2