Moving a DST to a new (possibly DST) container, in general

Idle curiosity:

Today I saw someone hoping to be able to convert Box<dyn Trait> to Arc<RwLock<dyn Trait>>. This is not as far as I know possible, though Box<dyn Trait> to Arc<dyn Trait> is, but it made me wonder: has anyone tried to design a feature making it possible to perform arbitrary wrapping and unwrapping of dynamically-sized types, and moving them to new containers (in the cases where the specific types involved allow it), without needing a specific conversion for the pair of containers involved?

unsized_locals can do this (assuming that if it stabilized, Arc::new and other constructors would also start accepting ?Sized input), but it might not always be acceptable to move the values to the stack and back to the heap. Do any of the “placement new” ideas cover moving an existing DST?

7 Likes

I'm way out of my scope here, but could it possibly be covered by the safe transmute effort, running it on container (and not the inner DST)?

It's definitely not a transmute. Box<T> to Arc<T> is a pretty specific (to Arc) conversion, since it has to copy the Box-allocated value into a new allocation slot that also contains the counters (and then deallocate the box without dropping the value). And RwLock<T> stores the T inline (in an UnsafeCell), not always on the heap.

The naive path here would be Box<dyn Trait> to Box<RwLock<dyn Trait>> (ala something like the Box<T> to Arc<T> case) and then to Arc<RwLock<dyn Trait>>. But that involves an extra copy and allocation that's immediately discarded.

3 Likes

A general form of this conversion would take Ptr<T: ?Sized>, where Ptr is some pointer-like type, such that it can own a value of type T.

Box<T> is a rather general such type, assuming that you're fine with having T heap-allocated. A general conversion would require turning a value of Container<T> into a Box<T> (possibly with a new heap allocation), and then converting Box<T> to the desired target type.

*mut T is another possible form of Ptr<T>, however it would require all conversions to be unsafe. It also runs the risk of either easy memory leaks (if Container<T> is transformed into *mut T, possibly with a new heap allocation) or unsoundness (if Container<T> allows to access its contained data as *mut T without dropping the container instance). If we try to avoid extra heap allocations and possible leaks, we would be forced to convert Container<T> into *mut T and a Container<MaybeUninit<T>>, where the latter type exists just to own the backing storage, but logically no longer having an instance of T. Of course, using such conversions would be very unsafe, so I don't imagine such APIs to land in std.

If we had owning references &move T, then I believe we could expose the desired API generally and soundly. It should be possible to create a &move T from a &move Container<T>, and to track whether T was moved from &move T, even if we hold an instance of &move Container<T> (this is basically the way Box<T> works currently, one can move the contents without invalidating the box). The conversion would look basically the same as with Box<T> above, except that we could safely use the existing backing storage without any new heap allocations. The moved-from container would know that its contents were moved, and wouldn't allow to access them again until some new data is moved in. Either that, or we can just drop the old container.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.