Skip to content

Instantly share code, notes, and snippets.

@koivunej
Last active August 10, 2023 06:50
Show Gist options
  • Save koivunej/5d74e9931bfb3f4eaec73740d326355f to your computer and use it in GitHub Desktop.
Save koivunej/5d74e9931bfb3f4eaec73740d326355f to your computer and use it in GitHub Desktop.
AsRef<V> trick with Arc<T>

AsRef<V> trick with Arc<T>

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

  1. Tempting "obvious" answer would be to rely on the std libraries ArcInner layout, calculate an offset to the wanted field and express ArcMapped<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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment