I'm writing a parser for a format that is a sequence of records. Records can be either data or definition records; the definition records contain instructions for how to parse subsequent data records. There are a handful of other ways in which the results of parsing one record should cause all subsequent records to be parsed differently.
I've tried to structure my code with a parsing state struct that looks like this:
pub struct FitParsingState<'a> {
map: HashMap<u16, Rc<FitDefinitionMessage>>,
last_timestamp: Option<FitFieldDateTime>,
timezone_offset_secs: Option<i32>,
developer_data_definitions: HashMap<u8, FitDeveloperDataDefinition<'a>>,
}
I then pass a mut reference to this state object down into the code which parses the next record:
let (fit_message, out) = match header {
FitRecordHeader::Normal(normal_header) => {
match normal_header.message_type {
FitNormalRecordHeaderMessageType::Data => {
let (data_message, o) = FitDataMessage::parse(o, FitRecordHeader::Normal(normal_header), parsing_state, None)?;
(FitMessage::Data(data_message), o)
},
FitNormalRecordHeaderMessageType::Definition => {
let (definition_message, o) = FitDefinitionMessage::parse(o, normal_header)?;
parsing_state.add(definition_message.header.local_mesg_num, definition_message.clone());
(FitMessage::Definition(definition_message.clone()), o)
}
}
},
_ => return Err(Error::unknown_error())
};
I then want to do some post-processing on whatever record comes back, as some messages should cause things in the parsing state to change. This is where I run into problems:
match &fit_message {
FitMessage::Data(m) => {
match m {
FitDataMessage::DeviceSettings(ds) => {
match *ds.deref() {
FitMessageDeviceSettings{time_zone_offset, ..} => {
// device_settings.time_zone_offset is 'in quarter hour increments',
// so a value of +15 = (15 increments * 15 minutes * 60 seconds) =
// = +13500 seconds
if let Some(tzo) = time_zone_offset {
parsing_state.set_timezone_offset((tzo * 15 * 60).into());
}
}
}
},
FitDataMessage::FieldDescription(fd) => {
if let Some(ddi) = fd.developer_data_index {
parsing_state.set_developer_data_definition(ddi, FitDataMessage::FieldDescription(fd.clone()));
}
}
_ => {},
}
},
_ => {},
}
The borrow checker complains that I'm trying to borrow parsing_state mutably more than once:
error[E0499]: cannot borrow `*parsing_state` as mutable more than once at a time
--> src/lib.rs:246:25
|
167 | let (data_message, o) = FitDataMessage::parse(o, FitRecordHeader::Normal(normal_header), parsing_state, None)?;
| ------------- first mutable borrow occurs here
...
246 | parsing_state.set_developer_data_definition(ddi, FitDataMessage::FieldDescription(fd.clone()));
| ^^^^^^^^^^^^^ second mutable borrow occurs here
...
256 | }
| - first borrow ends here
What I don't understand about this is why parsing_state is still borrowed after the first block above. I've tried nesting that block further into a separate block, but that doesn't help. I assume that what's happening is that down in FitDataMessage::parse I end up storing a reference to an Rc object that I retrieve from parsing_state into the message I'm parsing, but I'm guessing at that.
I'm new to rust, and this is my first serious dance with the borrow checker, so I'll try asking the general form of my question: if I'm writing a parser like this with some state that is going to change from record to record during the parse, what's the right rust-y way to keep track of that state? It's unavoidable that I'm going to end up with messageN needing to have a reference to messageM given the spec, so I suspect that I just haven't found the right magic combination of Rc/RefCell/Box/whatever to convince the borrow checker that I'm not a bad person. Help appreciated!