I'm trying to factor out some complex logic into a function, however I would need to pass two arguments where one is borrowed from another. MWE: Rust Playground
I understand why f1 fails to compile, but my question is whether there is a workaround or an idiom to use in this case. The code is sound, because if we "inline" f1_sub into f1, yielding f2, the compiler correctly sees that x is only used after the mutably borrowed slice ends. But splitting it does not seem to be possible.
To elaborate, in my use-case the portion equivalent here to "f1_sub" is quite complex, and I'm failing to see a way to avoid copy-pasting it everywhere (beyond macros I guess).
that's not possible with exclusive references, a.k.a. &mut borrows.
you are not using any unsafe, so sound is not the correct term here.
it depends very much on the actual implementation. if you cannot share the actual work, it's impossible to give advices that would be actually useful.
for example, if your code has some kind of interleaving pattern similar to this:
fn foo(container: &mut Container, data: &mut [Data]) {
// ...
// a chunk of code process the data, e.g.
slice.iter_mut().filter(...).find(...);
// ...
// modify the container. e.g.
x.push(...); x.dedup(...); x.retain(...);
// ...
// some other code to process the data
data.iter_mut(...).filter_map(...).fold(...);
// ...
}
you can then break it up into smaller units, in other words, reduce the borrowed region to minimum, something like:
this is just an example. how exactly you can refactor really depends on what you are doing. the most important principle is do not hold borrowed stuff longer than necessary.
If you used unsafe to call the method in the manner described, it would be UB due to &mut's no-alias rule, as the borrows must both be active at the point of the function call. Moreover, the borrows that are part of input parameters are considered to last throughout the entirety of the function (and the compiler does optimize based on that condition). The analysis you describe only applies to borrows created locally.
Perhaps think of the "inlining" as so:
/// "Inlined"
fn f2(x: &mut Vec<i32>) {
let slice: &mut [i32] = x.as_mut_slice();
let __pass_args_to_fn = (x, slice);
slice[1] *= 2;
x.push(123);
let __borrows_active_throughout_fn = (slice, x);
}