Open danakj opened 3 years ago
Hey Dana,
I got really hosed and never got back to this :upside_down_face:.
I just pushed some commits that add New::with()
, which can be used for post-construction work. For example:
new::of(blah).with(|this| /* this: Pin<&mut Self> */)
The new docs for the front matter of the crate demonstrate this pattern on Unmoveable
. I need to page in most of this design work at some point, but this should take care of almost all of the easy cases.
(Oh, and FYI: all of the names in the crate have changed to reflect what I used in the RustConf talk, since I felt those were more succinct.)
Currently to write a constructor/move-constructor/copy-constructor, you have to deal with a lot of unsafe code and boilerplate steps. Each constructor has to do:
The only steps that the type T actually cares about and wants to define are (2) and (5), but today it has to write the whole algorithm in each constructor/move-constructor/copy-constructor. Like so:
I have implemented a set of "SafeCtor" safe traits that a type can implement in order to have minimal unsafe code in their implementations (even none if they do not require moving/offsetting pointers).
These traits expose the step (2) above as
construct() -> (Self, Data)
orcopy_construct(from: &Self) -> (Self, Data)
ormove_construct(from: Pin<&mut Self>) -> (Self, Data)
. These trait functions provide the "constructed-from" object as a reference that can be directly used from safe rust. And they output Data that can be collected from the "constructed-from" object and represented in a useful way for initialization of self-references.And they expose step (5) above as an
initialize(this: &mut Self, d: Data)
that consumes the constructor's side-channel Data to set up any self-referential state in the constructed object.The other steps (1), (3) and (4) are performed in unsafe implementations in the
unsafe CopyCtor
andunsafe MoveCtor
traits, or in actor::construct()
function that replaces the direct use ofctor::from_placement_fn()
from the being-constructed type.@mcy has pointed out initial self-referential object construction could be done by exposing this 2-stage construction paradigm (i.e. steps (2) and (5)) through the constructing function such as
ctor::new_with<T>(init: T, setup: FnOnce(Pin<&mut T>))
, instead of using traits and requiringT : SafeCtor<Output = T>
.It presents a nice advantage: It would force the caller to construct the type T, which is reasonable for initial construction, and it even allows calling different constructors.
But it has a challenge as well, as copy- and move-constructors can not be implemented in the same way:
Here is an example of a self-referential type that points into a buffer. When moving, the move-constructed type must be able to set up a new self-reference at the same offset (or in this case, the next offset).
The only unsafe code here is that which deals with the pointer into
num
. The same self-referential initialization is shared between each of construction, copy-construction, and move-construction. It's not obvious if forcing that behaviour to be shared is undesirable.Here are the traits, which would appear in
ctor.rs
, as they provide generic implementations ofCopyCtor
andMoveCtor
for any type implementingSafeCopyCtor
orSafeMoveCtor
respectively. TheSafeCtor
trait provides the self-referential setup step (2nd stage of construction) used in all construction paths, but only provides a single type-construction function without any parameters. A way to provide multiple constructors is needed.Using the safe traits looks much like using the existing ones, except that
T::new()
(or similar) is not called directly.Perhaps
SafeCtor
could be renamed toSelfReferenceSetup
, thecontruct()
trait functions removed, and thector::construct()
function modified to receive aT
by value instead. That would handle both multiple construction paths, while still sharing the setup of self-references.