Context: I've got a data structure (call it Thing) which is always passed around in an Rc, and Thing currently has an id field, because two Things with the same data are not necessarily the same thing (and may become different in the future because of interior mutability). So my implementation of PartialEq for Thing compares their ids; Hash also just uses the id. Generating and storing ids seems unnecessary though, seeing as the Rc provides an id for us in the form of its backing pointer. I was however a bit hesitant to use Rc::as_ptr/ptr_eq for these purposes though, because I wasn't certain that the pointer would never change. Posts in Does comment on Rc<T>.as_ptr() imply Rc<T> is always pinned? suggest that it'd be totally fine so long as I don't use make_mut; I don't use use make_mut, but I'd like to make certain that I don't shoot myself in the foot later on.
The actual question: If I want to guarantee that the pointer I obtain from an Rc will always point to the same data for the lifetime of that Rc, should I be using Pin<Rc<Thing>> or Rc<Pin<Thing>>?
To me, the former implies that the address of the Rc won't change, which isn't helpful because I'll be cloning Rcs anyway, whilst the latter implies that the location of the Thing, which will be the backing pointer of the Rc, won't change. Moreover, it seems to me that doing anything useful with a Pin<Rc<T>> is quite difficult because I don't think you can just access a &Rc<T> from a Pin<Rc<T>>, which you need for doing anything useful with an Rc. However, I do suspect that I don't understand Pin very well, having only read its documentation for the first time today.
Thanks in advance for any clarity you can shed on the matter.
Pin is always used as some kind of Pin<Ptr<T>>, where Ptr<T> is some kind of pointer to T that ensures that T stays at consistent location, as long as we don't have any &mut Ptr<T>'s. The whole logic is in the fact that getting &mut Ptr<T> from Pin<Ptr<T>> is unsafe (unless T is Unpin - in this case, Pin essentially does nothing at all). So the thing you want to use is probably Pin<Rc<T>>.
Going off the description and thinking ahead just a little bit, using an in-memory location for the id might not be the ideal way to go. For one: each and every Thing you create will only ever be able to reference itself - by its own reference. Any caching approach you might ever come up with, will have to be limited to the current allocation of Thing's on the heap. The moment your program stops running, all of the id's will need to be regenerated from the top at next launch.
Assuming you're fine with all the above:
Both will work: the Rc<Pin<Box<....>>> is a bit clearer (especially if you're new to Pin) while the Pin<Rc<...>> will most likely have less of an impact on your code later on, performance-wise. As @Cerber-Ursi has pointed out: a Pin itself doesn't "pin" or do anything to the data. It's just a [compile-time] "marker" that imposes additional constraints on the use of a given pointer.
At its core, pinning a value means making the guarantee that the value's data will not be moved nor have its storage invalidated until it gets dropped. [Pin::new_unchecked]
Therefore
let num = 32;
// not a pointer, will fail to compile
let wrong_pin = Pin::new(num);
// will compile just fine, the `num` remains as-is
let right_pin = Pin::new(&num);
// both are fine
let rc = Rc::pin(num);
let boxed = Box::pin(num);
// additional constraint, prohibiting *any* move
use std::marker::PhantomPinned;
struct Pinned<T> {
data: T,
_pin: PhantomPinned
}
let pinned = Pinned { data: num, _pin: PhantomPinned };
let explicit = Rc::pin(pinned);
Rc<T> is (for all intents and purposes here) just a pointer to a heap allocation of T; with counters for the number of "strong" and "weak" references to that allocation given away on each clone(). An &Rc<T> is a pointer to that pointer. A Pin<Rc<T>> is an Rc<T> with some guarantees on top. Both auto-Deref into &T themselves. To clone() the Pin<Rc<T>> is to clone() the Rc<T> associated with it automatically. What other "useful" thing would you require "doing" there?
If you were thinking about getting your id from the &Rc<T> itself: that is most definitely one of the worst ways to go. Again: think of the Rc<T> as just another & reference. An &Rc<T> is a temporary stack-based ephemeral &&T. Whatever Rc<T>'s you are going to be using are going to be placed on the stack, *-ed into T and promptly discarded. And id you get from them will get invalidated pretty much as soon as you declare it. If anything, it's the &T you want here.
You don't need to use Pin at all. A given Rc always points to the same address.
If you did need Pin, then the thing you would want is Pin<Rc<T>> because that means that the T is pinned (not the Rc). There's no such thing as Rc<Pin<T>> unless T itself is a pointer.
But you dont need pinning at all; it's already guaranteed that the allocation will stay in one place.
Yes, but that’s not the same thing as saying that the value it points to won’t move. If
you want to guarantee that you get the same results from Rc::ptr_eq() as you would get from storing ID numbers in a private field of Thing, and
the Rcs are being exposed to code that is not trusted not to do a get_mut() or make_mut() on them,
then you do not have guarantee (1) from Rc<Thing> but can gain it by using Pin<Rc<Thing>> and Thing: !Unpin. In many situations, you won’t actually benefit from (1), so there is rarely a need for using Pin in this way, but rarely is not never.
You can clone an Rc<Thing> out of the Pin<Rc<Thing>>, then drop the Pin<Rc<Thing>> and finally call Rc::get_mut on the Rc<Thing> to get a mutable reference to Thing. As such pinning a value behind Rc is simply impossible. Rust-for-linux has it's own Arc clone which does not have a get_mut method to allow pinning: Arc in kernel::sync - Rust
Correct me if I'm wrong, but it seems to me (after a few more experiments) that in order to be able to "clone an Rc<Thing> out of the Pin<Rc<Thing>>" the Thing itself must at least Unpin:
Summary
let rc = Pin::into_inner({ Rc::pin(42) });
// ... clone + drop + get_mut = impossible ✔
use std::marker::PhantomPinned as Pinned;
let rc = Pin::into_inner({ Rc::pin(Pinned) });
// ^ `PhantomPinned` cannot be unpinned
Unless I'm missing some fairly obvious method that isn't Pin::into_inner (requires Unpin), Pin::as_ref (Deref's into Pin<&T> instead) or Rc::clone (needs an explicit &Rc<T> only (?) available through unsafe transmute and/or raw pointer casts). The Pin + !Unpin interface itself appears to be surprisingly robust, in fact. Again though - do point out what I'm missing here.
I believe the issue is only present if you want to pin an existing Rc, but if all you have is a Rc already pinned then there should be no way to obtain a Rc out of it.
Or maybe we are all missing something and Rc::pin is actually unsound.
It looks like this is not the case. Given an Pin<Rc<Thing>> if you call clone on it, you get another Pin<Rc<Thing>> and not a Rc<Thing>. You also cannot clone it by going through deref since deref goes straight from &Pin<Rc<T>> to &T without letting you obtain an intermediate &Rc<T>.
I thought you could always go from Pin<Ptr> to &Ptr, but it seems you can only go to &<Ptr as Deref>::Target. That indeed makes it impossible to clone an Rc<Thing> out of it.
Thanks for all the responses - I think my understanding of pinning has improved quite a bit. My understanding of the situation is:
The answer to the question is Pin<Rc<T>>, because this means that the address of T won't change. Also, Rc<Pin<T>> just won't be a thing if T is not a pointer (which in my case, it is not).
However, you can't get an Rc<T> or an &Rc<T> from a Pin<Rc<T>> unless T: Unpin, in which case Pin doesn't really do anything in the first place
...so I'm probably better off going for another solution. @bjorn3's link to rust-for-linux's Arc is interesting, and I wonder if creating my own newtype wrapper for Rc, without make_mut et al., would suffice for me - I suspect it would. It is interesting that the object pointed to by their Arc data is always pinned (according to the docs), yet there is no explicit Pin in sight - I suppose this is because if the location of the data ever changed, the pointers used by the Arcs would be invalidated, which would be a Bad Idea probably.
I'm also glad to see that Cunningham's Law came into effect well w.r.t. whether this is a good idea in the first place
What prevents me from giving one Thing another Thing (or Rc<Thing> or whatever) to be referenced?
For now, all Things are shorter-lived than the program itself and are constructed anew every run; however the possibility that things might need to be cached beyond the program life in the future is an interesting one, albeit one that I think I will worry about if it happens.
So am I correct in thinking that if I have Rc<T>s on the stack (which I'm fairly sure I do), or just that are longer lived than any code which needs the ID, then it is ok? If not I would be interested to know if you have any alternative suggestions on how to key these values.
I suspect that ultimately the best solution would simply be for me to not use Rc. But this has been a very interesting and informative discussion regardless of whether I continue on this path or not.