Suggestion 2:
ok take what I said in my previous comment, but add to it a list of exceptions that are known not to mutate the values. For example .next(&mut)
doesn't mutate the value (but rather the mutation happens by some other method/function after you called .next(&mut)
.
In other words:
use itertools::Itertools;
static MUT_REF_CONSUMERS_THAT_DONT_MUTATE: [&str; 3] = [
"std::iter::Iterator::next(&mut self)",
"std::array::IntoIter::next(&mut self)",
"(etc)",
];
fn main() {
let list_of_all_mut_ref_consumers: Vec<&str> = list_of_all_mut_ref_consumers();
let possible_mutators = list_of_all_mut_ref_consumers
.iter()
.filter(|mut_consumer| !MUT_REF_CONSUMERS_THAT_DONT_MUTATE.contains(mut_consumer))
.collect_vec();
let immutable_ref_is_allowed = possible_mutators.is_empty();
println!("{immutable_ref_is_allowed}");
}
pub(crate) fn list_of_all_mut_ref_consumers<'a>() -> Vec<&'a str> {
todo!();
}
Suggestion 3
another approach that could be used:
split &mut
into two different variants:
- A) one that's used by the lowest method/function that actually mutates the value (&mut)
- B) one that enables child methods/functions to mutate but doesn't do the mutation itself (&muto (or some other keyword))
Traverse the code from when &mut was initialized until it's dropped. if you only have B but not A, then immutable reference can be used. I actually think this approach will be cleaner compared to the one I described earlier in this comment.
There's gonna be a very large number of methods that will take &mut without directly performing any mutations. Not just in standard library, but when people write their own functions/methods and pass &mut arguments. At first, people who feel frustrated with the inability to have immutable references because of &mut can replace it with unsafe {&muto}
in their code to tell the compiler that the user has checked the code for borrow safety and has consciously decided to disagree with the borrow checker. Then eventually down the line, the compiler could calculate itself whether a function taking a &mut actually delegates the mutation to another function, in which case the compiler would take &mut in the source code and implicitly turn it into &muto
during compilation process
Suggestion 4
Introduce an attribute for function arguments that promises the compiler that the function will never directly mutate a value, but may only delegate mutations to its children. Let's call this attribute #[delegated_mut_ref]
. As an example, you'd need to rewrite this:
fn next(&mut self) -> Option<Self::Item>;
into this:
fn next(#[delegated_mut_ref] &mut self) -> Option<Self::Item>;
The standard library can be manually reviewed for &mut
args to assess whether they actually perform mutations directly or if they just empower some child items to perform mutation. If it's the latter, then add the #[delegated_mut_ref]
attribute to the &mut
argument.
Then, have the compiler follow the given &mut
throughout its scope and look for occurrences of &mut
that don't have the #[delegated_mut_ref]
attribute. If all the places where &mut
is used has the #[delegated_mut_ref]
attribute, then immutable reference should be allowed