fight-compiler - alternative approaches?
nneonneo opened this issue · comments
The single "fight-compiler" exercise borrows the list for iteration, then attempts to borrow the structure mutably in each iteration, which naturally fails.
One of the reasons why this fails is that do_something
receives a reference to the entire structure, meaning that it could (in principle) mutate the list while the iteration is ongoing in run
. Obviously, Rust forbids this.
However, the proposed solution (https://github.com/sunface/rust-by-practice/blob/master/solutions/fight-compiler/borrowing.md) involves refactoring the iteration to a regular indexed loop. This doesn't actually solve the underlying (potential) safety hazard: do_something
could still mutate the list, which could cause the caller to panic unexpectedly if e.g. the list was truncated by some other implementation of do_something
. It can be argued that the API is broken here, because it does not appropriately restrict implementers from doing unexpected things.
There are a few ways this can be solved (IMHO) more cleanly, although they involve changing the API of do_something
:
- Don't give
do_something
access to&mut self
; instead, give it a&mut i32
reference so it is only capable of mutatinga
and notlist
. So,pub fn do_something(a: &mut i32, n: i32) { *a = n; }
- Convert
do_something
to a closure. Closures get special treatment because the compiler can directly see what they borrow. So, for example,self.list.iter().for_each(|i| { self.a = *i; });
works. In fact, we can even do something likeself.list.iter().for_each(|i| { self.a = self.list[(7 - *i) as usize]; });
, borrowinglist
immutably anda
mutably, which (AFAIK) you simply cannot do with a separate function.
Couldn't you also just clone the array before iterating it? I feel like the rules are a little too loose. :)