I always have a strong opinion against "two-phase-intialization" or "two-stage-construction", even in C++. (there's a cppcon lightning talk for those from C++ background).
the problem with two phase initialzation is that you have to add "partial constructed" states into your type (e.g. the Option
type mentioned in the original post), which brings no benifits whatsoever, but makes the safety guarantee of your type weaker, because you can't hold certain invaraint in your type anymore, and you always need to check at runtime.
a constructor is where you should establish the invariants of your type, if the object coming out of the constructor could be in a "incomplete" or "invalid" state, and requires a second step to "initialize", you type would definitely be used incorrectly at sometime, by someone (which could probably be yourself).
in languages like C++ (or Java), you can' change the return type of a constructor, so a fallible constructor can only be implemented by throwing an exception on failure, (but some C++ people or organizations don't use exceptions). two phase intialization is invented just to overcome the language limitation.
but in rust, we don't have a C++ like "constructor" concept defined in the core language. instead, we treat certain associated functions as constructors
purely by convention, and indeed a constructor can return any type you deem suitable, be it Result<Self>
, Option<Self>
, Box<Self>
, thanks to the sophisticated type system and optimizer, (notably, ownership and move can guarantee you don't get unexpected copy of heavy resources, and better yet, many of the moves are actually eliminated by the optimizer)
so if your type must be initialized awaiting some Futures
be ready, just make the constructor return type like:fn new() -> impl Future<Output = Result<Self>>
, or, use a async function.