I've run into this problem a few times now in Rust. For a minimal example, consider the following:
struct Config {
aa: u32,
bb: f64,
cc: i32,
}
struct Inner<'a> {
// inner's methods need to reference aa and bb
config: &'a Config,
}
struct Outer<'a> {
// outer's methods need to reference aa, bb, and cc
config: Config,
inner: Inner<'a>,
}
fn main() {
// what would actually happen here is we'd read a config file to populate 'config'
let config = Config {aa: 5, bb: 0.3, cc: -8};
let inner = Inner {config: &config};
// this works fine, although now we have two copies of the config...
let outer = Outer {
config: Config {aa: 5, bb: 0.3, cc: -8},
inner
};
// do some stuff with outer
}
This compiles and would achieve the desired behaviour, unless outer
's config was ever modified; then we'd have different configs driving the outer
and inner
components. But suppose I want to have a constructor function for Outer
(which I do). If I write something like this, it won't compile:
impl<'a> Outer<'a> {
fn new() -> Outer<'a> {
// what would actually happen here is we'd read a config file to populate 'config'
let config = Config {aa: 5, bb: 0.3, cc: -8};
let inner = Inner {config: &config};
return Outer {config, inner};
}
}
There's a chicken-and-egg problem here. Outer
needs to be instantiated with an existing inner
and config
, but the Inner
type needs a reference to the same config
. inner
has to be built with a reference to the config
inside outer
, so it needs to be instantiated after outer
; but outer needs to be instantiated with an existing Inner
struct, so inner
needs to be instantiated before outer
.
If these three structs could be instantiated at the same time, there would be no problem. All references would last as long as they need to, and all the program logic would run fine under Rust. But I don't think Rust allows such a thing.
I've found a few ways of solving this, all of which seem kludgey and inelegant. I could:
- Use a reference-counting pointer for
config
:
struct Inner {
config: Rc<Config>,
}
struct Outer {
config: Rc<Config>,
inner: Inner,
}
I don't like this because I don't actually need any reference-counting features while using the Outer struct; and there's no advantage to having config
on the heap. It's logical for config
to be owned by outer
, and it shouldn't ever last any longer than outer
.
- Make Outer have an option of its Inner so that it can be instantiated with None, then set with
Some(inner)
:
struct Outer<'a> {
config: Config,
inner: Option<Inner<'a>>,
}
impl<'a> Outer<'a> {
fn new() -> Outer<'a> {
let config = Config {aa: 5, bb: 0.3, cc: -8};
let outer = Outer {config, None};
let inner = Inner {config: &outer.config};
outer.inner = Some(inner);
return outer;
}
}
I don't like this because in the logic this program is supposed to implement, there's no case where an Outer should ever lack an Inner. The only time it ever needs to be None is at this point in new()
. All the other methods of Outer must now needlessly call unwrap()
on something that will always be Some
.
Is there some elegant idiomatic way of dealing with this issue in Rust other than what I've outlined here?