That’s actually what typically is meant by “self-referencing” struct, and also what ouroboros tailors to. It’s not necessarily that any given field references itself, but the struct as a whole does when one field contains a reference to another field (in some form). Note that this is only true, assuming I interpret what you’re saying correctly, namely, that not only the construction requires a reference to another field, but also that those references are required to stay live beyond the construction.
In Rust syntax this would mean a setting that looks less like
struct System {
ver: Version,
logger: Logger,
computer: Computation,
}
and more like
struct System {
ver: Version,
logger: Logger<'a>,
computer: Computation<'b>,
}
where instead of 'a
(or 'b
) what we “want” to say is something like “lifetime of field ver
” (or logger
) or “this struct borrows from the field ver
” (or logger
).
If on the other hand the types of Logger
or Computation
don’t have lifetime parameters, there should be no problem in the first place, so I doubt that’s the case, given that you wouldn’t have opened this thread in this case.
Ouroboros can express this e.g. as follows:
use ouroboros::self_referencing;
#[self_referencing]
struct SystemFields {
ver: Version,
#[borrows(ver)]
#[covariant]
logger: Logger<'this>,
#[borrows(logger)]
#[covariant]
computer: Computer<'this>,
}
// wrapper so we can define our own "new" function, using
// the ouroboros-generated one internally
struct System(SystemFields);
impl System {
fn new() -> Self {
Self(
// let's use the more fancy …Builder API instead of
// `SystemFields::new` (both are essentially equivalent though)
SystemFieldsBuilder {
ver: Version::new(),
logger_builder: |version| Logger::new(version),
computer_builder: |logger| Computer::new(logger),
}
.build(),
)
}
}
(full code in the “Rust Explorer”)
Note by the way that ouroboros
will implicitly add a level of indirection to the borrowed fields ver
and logger
(i.e. as if those were Box<Version>
and Box<Logger<'_>>
), otherwise this System
struct could not be moved, even though Rust typically assumes every type can be moved. (This is a significant difference to C++ by the way, where “moving”, if it’s even supported by a given type, only happens through customizable “move constructors”) There’s alternative approaches in Rust where the safe API around such a struct would utilize Pin
so that users can never move it, but that’s a bit more complicated and I’m not aware of existing crates akin to ouroboros
that would help you with building such a struct without touching the unsafe
ty yourself.