You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Mutable references vs self-referential structs #148
The reason it fails is that each time through the loop, we call Pin::as_mut, which creates a fresh mutable reference to the generator. Since mutable references are unique, that invalidates the pointer that points from one field of the generator to another.
This is basically a particularly bad instance of #133.
When a Pin<&mut T> gets passed around, it gets retagged the same way as an &mut T, assuring uniqueness of this pointer to the entire memory range. This is probably not something we want. So maybe we need a "no retagging for private fields" kind of thing, similar to what we might need for protectors. This is the (relatively) easy part.
However, even just calling Pin::as_mut creates a mutable reference internally (before wrapping it in Pin again). So even if we fix the above, we'd still assert uniqueness here. In some sense it would be more correct if a Pin<&mut T> would actually be represented as a NonNull<T>, as that's a more honest reflection of the aliasing situation: it's not a unique pointer, there can be pointers inside this data structure that alias. This is the hard part.
If we still had PinMut as a separate type, this would be fixable, but with Pin<impl Deref>, that's hard. And even then, map_unchecked_mut still creates a mutable reference and even passes it as a function argument, which is about as strongly asserting uniqueness as we can -- and which we don't want, aliasing pointers are allowed to exist.
If we ignore the fact that references are wrapped inside the Pin struct, self-referential generators do the equivalent of:
fnmain(){letmut local = 42;let raw_ptr = &mut local as*muti32;// create raw pointerlet safe_ref = &mut local;// create a reference, which is unique, and hence invalidates the raw pointerprintln!("{}",unsafe{*raw_ptr });// UB}
I explicitly wanted that code to be UB. While raw pointers aliasing with each other is allowed, having a single raw pointer and a mutable reference acts pretty much like having two mutable references -- after all, between the two of them, it's enough if one side does not allow aliasing, since "aliases-with" is a symmetric relation. If we replace raw_ptr by another reference above, the code is rejected by both the borrow checker and Stacked Borrows.
So to fix this problem, I see two general options:
"Hide" references in struct fields from Stacked Borrows. Then the implementation of Pin methods would have to do lots of transmute to avoid ever creating a "bare" reference. And map_unchecked_mut has a problem. It should probably use raw pointers (and RFC for an operator to take a raw reference rfcs#2582 to get a field address). On the other hand, at least we have a plan.
Allow the example code above. We would have to somehow "lazily" activate mutable references. It's a bit like two-phase borrows but worse. I don't have a very concrete idea what this would look like, but I think Stacked Borrows would have to become Tree-shaped Borrows or so -- a stack just does not have enough structure.
@RalfJung: Could this be resolved by turning Pin<&'a mut T> into some abstraction around (&'a UnsafeCell<T>; PhantomData<&'a mut T>) that does not actually hold a reference, but only behaves like one and spawns actual references on demand?
This would likely require turning Pin into Compiler Magic(tm) that is hard to implement for user-defined pointer types, though, and I can imagine backwards-incompatible implications in other areas such as changing the safety contract for extracting mutable references from Pin...
EDIT: Ah, I see that you explored a NonNull-based variant of this strategy above.
The way I understand how Pin works, the self referential references borrow their validity from the one in the pin. So under SB, they're retagged from the pin.
The most minimal change that would make pin play nicely with SB I think would be to somehow keep the interior references valid even while the unsafe map_unchecked_mut reference is used.
Could it be possible to not retag for pin's map_unchecked_mut and only pop tags when the reference is used to access that memory location? (This is super spitball, sorry)
The way I understand how Pin works, the self referential references borrow their validity from the one in the pin. So under SB, they're retagged from the pin.
Correct.
However, then the pin itself eventually gets retagged, and that kills the self-referential borrows. This currently happens all the time, but could be reduced to just "inside the Pin implementation" if we make retag respect privacy, or just generally not enter structs, or so.
Could it be possible to not retag for pin's map_unchecked_mut
Well it'd need a magic marker or so.
and only pop tags when the reference is used to access that memory location? (This is super spitball, sorry)
#194 indicates that this is a problem with self-referential structs in general, not just self-referential generators. That's not surprising, so I generalized the issue title.
fn main() {
let mut local = 42;
let raw_ptr = &mut local as *mut i32; // create raw pointer
let safe_ref = &mut local; // create a reference, which is unique, and hence invalidates the raw pointer
println!("{}", unsafe { *raw_ptr }); // UB
}
While working on pin-project, I wanted to write this code:
The key thing here is the unproject method. Basically, I want to be able to the following:
Convert an &mut Self to a *mut Self
Create mutable references to fields of Self (&mut self.field, etc.)
Later, upgrade the *mut Self to a &mut Self, after all of the mutable fields references have gone out of scope.
However, Miri flags this as UB for (I believe) a similar reason as your example - creating a mutable reference to a field ends up transitively asserts that we have unique ownership of the base type. Therefore, any pre-existing raw pointers to the base type will be invalidated.
Allowing this kind of pattern would make pin-project much more useful. It would allow the following pattern:
impl MyType {
fn process(self: Pin<&mut Self>) {
let this = self.project();
// Use a pin-projected field - e.g. poll a wrapped future
...
// Construct a new instance of MyType - e.g. a new enum variant
let new_foo: MyType = ...;
// Overwrite ourself using Pin::set
let new_self: Pin<&mut Self> = this.unproject();
new_self.set(new_foo);
}
With arbitrary_self_types, pin-project could take self: &mut Pin<&mut Self> to get a lifetime to tie the projection to, and let NLL take care of the projection lifetime then allow using self again. example.
It is the same kind of reasoning as to why this could be sound: in the self-referential case, it's the whole pointer existing that invalidates the part pointers, though their usage regions are disjoint. In the unproject case, it's the part pointers invalidating the whole pointer, even though they are statically required to be used in disjoint regions.
However, we haven't been doing the same for all structs containing a Pointers. Tokio's MSRV is too old for the addr_of! macro, so it's pretty difficult to do it everywhere. It's also difficult if types are not Copy.
Hm, I don't quite understand what that Pointers struct is about, but indeed without addr_of! some code cannot be written properly. Would it be an option to have a build.rs set a cfg flag such that on recent enough Rust, addr_of! is used, but older Rust fall back to &expr as *const _? (Somewhat like the maybe-uninit crate.)
But anyway that seems unrelated to self-referential generators, so we should probably move the discussion elsewhere (e.g. Zulip).
I believe I'm running into a variation of this here: mitsuhiko/deser#37 — While I don't have a generator, I have the situation where I am trying to stash away a value temporarily on an iterator like type for one iteration and the returning the value invalidates all pointers.
While what I'm doing is probably not a very common issue, it seems like you can run into this in the absence of generators, futures and pin as well.
This issue here is specific about self-referential data structures though, not sure how those would come up in an iterator? So on first sight that does not really sound like the same problem.
@RalfJung it's not a traditional iterator, it's a struct that is iterated like this:
whileletSome(value) = thing.next()? { ...}
And it works by stashing data onto self for next() to return a borrow to. Since I create self referential data within the "iterator" to make data survive until the next iteration in the form of some state machine, I am getting some pointers invalidated presumably because fn next has &mut self. That's at least my interpretation of what is happening.
As an observer, a thought occurred regarding the UnsafeWindow proposal. Would it be possible to prevent this type from ever being referenced? As in, it should be treated effectively like an UnsafeCell, but creating direct references to an UnsafeWindow should be disallowed.
Based on a minute's worth of thought it seems like if UnsafeWindow were allowed to be turned into a reference, we run into the same set of problems every other proposal has, where we end up having to make exemptions for certain referencing patterns wholesale where we could instead restrict it to certain regions through api enforcement.
It feels like an approach where UnsafeWindow would instead somehow provide non aliasing mutable references to the inner value may allow for easier restrictions on what is allowed in general while still allowing the self referential behaviour.
Thinking in terms of mutability, Id say UnsafeWindow would behave like UnsafeCell, as in an immutable UnsafeWindow would still allow for multiple mutable references to the inner value to exist.
In summary, UnsafeWindow would be ownership only, and dereferencing an unsafe window should result in, say, a new reference that is implicitly assumed to be non aliasing. Usage in ways that allow the reference to exist outside of the variable's scope would be UB, but I can' figure out if there is a way to statically disallow such a situation, aince if we attach ifetimes to the created references it would imply they are tied to the UnsafeWindow and we may end up looping around to the aliasing mutable reference problem again.
Welp, that RFC sort of is exactly what I envisioned.
Activity
RalfJung commentedon Jun 23, 2019
In some sense there are two problems here:
Pin<&mut T>gets passed around, it gets retagged the same way as an&mut T, assuring uniqueness of this pointer to the entire memory range. This is probably not something we want. So maybe we need a "no retagging for private fields" kind of thing, similar to what we might need for protectors. This is the (relatively) easy part.Pin::as_mutcreates a mutable reference internally (before wrapping it inPinagain). So even if we fix the above, we'd still assert uniqueness here. In some sense it would be more correct if aPin<&mut T>would actually be represented as aNonNull<T>, as that's a more honest reflection of the aliasing situation: it's not a unique pointer, there can be pointers inside this data structure that alias. This is the hard part.If we still had
PinMutas a separate type, this would be fixable, but withPin<impl Deref>, that's hard. And even then,map_unchecked_mutstill creates a mutable reference and even passes it as a function argument, which is about as strongly asserting uniqueness as we can -- and which we don't want, aliasing pointers are allowed to exist.RalfJung commentedon Jun 25, 2019
If we ignore the fact that references are wrapped inside the
Pinstruct, self-referential generators do the equivalent of:I explicitly wanted that code to be UB. While raw pointers aliasing with each other is allowed, having a single raw pointer and a mutable reference acts pretty much like having two mutable references -- after all, between the two of them, it's enough if one side does not allow aliasing, since "aliases-with" is a symmetric relation. If we replace
raw_ptrby another reference above, the code is rejected by both the borrow checker and Stacked Borrows.So to fix this problem, I see two general options:
Pinmethods would have to do lots oftransmuteto avoid ever creating a "bare" reference. Andmap_unchecked_muthas a problem. It should probably use raw pointers (and RFC for an operator to take a raw reference rfcs#2582 to get a field address). On the other hand, at least we have a plan.HadrienG2 commentedon Jun 29, 2019
@RalfJung: Could this be resolved by turning
Pin<&'a mut T>into some abstraction around(&'a UnsafeCell<T>; PhantomData<&'a mut T>)that does not actually hold a reference, but only behaves like one and spawns actual references on demand?This would likely require turning Pin into Compiler Magic(tm) that is hard to implement for user-defined pointer types, though, and I can imagine backwards-incompatible implications in other areas such as changing the safety contract for extracting mutable references from Pin...
EDIT: Ah, I see that you explored a
NonNull-based variant of this strategy above.RalfJung commentedon Jun 29, 2019
You don't need
UnsafeCell,*mut Twould do it. And yes that's basically what I had in mind with option (1).CAD97 commentedon Jun 29, 2019
The way I understand how Pin works, the self referential references borrow their validity from the one in the pin. So under SB, they're retagged from the pin.
The most minimal change that would make pin play nicely with SB I think would be to somehow keep the interior references valid even while the unsafe map_unchecked_mut reference is used.
Could it be possible to not retag for pin's map_unchecked_mut and only pop tags when the reference is used to access that memory location? (This is super spitball, sorry)
RalfJung commentedon Jun 29, 2019
Correct.
However, then the pin itself eventually gets retagged, and that kills the self-referential borrows. This currently happens all the time, but could be reduced to just "inside the
Pinimplementation" if we make retag respect privacy, or just generally not enter structs, or so.Well it'd need a magic marker or so.
That's basically option (2) from above.
[-]Stacked Borrows vs self-referential generators[/-][+]Stacked Borrows vs self-referential structs[/+]RalfJung commentedon Aug 13, 2019
#194 indicates that this is a problem with self-referential structs in general, not just self-referential generators. That's not surprising, so I generalized the issue title.
Aaron1011 commentedon Aug 22, 2019
@RalfJung: Related to your example of:
While working on pin-project, I wanted to write this code:
The key thing here is the
unprojectmethod. Basically, I want to be able to the following:&mut Selfto a*mut SelfSelf(&mut self.field, etc.)*mut Selfto a&mut Self, after all of the mutable fields references have gone out of scope.However, Miri flags this as UB for (I believe) a similar reason as your example - creating a mutable reference to a field ends up transitively asserts that we have unique ownership of the base type. Therefore, any pre-existing raw pointers to the base type will be invalidated.
Allowing this kind of pattern would make
pin-projectmuch more useful. It would allow the following pattern:This is especially useful when working with enums - here's a real-world example in Hyper.
However, I can't see a way to avoid creating and later 'upgrading' a raw pointer when trying to safely abstract this pattern in
pin-project.CAD97 commentedon Aug 22, 2019
With
arbitrary_self_types,pin-projectcould takeself: &mut Pin<&mut Self>to get a lifetime to tie the projection to, and let NLL take care of the projection lifetime then allow usingselfagain. example.It is the same kind of reasoning as to why this could be sound: in the self-referential case, it's the whole pointer existing that invalidates the part pointers, though their usage regions are disjoint. In the
unprojectcase, it's the part pointers invalidating the whole pointer, even though they are statically required to be used in disjoint regions.Make generated 'project' reference take an '&mut Pin<&mut Self>'
Make generated 'project' reference take an '&mut Pin<&mut Self>'
Merge #47
17 remaining items
RalfJung commentedon Jan 2, 2022
The moment you create a reference to that field, it would require uniqueness.
RalfJung commentedon Jan 2, 2022
As in, make this configurable via a flag? That's possible, but then the question remains what the default should be.
Darksonn commentedon Jan 2, 2022
Whether it's a flag or not is not that important to me.
Yes, that was the impression I got as well. In Tokio I have been doing this because it's unclear to me exactly what is required:
https://github.com/tokio-rs/tokio/blob/12dd06336d2af8c2d735d4d9e3dc0454ad7942a0/tokio/src/util/linked_list.rs#L285-L334
However, we haven't been doing the same for all structs containing a
Pointers. Tokio's MSRV is too old for theaddr_of!macro, so it's pretty difficult to do it everywhere. It's also difficult if types are not Copy.RalfJung commentedon Jan 2, 2022
Hm, I don't quite understand what that
Pointersstruct is about, but indeed withoutaddr_of!some code cannot be written properly. Would it be an option to have abuild.rsset acfgflag such that on recent enough Rust,addr_of!is used, but older Rust fall back to&expr as *const _? (Somewhat like the maybe-uninit crate.)But anyway that seems unrelated to self-referential generators, so we should probably move the discussion elsewhere (e.g. Zulip).
Auto merge of #1952 - RalfJung:self-referential, r=RalfJung
mitsuhiko commentedon Feb 18, 2022
I believe I'm running into a variation of this here: mitsuhiko/deser#37 — While I don't have a generator, I have the situation where I am trying to stash away a value temporarily on an iterator like type for one iteration and the returning the value invalidates all pointers.
While what I'm doing is probably not a very common issue, it seems like you can run into this in the absence of generators, futures and pin as well.
thomcc commentedon Feb 18, 2022
Yes, it's definitely a thing that happens outside of the case that's mentioned at the start.
RalfJung commentedon Feb 18, 2022
This issue here is specific about self-referential data structures though, not sure how those would come up in an iterator? So on first sight that does not really sound like the same problem.
mitsuhiko commentedon Feb 18, 2022
@RalfJung it's not a traditional iterator, it's a struct that is iterated like this:
And it works by stashing data onto
selffornext()to return a borrow to. Since I create self referential data within the "iterator" to make data survive until the next iteration in the form of some state machine, I am getting some pointers invalidated presumably becausefn nexthas&mut self. That's at least my interpretation of what is happening.CAD97 commentedon May 9, 2022
Cross-linking recent zulip discussion about "
UnsafeMutCell": https://rust-lang.zulipchat.com/#narrow/stream/213817-t-lang/topic/aliased.20mutability[-]Stacked Borrows vs self-referential structs[/-][+]Mutable references vs self-referential structs[/+]JakobDegen commentedon Aug 1, 2023
Visiting for backlog bonanza. Retitling to clarify that this is not SB specific
RalfJung commentedon Aug 1, 2023
WarpspeedSCP commentedon Aug 19, 2023
As an observer, a thought occurred regarding the UnsafeWindow proposal. Would it be possible to prevent this type from ever being referenced? As in, it should be treated effectively like an UnsafeCell, but creating direct references to an UnsafeWindow should be disallowed.
Based on a minute's worth of thought it seems like if UnsafeWindow were allowed to be turned into a reference, we run into the same set of problems every other proposal has, where we end up having to make exemptions for certain referencing patterns wholesale where we could instead restrict it to certain regions through api enforcement.
It feels like an approach where UnsafeWindow would instead somehow provide non aliasing mutable references to the inner value may allow for easier restrictions on what is allowed in general while still allowing the self referential behaviour.
Thinking in terms of mutability, Id say UnsafeWindow would behave like UnsafeCell, as in an immutable UnsafeWindow would still allow for multiple mutable references to the inner value to exist.
In summary, UnsafeWindow would be ownership only, and dereferencing an unsafe window should result in, say, a new reference that is implicitly assumed to be non aliasing. Usage in ways that allow the reference to exist outside of the variable's scope would be UB, but I can' figure out if there is a way to statically disallow such a situation, aince if we attach ifetimes to the created references it would imply they are tied to the UnsafeWindow and we may end up looping around to the aliasing mutable reference problem again.
Welp, that RFC sort of is exactly what I envisioned.