Hi,
I am designing a system with some config and a state that can be initialized from config, borrowing from it. The state also has an update
function that allows swapping out the config for a new version. This will retain parts of the state that are still valid under the new configuration, while re-initializing other parts. The example code below uses a simplified version of both structures.
struct Config {
name: String,
}
struct State<'a> {
name: &'a str,
value: String,
}
impl<'a> State<'a> {
fn new(config: &'a Config, value: String) -> Self {
Self {
name: &config.name,
value,
}
}
fn update<'b>(self, config: &'b Config) -> State<'b> {
State {
name: &config.name,
value: self.value,
}
}
fn identify(&self) {
eprintln!("state has {}", self.name);
}
}
When actually trying to use these structures, however, we run into some difficulties. Ideally, we would want a current
config variable and a state
variable borrowing from it. When an updated config arrives, we can construct a new state, consuming the old one, allowing the original config to be dropped. To do so, we need to borrow from the new config, making it impossible to move it to the original config variable afterwards.
It is easy enough to update the config once:
fn main() {
let a: Config = Config {
name: String::from("my name"),
};
let mut state = State::new(&a, String::from("state"));
state.identify();
let b: Config = Config {
name: String::from("another name"),
};
state = state.update(&b);
std::mem::drop(a);
state.identify();
}
I was actually a little surprised to see this compile. I'd expect the compiler to deduce a lifetime for state
based on the reference to config a
and then complain when we overwrite its value with a state with another lifetime bound to config b
. I'd expect to need let state = state.update(&b)
instead; but the compiler accepts the program and seems to do the right thing. If i try to drop a
before the update, it complains. It seems as if the type of state
changed through the assignment, even though it is still the same variable. Can anyone explain how this actually works?
To update the config in a loop, i came up with the following solution:
fn main() {
let mut configs = [
Config {
name: String::from("my name"),
},
Config {
name: String::from("another name"),
},
Config {
name: String::from("third name"),
},
Config {
name: String::from("fourth name"),
},
]
.into_iter();
let mut a: Option<Config> = (&mut configs).next();
let mut b: Option<Config> = None;
let mut state: State<'_> = State::new(a.as_ref().unwrap(), String::from("state"));
state.identify();
while let Some(next) = configs.next() {
b = Some(next);
state = state.update(b.as_ref().unwrap());
state.identify();
if let Some(next) = configs.next() {
a = Some(next);
state = state.update(a.as_ref().unwrap());
state.identify();
} else {
break;
}
}
}
This alternates between two variables to hold the current config. It works, though it produces a warning about the value of b
never being read (which i believe is not correct). Any suggestions for a more elegant solution?