Hi, sorry it's taken me a bit of time to get back here. I was traveling for a week.
I think your “if it has any !Copy fields” clause is supposed to be the base case; if a type has only Copy fields (but isn't itself Copy), instead of copying each field, we mark the entire place as used.
Yeah, I think this is what I was at least trying to say. A reborrowable type that is internally fully Copy but is not itself !Copy
should mark its "reborrow source" as used for the duration of the reborrow result's lifetime (existence, not the captured lifetime).
So when reborrowing Vec
we find a field that is not !Copy
; ie. the Vec
itself will not get marked as used. We recurse into the ptr: Mut
field and that type is made out of fully Copy
fields so we mark the vec.ptr
as used.
I'm not sure if this makes the operation not-compositional, though. Any Mut
field in any type will always be marked as used on reborrow since its internals will always all be Copy
(generics could change that though). The only "non-compositional" part is that any type's reborrowing logic is dependent on its field types in a sort of "boolean or" sort of way, maybe?
I'd argue that something like a #[reborrow]
trait would make things less compositional, as then you could have two types Vec
and Vec2
where in one case Mut
is recursed into and in another it isn't.
Regarding lifetime capture, I am very interested in figuring out if we could make that work as the basis of the reborrow. I'm just wondering about my use-case example of GcScope<'a, 'b>
where 'a
is an exclusive lifetime and 'b
is a shared lifetime. Since GcScope
wouldn't be Copy
, we'd have to recurse into the type to separate the two lifetimes from one another. But then inside of it, the two lifetimes are currently both captured by PhantomData: Copy
fields: how do we now figure out that the field carrying 'a
should be marked as used and 'b
should be left unused.
Note: I think your
If any Copy fields capture a lifetime, mark all fields that capture that lifetime as used, and recurse on any fields that don't capture such a lifetime.
is the wrong way around: reborrowing &'a T
is just a copy and should not mark the original reference as used (disabled for reads and writes). Capturing a lifetime on a field that is !Copy
is the interesting case, ie. &'a mut T
.
I'm not sure that thinking of a 'a <: 'b
lifetime "subset-equal" relation is useful here. AFAIU, rustc does not perform any lifetime subsetting during reborrowing, so trying to introduce such an operation is just reference-level fiction that leads one astray. The 'a
in &'a mut T
is effectively the lifetime of the T
anyways, the validity of the data or as you and the compiler puts it, a borrow on T
. Reborrowing &mut T
never changes that validity.
I prefer thinking of the reference type's "owned lifetime" (capitalised here for effect), so eg. let original: 'A &'a mut T
; this is the same exclusive reference to T
as before where T
is valid for 'a
, but the exclusive reference is valid for 'A
. Reborrowing this creates a new exclusive reference let derived: 'B &'a mut T
where 'A <: 'B
, and the original
gets disabled for the lifetime 'B
.
Conceptually I think of this as the same as let original: Vec<T>;
and let derived = original;
, ie. a move, just with a "return move" happening automatically when derived
is dropped: the value we're acting upon here is a move-only value, and it happens to hold within it an exclusive reference to data that is valid for some duration of time. Moving the value doesn't change validity of the data.
But, of course this is all the same stuff as you're saying as well, just in different words and perhaps a slightly different viewpoint.
This may indeed be the best choice; this is then the PhantomExclusive
and PhantomShared
that I suggested earlier. The hidden type-level distinction on lifetimes is definitely a pain, but luckily it's a pain that already somewhat exists in the compiler (with exclusive references and Pin reborrowing), so maybe it's not that bad?
Regarding privacy, my immediate thought is that reborrowing does not relate to field publicity at all. Implementing Reborrow for a type can only be done in the crate that defines said type, so all of its fields can (conceptually) be accessed by the reborrow action. The compiler of course also sees all the fields and cares not one whit about the publicity of the fields. In my GcScope<'a, 'b>
case none of the fields are public to users, but reborrowing the type should still work equally well. Even if the fields were public and users could eg. destructure the GcScope<'a, 'b>
into Gc<'a>
and Scope<'b>
, the reborrowing should still work the same.
As for destructuring a type that has some private reborrowed fields and some public reborrowed or copy fields, the public reborrowed fields of course stay alive past the destructuring while the private fields effectively drop at the destructuring site. Or so I'd expect things to work.