I am in the process of porting some (old) C++ code to rust. The code includes a main class (System), the contains HashMaps and Vectors of various other classes. The original code used pointers. For the most part most of this data structure is "static" -- it is read from a set of files and one section is ever persistently modified, although some of the other structures are updated during processing.
I have most of the data structure implemented and working, but I have hit a snag.
The original C++ code is here:
And my rust port is here:
The build log is here:
type or Compiling freight_car_forwarder v0.1.0 (/home/heller/RustProjects/freight_car_forwarder)
error: lifetime may not live long enough
--> src/system.rs:181:9
|
180 | pub fn IndustryByIndexMut(&mut self, i: usize) -> Option<&mut Industry> {
| ---------
| |
| let's call the lifetime of this reference `'1`
| has type `&mut system::System<'2>`
181 | self.industries.get_mut(&i)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ associated function was supposed to return data with lifetime `'2` but it is returning data with lifetime `'1`
|
= note: requirement occurs because of a mutable reference to `Industry<'_>`
= note: mutable references are invariant over their type parameter
= help: see <https://doc.rust-lang.org/nomicon/subtyping.html> for more information about variance
error: could not compile `freight_car_forwarder` due to previous error
I think I need to specify lifetimes, but I am not sure where.
There's a good chance you don't want to be storing &Car in your long term structs. But let's accept that for now and look at the problem site:
// vv further below I call this `'_unnamed`
impl <'system>System <'_> {
// up-to-date rustc will warn you about some elided lifetimes here
// or alternatively use `#![deny(elided_lifetimes_in_paths)]`
pub fn IndustryByIndexMut(&mut self, i: usize) -> Option<&mut Industry> {
self.industries.get_mut(&i)
}
// Desugared
pub fn IndustryByIndexMut<'this>(self: &'this mut System<'_unnamed)
-> Option<&'this mut Industry<'this>>
{
self.industries.get_mut(&i)
}
And the problem is, you have Industry<'_unnamed> not Industry<'this>. And you can't coerce the inner lifetime due to invariance (it would be unsound). And you don't want to return that nested lifetime anyway.
OK, that combo solves it -- the code compiles now.
The Vec<&Car> in the Industry struct are always going to be refernces to some subset of the elements on the Vec in the System struct. The Car structs are the one piece of the data that does get updated (and will be writen out as persistent updated data).
But the Industry structs are also owned by the System struct. So that's what we call a self-referencial struct. In Rust, references are the wrong tool for the job, and you'll probably have a bad time of it. (When you try to make a method to actually create the references into Systems::cars and store them in System::industries, the compiler is going to suggest APIs like &'this mut System<'this>, and that will render your System<'_> unusable.)
You might want Rc<Car> or similar instead. Or maybe move ownership of the Cars to outside of both System and Industry (though long-lived reference-carrying data structures is a tough journey when still learning Rust).
I suppose I could just use a Vec, where the elements are the indexes into the Vec vector. That might work. It will make the rust code look more the original QBasic code this package was originally coded in (lots of BASIC arrays of numbers which indexed each other).