How do you globally track locations of all instances of a T without move constructors?

In C++ I can write a class that in its constructor registers inserts its this pointer into a global container, on destruction removes the pointer from the global container, and on move changes the value stored inside the global container. Sometimes this is useful for debugging and sometimes it is useful for performance hacks.

I know Rust doesn't have move constructors or move assignment operators and generally assumes that moves can always be achieved by shallow byte copying. I also understand that in general unsafe Rust code can rely on this property, which is a barrier to having move constructors and move assignment operators as a general language feature. I also know that in rust drop is not guaranteed to run because of things like reference cycles.

My question is is there some way to achieve why I'm able to achieve in C++, so long as I am okay with the fact that I am going to have to manually audit any unsafe code dealing with this particular T and manually convince myself that where it is used drop will run. I understand these are giant caveats and I am okay with them for my project (I'm hoping this prevents this thread from derailing into people lecturing that I shouldn't want what I want).

My current plan is to never give users direct access to T and only give them references. Users will have to ask a central registry to create Ts under the covers on their behalf, and then be able to move them around only by calling an explicit move method on the registry and providing it handles. However, this is extremely limiting, and prevents me sometimes from being able to get the memory layout that I want which is important for performance (I can't just put a T in a struct next to other fields I know will already be hot when I want to access the T, the best I can do is put a handle to the T instance there).

Ultimately, you can't really do this without indirection. (With indirection it's easy — moving a Box<T> doesn't move the T)

2 Likes

The other option, if you're really willing to deal with the bad ergonomics, is to have the registry hold pinned references.

1 Like

If you wanted to have the literal equivalent of an "instance tracking" system without having access to move constructors or a GC'd language where all objects are already reference types, you would typically create some Box-like smart pointer which allocates the value on the heap and handles the registration stuff.

Rust and C++ both implement move semantics in different ways and I think the way C++ does things would go counter to some of Rust's core tenets. For example, when I move a unique_ptr<T> it will leave the old variable in an uninitialized state, whereas Rust says that any instance of a value must be always completely valid.

On one hand, Rust's way of doing things gives us a lot of nice guarantees (moving is just a memcpy(), it's not possible to use something after it was moved out of, etc.), but it does also mean some of your previous patterns just aren't expressible. Another example is a self-referential type where an object may directly contain a pointer to one of its fields and you update this pointer every time the object is moved.