-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support for intrusive data structures and unmoveable types #417
Comments
Its already kinda possible to create unmovable types by borrowing self, see this as an example: https://gist.github.com/Kimundi/d846f1da490ef4e9b0d9 Or here a bit more integrated solution: https://gist.github.com/Kimundi/81b9944a896052b5407f |
I think what is desired here is not just the ability to mark types as not moveable, but to actually control the process of moving. I was thinking that this could be accomplished with @Kimundi's scheme if we require |
Sure you can have non-movable types by not giving the user I don't think a compiler-supported but user-defined |
@arielb1 You could just have types which do non-trivial moves such as a Vec resize do a I imagine we would introduce a new rule: types are either Copy or Move, and there is a blanket impl of Move for all non-Copy types which is just memcpy. |
@arielb1, @reem What I meant by @Kimundi That's interesting and clever... I wonder if it's adequate for intrusive structures like |
Non-trivial move is a nightmare to work with, because it inserts user-defined operations to rather arbitrary places. A |
It strikes me that for an intrusive doubly-linked list type like This is eerily similar to the restrictions on global These restrictions also happen to correspond to the restrictions on things which are |
Some more thoughts: First, the fact that we seem to want essentially the same restrictions as which an This also reveals a deeper conundrum. Shared borrow restrictions prevent moving, even via But it seems like being able to explicitly move is a capability you would want sometimes? I don't have a great deal of experience with intrusive data structures. Can someone who does perhaps shed some light on how this sort of thing is usually reasoned about in other languages? |
Like this? https://gist.github.com/pythonesque/d002384c29f83f9efd09 Pretty sure NoMoveGuard is unmovable in safe code. _Edit:_ https://gist.github.com/pythonesque/d002384c29f83f9efd09#file-intlist-rs proof of concept. |
@pythonesque Just a note, but line 15 is undefined behavior due to undefined struct layout. You should construct it explicitly then transmute the lifetime only. |
@reem: The NoMoveGuard is zero sized and markers have no alignment / size requirements. Is the behavior not defined in this context? (Also, the lifetime doesn't actually change). |
I'm almost 100% sure that struct layout is always undefined, i.e. even going from T -> NewType(T) through transmute is UB. |
Okay, updated to remove UB: https://gist.github.com/pythonesque/d002384c29f83f9efd09. _Edit_ I'm not quite sure what about this requires completely immovable types: https://gist.github.com/pythonesque/d002384c29f83f9efd09#file-intlist2-rs is basically identical and doesn't use any unsafe code. So maybe I'm approaching this incorrectly. |
For what it's worth, I've done a lot of work with movable intrusive types. Generally in these cases, I use the intrusive structure to manage ownership of its container. That is, moving the intrusive structure moves the container at the same time. For example, if I use a linked-list to pass ownership of a structure from one thread to another, then I can use an intrusive link on the list to represent ownership of the containing structure. I'm not sure if I expressed that idea clearly, but it's a hope of mine that Rust will support code like that safely. |
I went with the The use case which actually made me start thinking about intrusive data structures and unmoveable types was "a weak reference for any type" (not just @aidancully Unfortunately I don't understand what you're trying to say, even though I'd like to. What do you mean by "the intrusive structure" and "its container"? What does it mean to use a linked list to "pass ownership"? |
@glaebhoerl I'm going to try to express the idea in C, because C has well-understood semantics in this domain... Consider the following C code:
In traditional usage, the
Then it becomes possible to create linked-lists of What I mean by "passing ownership" is, consider the case that one thread reads from
In Rust terms, ownership of the Obviously I've left out a lot of details for ensuring safety when using this sort of pattern, but I think this should express the idea more clearly than the single paragraph you referenced earlier. I also think this might be a more traditional use of intrusive structures than what you're describing. AFAICT, the Rust ownership system doesn't handle this sort of usage (representing ownership of a container via ownership of a contained field) at all, right now... I'm working on an RFC around the idea, would be happy to talk about it elsewhere. (To give a preview: build intrusive structure support off a "singleton type" construct, allowing a safe "containerof"-type operation. Still working on many details.) Thanks |
@glaebhoerl Would boxing the |
@aidancully Possibly, but that's a workaround with a performance cost, and (AFAICT) will also stop working if/when we get something like (Still digesting your previous comment.) |
I think the simplest meaningful example of a type where you really want something like non-moveability and You'd want an API something like this:
With the
(For Is there any other way to support exposing a safe interface for a type like this? |
I agree with @arielb1 that a "bound" analogous to E.g. something like a lang-item marker But I have not yet attempted to encode the examples given the comment thread via such an approach; the idea hasn't gone beyond the level of "thought experiment" for me yet. |
Related to this issue: rust-tenacious has been retired so there's now no available lint for flagging that you've misused an unmovable type. |
@aidanhs It was pretty bad at its job though, there's no way to know what happens without looking at the MIR, preferably after MIR optimizations (although they shouldn't introduce more moves). However, doing it on the MIR may be as simple as writing a MIR visitor that checks the type of every consumed lvalue and errors if it's unmovable. Since rust-tenacious was discontinued, access to MIR has been streamlined and nowadays it should be possible to write such a lint. |
We now have pinning. Does that mean we can close this issue? It seems at least it should be updated to say what is still missing. |
I think we're still missing |
I don't think we need I think what is confusing me is that the issue conflates two very different concerns: Intrusive data structures (possible with
There is no such thing. Their invariant explicitly says "though shall not move me around". |
@RalfJung The reason it conflates them is that back in 2014 the issue was not well understood (at least by me).
Possibly what @Ekleog meant to express is "transfer ownership without physically moving (and without incurring heap allocation)"? (Side note |
Agreed. That's why I was suggesting in the light of what we know now, the issue title + description needs updating, or the issue should be closed (and potentially a new one opened for what is still deemed missing). |
(For the record the reason I didn't express any opinion or preference on that is because I don't have one.) |
> safely moving !Unpin things around while respecting their invariants?
There is no such thing. Their invariant explicitly says "though shall not move me around".
Well, if the !Unpin thing does implement a ReallyMove trait that has a
function like (pseudo-syntax and just for the idea, I haven't thought
about the exact type esp. of `out`):
```
fn really_move(self: Pin<&mut Self>, out: Pin<&needsinit Self>)
```
Then the thing could be safely moved.
I can imagine this be useful for eg. stack-linked-lists, where you would
want to move a node, but then you need to re-attach the now-dangling
links. The `really_move` function would allow to do this
moving-and-reattaching behaviour.
|
Moving and reattaching might be better done via a method that unlinks the node and un- I'm not sure, though, how widely applicable that pattern would be among "pinned objects that want to move," or even if it's allowed by |
Rust currently has very poor support for intrusive data structures: it is extremely difficult or impossible to define a safe interface for them. This is because all types in Rust are moveable, and moving invalidates any pointers into the given value. Rust's current assumption is that values only have pointers pointing out from them, and any pointers into them are known statically and have their validity enforced at compile time by borrow checker restrictions. But this fails to accomodate the case where the validity of pointers into values is maintained by runtime code, as is the case for intrusive data structures.
We would like to be able to have a type such as:
and allow client code to deal in instances of
IntListNode
directly and store them wherever, such as on the stack, with e.g. theDrop
impl written to patch upprev.next
andnext.prev
. The implementation would almost certainly requireunsafe
code, but we would like to at least expose a safe interface. This requires thatIntListNode
not be implicitly movable.We could also have a trait such as:
for explicit moves of normally nonmoveable types, using
&move
and&out
references to avoid the paradox of passing nonmoveable types by value. But we need nonmoveable types first. (relocate()
should be equivalent to aclone()
followed by adrop()
on the original, except likely more efficient, andclone()
itself may not be available.)An "interesting" question arising from nonmoveable types is how to acquaint them with the generics system. (Do we have a
Move
bound, a laCopy
? Do we require it to be explicitly specified? Or explicitly waived?)The text was updated successfully, but these errors were encountered: