I know exactly what you mean about "simplest things cannot be done 'normally'". Rust is really different and it is easy to get discouraged (happens to me regularly and I have spent MUCH more than a few weeks). Learning to think about programming differently is a real challenge. I would say it is a better way of thinking, because you must think and design explicitly for shared vs exclusive data access, which makes your program much better overall.
Taking a break isn't a bad idea sometimes, but I hope you continue. The benefit of Rust is very large: no data races or memory safety issues, with C/C++ level performance. No other language I know of can do this currently. It is worth the trouble.
The key for me is to not try to make Rust conform to how I have programmed in the past. In this case I suggest to just try passing the Game parameter to the Lemming functions when you need it, or vice versa. It doesn't feel right at first and you often have to organize your program differently than you might normally do, but in most cases it works out fine.
Here is an example of what I'm talking about:
struct Game {
data: usize,
lemmings: Vec<Lemming>,
}
impl Game {
fn f(&mut self, lemming_id: usize) {
self.data += 1;
self.lemmings[0].g(&mut self.data);
}
fn add_lemming(&mut self) -> usize {
let id = self.lemmings.len();
self.lemmings.push(Lemming::new());
id
}
}
struct Lemming {
data: usize,
}
impl Lemming {
fn new() -> Self {
Self { data: 1 }
}
fn g(&mut self, game_data: &mut usize) {
*game_data += self.data;
self.data += 1;
}
}
let mut game = Game { data: 1, lemmings: vec![] };
let lemming_id = game.add_lemming();
game.f(lemming_id);
If you tried to do this differently, for example, by passing a &mut
for both the Game and Lemming to a function, you would get a compile error:
impl Lemming {
fn h(&mut self, game: &mut Game) {
// ...
}
}
game.lemmings[lemming_id].h(&mut game);
error[E0499]: cannot borrow `game` as mutable more than once at a time
--> src/lib.rs:58:37
|
58 | game.lemmings[lemming_id].h(&mut game);
| ------------- - ^^^^^^^^^ second mutable borrow occurs here
| | |
| | first borrow later used by call
| first mutable borrow occurs here
The &mut Game
and &mut Lemming
are exclusive borrows and they overlap, so they're not allowed. You get this error even if h
actually uses non-overlapping fields, since the compiler doesn't look at the implementation of the function, only at the signature, when type checking calls to that function.
To solve this we can pass &mut
parameters for different fields of Game, and that way the compiler knows there is no overlap.
impl Game {
fn f(&mut self, lemming_id: usize) {
self.data += 1;
self.lemmings[0].g(&mut self.data);
}
Obviously, doing this sort of thing will probably require you to restructure your old Game code. This is the price we pay for the benefits of Rust.
EDIT: This is really the same thing that @zirconium-n posted above, I missed that somehow as I was replying earlier. Their reply is a better description of this approach.