Can a Rust closure be used by multiple threads? - multithreading

I'd like the ability to have multiple threads evaluate the same closure. The application I have in mind is parallelized numerical integration, so a situation where the function domain can be easily split into N chunks and handed to threads.
This is a simple function that evaluates the provided closure multiple times and averages the result:
use std::sync::mpsc;
use std::thread;
const THREAD_COUNT: u64 = 4;
fn average<F: Fn(f64) -> f64>(f: F) -> f64 {
let (tx, rx) = mpsc::channel();
for id in 0..THREAD_COUNT {
let thread_tx = tx.clone();
thread::spawn(move || {
thread_tx.send(f(id as f64));
});
}
let mut total = 0.0;
for id in 0..THREAD_COUNT {
total += rx.recv().unwrap();
}
total / THREAD_COUNT as f64
}
fn main() {
average(|x: f64| -> f64 { x });
}
When I compile I get this error:
error[E0277]: `F` cannot be sent between threads safely
--> src/main.rs:10:9
|
10 | thread::spawn(move || {
| ^^^^^^^^^^^^^ `F` cannot be sent between threads safely
|
= help: within `[closure#src/main.rs:10:23: 12:10 thread_tx:std::sync::mpsc::Sender<f64>, f:F, id:u64]`, the trait `std::marker::Send` is not implemented for `F`
= help: consider adding a `where F: std::marker::Send` bound
= note: required because it appears within the type `[closure#src/main.rs:10:23: 12:10 thread_tx:std::sync::mpsc::Sender<f64>, f:F, id:u64]`
= note: required by `std::thread::spawn`
So I add + Send to the bounds on F and get a new error:
error[E0310]: the parameter type `F` may not live long enough
--> src/main.rs:10:9
|
6 | fn average<F: Fn(f64) -> f64 + Send>(f: F) -> f64 {
| -- help: consider adding an explicit lifetime bound `F: 'static`...
...
10 | thread::spawn(move || {
| ^^^^^^^^^^^^^
|
note: ...so that the type `[closure#src/main.rs:10:23: 12:10 thread_tx:std::sync::mpsc::Sender<f64>, f:F, id:u64]` will meet its required lifetime bounds
--> src/main.rs:10:9
|
10 | thread::spawn(move || {
| ^^^^^^^^^^^^^
So I add + 'static to F and get this:
error[E0382]: capture of moved value: `f`
--> src/main.rs:11:28
|
10 | thread::spawn(move || {
| ------- value moved (into closure) here
11 | thread_tx.send(f(id as f64));
| ^ value captured here after move
|
= note: move occurs because `f` has type `F`, which does not implement the `Copy` trait
So I add + Copy to F and get:
error: the trait `core::marker::Copy` is not implemented for the type `[closure#src/test.rs:115:11: 115:26]
It seems every thread wants its own copy of the closure (because of move) but closures don't implement Copy so no luck. It seems strange to me because if the closures are never mutating state then what's the safety issue with multiple threads accessing them?
I can get the code to work by providing a regular function instead of a closure, but this makes my code non-generic, i.e. it only works for a specific function instead of for anything that's Fn(f64) -> f64. And for the type of integration I'm doing, the functions integrated often have certain fixed variables mixed with the variable of integration, so it would seem natural to capture the fixed variables with a closure.
Is there some way to make this kind of multithreaded function evaluation work in a generic manner? Am I just thinking about things wrong?

The ultimate problem revolves around who owns the closure. The code as written states that ownership of the closure is transferred to average. This function then tries to give the closure to multiple threads, which fails as you have seen, as you can't give one item to multiple children.
but closures don't implement Copy so no luck
As of Rust 1.26.0, closures do implement Clone and Copy if all of the captured variables do. This means your final example code now works as-is:
fn average<F: Fn(f64) -> f64 + Send + 'static + Copy>(f: F) -> f64 { /* ... */ }
However, it's possible that your closures won't implement Copy or Clone.
You cannot give out a reference to the closure owned by average because the thread created with thread::spawn may outlive the call to average. When average exits, any stack-allocated variables will be destroyed. Any use of them would cause memory unsafety, which Rust aims to prevent.
One solution is to use an Arc. This will allow multiple shared owners of a single resource in a multithreaded context. When the wrapped closure is cloned, only a new reference is created. When all references disappear, the object is freed.
use std::{
sync::{mpsc, Arc},
thread,
};
const THREAD_COUNT: u64 = 4;
fn average<F>(f: F) -> f64
where
F: Fn(f64) -> f64 + Send + Sync + 'static,
{
let (tx, rx) = mpsc::channel();
let f = Arc::new(f);
for id in 0..THREAD_COUNT {
let thread_tx = tx.clone();
let f = f.clone();
thread::spawn(move || {
thread_tx.send(f(id as f64)).unwrap();
});
}
let mut total = 0.0;
for _ in 0..THREAD_COUNT {
total += rx.recv().unwrap();
}
total / THREAD_COUNT as f64
}
fn main() {
average(|x| x);
}
A more standard solution is to use scoped threads. These threads are guaranteed to exit by a certain time, which allows you to pass references that outlive the threads to the threads.
See also:
How can I pass a reference to a stack variable to a thread?
How do I pass disjoint slices from a vector to different threads?

Related

What does `move occurs due to use in generator` error mean?

I'm having this problem about generators:
use tokio::runtime::Runtime;
use tokio::task::JoinHandle;
use std::sync::Arc;
pub fn run(f: Box<dyn Fn() -> Result<(), ()> + Send>) {
f();
}
fn main() {
let x = Arc::new(0);
run(Box::new(move ||{
let rt = Runtime::new().unwrap();
let _t = rt.block_on(async move {
let y = x;
});
Ok(())
}));
}
https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=de28ecf9e5baf6a017cd6a5230d74d7b
Error:
error[E0507]: cannot move out of `x`, a captured variable in an `Fn` closure
--> src/main.rs:13:41
|
10 | let x = Arc::new(0);
| - captured outer variable
...
13 | let _t = rt.block_on(async move {
| _________________________________________^
14 | | let y = x;
| | -
| | |
| | move occurs because `x` has type `Arc<i32>`, which does not implement the `Copy` trait
| | move occurs due to use in generator
15 | | });
| |_________^ move out of `x` occurs here
I don't understand why x is borrowed, if in both the closure and the block, I use move. So x should be moved to rt.block_on's closure. There should be no borrow at all.
Generators are an unstable feature, currently only available in nightly, which can be compared to generators or coroutines in other languages (e.g. Javascript, Go, Python).
Generators are essentially state machines that can pause execution using yield and later be resumed again, with the possibility of passing data in each transition. This pattern is ideal for asynchronous programming and the Rust compiler expands certain async code to use generators, even though you can't explicitly use them yourself without the nightly feature enabled.
It is possible that it is a bug that these messages are not properly feature-gated, or it may just have been too complicated to present different errors for code generated from async desugaring than for code you explicitly wrote yourself.
So let's ignore generators, which are a bit of a red herring for your actual problem.
You are moving x into a closure:
let x = Arc::new(0);
run(Box::new(move ||{
// 'move' closure uses x so x is moved
}));
And then the closure moves x again into an async block. The problem is that the signature of run accepts a Fn closure - a closure that cannot modify its environment but may be called multiple times. However the closure you have provided moves x into the async block the first time it's called, so it would be invalid to call it a second time.
Given you have said that you can't change run to accept an FnOnce, you need to make f callable multiple times by preventing it from moving x. You can do that by cloning it:
fn main() {
let x = Arc::new(0);
run(Box::new(move || {
let rt = Runtime::new().unwrap();
// each time the closure is called, it will pass a clone of the Arc
// into the task, so it can be called more than once
let x = x.clone();
let _t = rt.block_on(async move {
let y = x;
});
Ok(())
}));
}
The whole design of Arc is about cheap cloning. It's cheap because it only copies the pointer to your data and increments the reference count, which is how it knows when it is safe to free the memory of the holding its data. If you are never cloning an Arc then you almost certainly didn't need it in the first place!
The "move occurs due to use in generator" error seems to confuse things a bit here, but the overall issue isn't related to generators.
Your run function accepts Fn() functions, not FnOnce() functions. That means that the function passed to run must be able to execute multiple times. Given that, your code cannot work as written because running your function takes ownership of x when assigning it to y. If the first call to the function takes ownership of x, what is supposed to happen the second time the function runs? Ownership of x was already given to the future the first time it executed, and two things cant own the same value. The same issue would also happen with
run(Box::new(move ||{
let y = x;
Ok(())
}));
Since you have an Arc I'm guessing you had a sense of this already, but it won't happen without a bit more logic. Since you're using an Arc, you need to clone x at the start of the function, so that each time the function runs, it gets its own Arc object to handle and manipulate.
fn main() {
let x = Arc::new(0);
run(Box::new(move ||{
// Make a new Arc that can be given to the promise without needing
// to worry about what future calls to this callback willl do.
let x = Arc::clone(&x);
let rt = Runtime::new().unwrap();
let _t = rt.block_on(async move {
let y = x;
});
Ok(())
}));
}

Why is Rust forcing to use move in case of i32 while spawing threads?

I'm new to Rust and looks like I'm seriously missing some concept here.
use std::thread;
fn main() {
let mut children = vec![];
//spawn threads
for i in 0..10 {
let c = thread::spawn(|| {
println!("thread id is {}", i);
});
children.push(c);
}
for j in children {
j.join().expect("thread joining issue");
}
}
It fails with the error:
error[E0373]: closure may outlive the current function, but it borrows `i`, which is owned by the current function
Since the type of i is i32 , and there are no references involved shouldn't Rust copy the value instead of forcing to move ?
The answer to your original question is that println! borrows its arguments. However, as you pointed out in the comments, even (apparently) moving the integer into the closure still causes a compile error.
For the purposes of this answer, we'll work with this code.
fn use_closure<F: FnOnce() + 'static>(_: F) {}
fn main() {
let x: i32 = 0;
use_closure(|| {
let _y = x;
});
}
(playground)
use_closure simulates what thread::spawn does in the original code: it consumes a closure whose type has to be 'static.
Attempting to compile this gives the error
error[E0373]: closure may outlive the current function, but it borrows `x`, which is owned by the current function
--> src/main.rs:5:17
|
5 | use_closure(|| {
| ^^ may outlive borrowed value `x`
6 | let _y = x;
| - `x` is borrowed here
|
note: function requires argument type to outlive `'static`
--> src/main.rs:5:5
|
5 | / use_closure(|| {
6 | | let _y = x;
7 | | });
| |______^
help: to force the closure to take ownership of `x` (and any other referenced variables), use the `move` keyword
|
5 | use_closure(move || {
| ^^^^^^^
Wait, what?
6 | let _y = x;
| - `x` is borrowed here
Why is x borrowed there? Shouldn't it be a copy? The answer lies in "capture modes" for closures. From the documentation
The compiler prefers to capture a closed-over variable by immutable borrow, followed by unique immutable borrow (see below), by mutable borrow, and finally by move. It will pick the first choice of these that allows the closure to compile. The choice is made only with regards to the contents of the closure expression; the compiler does not take into account surrounding code, such as the lifetimes of involved variables.
Precisely because x has a Copy type, the closure itself can compile with a mere immutable borrow. Given an immutable borrow of x (call it bor), we can do our assignment to _y with _y = *bor. This isn't a "move out of data behind a reference" because this is a copy instead of a move.
However, since the closure borrows a local variable, its type won't be 'static, so it won't be usable in use_closure or thread::spawn.
Trying the same code with a type that isn't Copy, it actually works perfectly, since the closure is forced to capture x by moving it.
fn use_closure<F: FnOnce() + 'static>(_: F) {}
fn main() {
let x: Vec<i32> = vec![];
use_closure(|| {
let _y = x;
});
}
(playground)
Of course, as you already know, the solution is to use the move keyword in front of the closure. This forces all captured variables to be moved into the closure. Since the variable won't be borrowed, the closure will have a static type and will be able to be used in use_closure or thread::spawn.
fn use_closure<F: FnOnce() + 'static>(_: F) {}
fn main() {
let x: i32 = 0;
use_closure(move || {
let _y = x;
});
}
(playground)

Why can't I create a closure that produces mutable references to what it closes on? [duplicate]

I was playing around with Rust closures when I hit this interesting scenario:
fn main() {
let mut y = 10;
let f = || &mut y;
f();
}
This gives an error:
error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
--> src/main.rs:4:16
|
4 | let f = || &mut y;
| ^^^^^^
|
note: first, the lifetime cannot outlive the lifetime as defined on the body at 4:13...
--> src/main.rs:4:13
|
4 | let f = || &mut y;
| ^^^^^^^^^
note: ...so that closure can access `y`
--> src/main.rs:4:16
|
4 | let f = || &mut y;
| ^^^^^^
note: but, the lifetime must be valid for the call at 6:5...
--> src/main.rs:6:5
|
6 | f();
| ^^^
note: ...so type `&mut i32` of expression is valid during the expression
--> src/main.rs:6:5
|
6 | f();
| ^^^
Even though the compiler is trying to explain it line by line, I still haven't understood what exactly it is complaining about.
Is it trying to say that the mutable reference cannot outlive the enclosing closure?
The compiler does not complain if I remove the call f().
Short version
The closure f stores a mutable reference to y. If it were allowed to return a copy of this reference, you would end up with two simultaneous mutable references to y (one in the closure, one returned), which is forbidden by Rust's memory safety rules.
Long version
The closure can be thought of as
struct __Closure<'a> {
y: &'a mut i32,
}
Since it contains a mutable reference, the closure is called as FnMut, essentially with the definition
fn call_mut(&mut self, args: ()) -> &'a mut i32 { self.y }
Since we only have a mutable reference to the closure itself, we can't move the field y out, neither are we able to copy it, since mutable references aren't Copy.
We can trick the compiler into accepting the code by forcing the closure to be called as FnOnce instead of FnMut. This code works fine:
fn main() {
let x = String::new();
let mut y: u32 = 10;
let f = || {
drop(x);
&mut y
};
f();
}
Since we are consuming x inside the scope of the closure and x is not Copy, the compiler detects that the closure can only be FnOnce. Calling an FnOnce closure passes the closure itself by value, so we are allowed to move the mutable reference out.
Another more explicit way to force the closure to be FnOnce is to pass it to a generic function with a trait bound. This code works fine as well:
fn make_fn_once<'a, T, F: FnOnce() -> T>(f: F) -> F {
f
}
fn main() {
let mut y: u32 = 10;
let f = make_fn_once(|| {
&mut y
});
f();
}
There are two main things at play here:
Closures cannot return references to their environment
A mutable reference to a mutable reference can only use the lifetime of the outer reference (unlike with immutable references)
Closures returning references to environment
Closures cannot return any references with the lifetime of self (the closure object). Why is that? Every closure can be called as FnOnce, since that's the super-trait of FnMut which in turn is the super-trait of Fn. FnOnce has this method:
fn call_once(self, args: Args) -> Self::Output;
Note that self is passed by value. So since self is consumed (and now lives within the call_once function`) we cannot return references to it -- that would be equivalent to returning references to a local function variable.
In theory, the call_mut would allow to return references to self (since it receives &mut self). But since call_once, call_mut and call are all implemented with the same body, closures in general cannot return references to self (that is: to their captured environment).
Just to be sure: closures can capture references and return those! And they can capture by reference and return that reference. Those things are something different. It's just about what is stored in the closure type. If there is a reference stored within the type, it can be returned. But we can't return references to anything stored within the closure type.
Nested mutable references
Consider this function (note that the argument type implies 'inner: 'outer; 'outer being shorter than 'inner):
fn foo<'outer, 'inner>(x: &'outer mut &'inner mut i32) -> &'inner mut i32 {
*x
}
This won't compile. On the first glance, it seems like it should compile, since we're just peeling one layer of references. And it does work for immutable references! But mutable references are different here to preserve soundness.
It's OK to return &'outer mut i32, though. But it's impossible to get a direct reference with the longer (inner) lifetime.
Manually writing the closure
Let's try to hand code the closure you were trying to write:
let mut y = 10;
struct Foo<'a>(&'a mut i32);
impl<'a> Foo<'a> {
fn call<'s>(&'s mut self) -> &'??? mut i32 { self.0 }
}
let mut f = Foo(&mut y);
f.call();
What lifetime should the returned reference have?
It can't be 'a, because we basically have a &'s mut &'a mut i32. And as discussed above, in such a nested mutable reference situation, we can't extract the longer lifetime!
But it also can't be 's since that would mean the closure returns something with the lifetime of 'self ("borrowed from self"). And as discussed above, closures can't do that.
So the compiler can't generate the closure impls for us.
Consider this code:
fn main() {
let mut y: u32 = 10;
let ry = &mut y;
let f = || ry;
f();
}
It works because the compiler is able to infer ry's lifetime: the reference ry lives in the same scope of y.
Now, the equivalent version of your code:
fn main() {
let mut y: u32 = 10;
let f = || {
let ry = &mut y;
ry
};
f();
}
Now the compiler assigns to ry a lifetime associated to the scope of the closure body, not to the lifetime associated with the main body.
Also note that the immutable reference case works:
fn main() {
let mut y: u32 = 10;
let f = || {
let ry = &y;
ry
};
f();
}
This is because &T has copy semantics and &mut T has move semantics, see Copy/move semantics documentation of &T/&mut T types itself for more details.
The missing piece
The compiler throws an error related to a lifetime:
cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
but as pointed out by Sven Marnach there is also a problem related to the error
cannot move out of borrowed content
But why doesn't the compiler throw this error?
The short answer is that the compiler first executes type checking and then borrow checking.
the long answer
A closure is made up of two pieces:
the state of the closure: a struct containing all the variables captured by the closure
the logic of the closure: an implementation of the FnOnce, FnMut or Fn trait
In this case the state of the closure is the mutable reference y and the logic is the body of the closure { &mut y } that simply returns a mutable reference.
When a reference is encountered, Rust controls two aspects:
the state: if the reference points to a valid memory slice, (i.e. the read-only part of lifetime validity);
the logic: if the memory slice is aliased, in other words if it is pointed from more than one reference simultaneously;
Note the move out from borrowed content is forbidden for avoiding memory aliasing.
The Rust compiler executes its job through several stages, here's a simplified workflow:
.rs input -> AST -> HIR -> HIR postprocessing -> MIR -> HIR postprocessing -> LLVM IR -> binary
The compiler reports a lifetime problem because it first executes the type checking phase in HIR postprocessing (which comprises lifetime analysis) and after that, if successful, executes borrow checking in the MIR postprocessing phase.

How can I construct a container from within a closure with a 'static bound? [duplicate]

This question already has an answer here:
Mutating the same data in multiple 'static closures
(1 answer)
Closed 4 years ago.
I'm using the libpulse_binding library, and I'm trying to obtain a sequence of SinkInputInfos from the get_sink_input_info_list function:
pub fn get_sink_input_info_list<F>(
&self,
callback: F,
) -> Operation<dyn FnMut(ListResult<&SinkInputInfo>)>
where
F: FnMut(ListResult<&SinkInputInfo>) + 'static,
The function takes a callback and calls it once for each SinkInputInfo it produces. I'm trying to collect all those SinkInputInfos into a single list so I can get a clearer view of the state of the world. Irritatingly, SinkInputInfo doesn't implement Copy or Clone, so I made a custom struct and implemented From to get the useful information out of the SinkInputInfo:
struct StreamInfo {
readable_name: String,
binary_name: String,
pid: String,
}
impl From<&pulse::context::introspect::SinkInputInfo<'_>> for StreamInfo {
fn from(info: &pulse::context::introspect::SinkInputInfo) -> Self {
let name = info.proplist.gets("application.name").unwrap();
let binary = info.proplist.gets("application.process.binary").unwrap();
let pid = info.proplist.gets("application.process.id").unwrap();
StreamInfo {
readable_name: name,
binary_name: binary,
pid: pid,
}
}
}
However, this doesn't seem to work. I have the following code:
let mut sink_infos: Vec<StreamInfo> = Vec::new();
let op = introspector.get_sink_input_info_list(|result| match result {
pulse::callbacks::ListResult::Item(info) => sink_infos.push(info.into()),
pulse::callbacks::ListResult::End => {},
pulse::callbacks::ListResult::Error => panic!("Error getting sink input info"),
});
but it doesn't compile:
error[E0373]: closure may outlive the current function, but it borrows `sink_infos`, which is owned by the current function
--> src/bin/play-pause.rs:49:52
|
49 | let op = introspector.get_sink_input_info_list(|result| match result {
| ^^^^^^^^ may outlive borrowed value `sink_infos`
50 | pulse::callbacks::ListResult::Item(info) => sink_infos.push(info.into()),
| ---------- `sink_infos` is borrowed here
|
note: function requires argument type to outlive `'static`
--> src/bin/play-pause.rs:49:14
|
49 | let op = introspector.get_sink_input_info_list(|result| match result {
| ______________^
50 | | pulse::callbacks::ListResult::Item(info) => sink_infos.push(info.into()),
51 | | pulse::callbacks::ListResult::End => {},
52 | | pulse::callbacks::ListResult::Error => panic!("Error getting sink input info"),
53 | | });
| |______^
help: to force the closure to take ownership of `sink_infos` (and any other referenced variables), use the `move` keyword
|
49 | let op = introspector.get_sink_input_info_list(move |result| match result {
| ^^^^^^^^^^^^^
tldr: the closure has to have the 'static lifetime because libpulse_binding says so (presumably because it gets handed to the PulseAudio C API, which can then do whatever it likes with it), but sink_infos is not 'static, and the closure has to borrow sink_infos in order to append to it, which makes the closure not 'static, IIUC.
How can I make a Vec (or any container, I'm not fussy) of SinkInputInfos given a 'static closure that gets called repeatedly with a &SinkInputInfo?
The basic problem here is that you're running into Rust's borrowing rules:
Given an object T, it is only possible to have one of the following:
Having several immutable references (&T) to the object (also known as aliasing).
Having one mutable reference (&mut T) to the object (also known as mutability).
You are trying to keep a reference &Vec to your Vec (so you can use it later) whilst simultaneously trying to add things to it in a closure (i.e. an &mut Vec). Rust doesn't know that you won't try to use the &Vec while the closure is using the &mut Vec, so it doesn't let you create a &mut Vec in the closure while there's still a &Vec hanging around outside the closure.
The next best thing you can do is to use an Rc. This will allow you to sidestep the compiler's borrow-checking and instead defer it to runtime. However: this means that if you try to violate the borrowing rules while your program is running, it will panic instead of being a compile-time error!
For the most part, you can treat the Rc<Vec<_>> the same as a normal Vec, since Rc implements Deref.
Since you also want to be able to mutate the Vec in order to add things to it, you also need to put it in a RefCell. This will put a lock around the Vec, making sure that you only have one &mut Vec available at once, and that if you have an &Vec, you can't have a &mut Vec (again, if you try to violate the rules, your program will panic). You can use the .borrow() and .borrow_mut() methods on RefCell to get shared and mutable references to the Vec (there are also try_* variants of these methods, if you can do something sensible if a borrow is not possible).
If you didn't use a RefCell, you would only be able to obtain immutable/shared references (&Vec) from the Rc (unless you only had one Rc, but then you wouldn't need an Rc!)
Try something like the following:
use std::cell::RefCell;
use std::rc::Rc;
let sink_infos: Rc<RefCell<Vec<StreamInfo>>> = Rc::new(RefCell::new(Vec::new()));
let sink_infos2 = sink_infos.clone(); // Create a new Rc which points to the same data.
let op = introspector.get_sink_input_info_list(move |result| match result {
pulse::callbacks::ListResult::Item(info) => sink_infos2.borrow_mut().push(info.into()),
pulse::callbacks::ListResult::End => {},
pulse::callbacks::ListResult::Error => panic!("Error getting sink input info"),
});

Why can I not return a mutable reference to an outer variable from a closure?

I was playing around with Rust closures when I hit this interesting scenario:
fn main() {
let mut y = 10;
let f = || &mut y;
f();
}
This gives an error:
error[E0495]: cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
--> src/main.rs:4:16
|
4 | let f = || &mut y;
| ^^^^^^
|
note: first, the lifetime cannot outlive the lifetime as defined on the body at 4:13...
--> src/main.rs:4:13
|
4 | let f = || &mut y;
| ^^^^^^^^^
note: ...so that closure can access `y`
--> src/main.rs:4:16
|
4 | let f = || &mut y;
| ^^^^^^
note: but, the lifetime must be valid for the call at 6:5...
--> src/main.rs:6:5
|
6 | f();
| ^^^
note: ...so type `&mut i32` of expression is valid during the expression
--> src/main.rs:6:5
|
6 | f();
| ^^^
Even though the compiler is trying to explain it line by line, I still haven't understood what exactly it is complaining about.
Is it trying to say that the mutable reference cannot outlive the enclosing closure?
The compiler does not complain if I remove the call f().
Short version
The closure f stores a mutable reference to y. If it were allowed to return a copy of this reference, you would end up with two simultaneous mutable references to y (one in the closure, one returned), which is forbidden by Rust's memory safety rules.
Long version
The closure can be thought of as
struct __Closure<'a> {
y: &'a mut i32,
}
Since it contains a mutable reference, the closure is called as FnMut, essentially with the definition
fn call_mut(&mut self, args: ()) -> &'a mut i32 { self.y }
Since we only have a mutable reference to the closure itself, we can't move the field y out, neither are we able to copy it, since mutable references aren't Copy.
We can trick the compiler into accepting the code by forcing the closure to be called as FnOnce instead of FnMut. This code works fine:
fn main() {
let x = String::new();
let mut y: u32 = 10;
let f = || {
drop(x);
&mut y
};
f();
}
Since we are consuming x inside the scope of the closure and x is not Copy, the compiler detects that the closure can only be FnOnce. Calling an FnOnce closure passes the closure itself by value, so we are allowed to move the mutable reference out.
Another more explicit way to force the closure to be FnOnce is to pass it to a generic function with a trait bound. This code works fine as well:
fn make_fn_once<'a, T, F: FnOnce() -> T>(f: F) -> F {
f
}
fn main() {
let mut y: u32 = 10;
let f = make_fn_once(|| {
&mut y
});
f();
}
There are two main things at play here:
Closures cannot return references to their environment
A mutable reference to a mutable reference can only use the lifetime of the outer reference (unlike with immutable references)
Closures returning references to environment
Closures cannot return any references with the lifetime of self (the closure object). Why is that? Every closure can be called as FnOnce, since that's the super-trait of FnMut which in turn is the super-trait of Fn. FnOnce has this method:
fn call_once(self, args: Args) -> Self::Output;
Note that self is passed by value. So since self is consumed (and now lives within the call_once function`) we cannot return references to it -- that would be equivalent to returning references to a local function variable.
In theory, the call_mut would allow to return references to self (since it receives &mut self). But since call_once, call_mut and call are all implemented with the same body, closures in general cannot return references to self (that is: to their captured environment).
Just to be sure: closures can capture references and return those! And they can capture by reference and return that reference. Those things are something different. It's just about what is stored in the closure type. If there is a reference stored within the type, it can be returned. But we can't return references to anything stored within the closure type.
Nested mutable references
Consider this function (note that the argument type implies 'inner: 'outer; 'outer being shorter than 'inner):
fn foo<'outer, 'inner>(x: &'outer mut &'inner mut i32) -> &'inner mut i32 {
*x
}
This won't compile. On the first glance, it seems like it should compile, since we're just peeling one layer of references. And it does work for immutable references! But mutable references are different here to preserve soundness.
It's OK to return &'outer mut i32, though. But it's impossible to get a direct reference with the longer (inner) lifetime.
Manually writing the closure
Let's try to hand code the closure you were trying to write:
let mut y = 10;
struct Foo<'a>(&'a mut i32);
impl<'a> Foo<'a> {
fn call<'s>(&'s mut self) -> &'??? mut i32 { self.0 }
}
let mut f = Foo(&mut y);
f.call();
What lifetime should the returned reference have?
It can't be 'a, because we basically have a &'s mut &'a mut i32. And as discussed above, in such a nested mutable reference situation, we can't extract the longer lifetime!
But it also can't be 's since that would mean the closure returns something with the lifetime of 'self ("borrowed from self"). And as discussed above, closures can't do that.
So the compiler can't generate the closure impls for us.
Consider this code:
fn main() {
let mut y: u32 = 10;
let ry = &mut y;
let f = || ry;
f();
}
It works because the compiler is able to infer ry's lifetime: the reference ry lives in the same scope of y.
Now, the equivalent version of your code:
fn main() {
let mut y: u32 = 10;
let f = || {
let ry = &mut y;
ry
};
f();
}
Now the compiler assigns to ry a lifetime associated to the scope of the closure body, not to the lifetime associated with the main body.
Also note that the immutable reference case works:
fn main() {
let mut y: u32 = 10;
let f = || {
let ry = &y;
ry
};
f();
}
This is because &T has copy semantics and &mut T has move semantics, see Copy/move semantics documentation of &T/&mut T types itself for more details.
The missing piece
The compiler throws an error related to a lifetime:
cannot infer an appropriate lifetime for borrow expression due to conflicting requirements
but as pointed out by Sven Marnach there is also a problem related to the error
cannot move out of borrowed content
But why doesn't the compiler throw this error?
The short answer is that the compiler first executes type checking and then borrow checking.
the long answer
A closure is made up of two pieces:
the state of the closure: a struct containing all the variables captured by the closure
the logic of the closure: an implementation of the FnOnce, FnMut or Fn trait
In this case the state of the closure is the mutable reference y and the logic is the body of the closure { &mut y } that simply returns a mutable reference.
When a reference is encountered, Rust controls two aspects:
the state: if the reference points to a valid memory slice, (i.e. the read-only part of lifetime validity);
the logic: if the memory slice is aliased, in other words if it is pointed from more than one reference simultaneously;
Note the move out from borrowed content is forbidden for avoiding memory aliasing.
The Rust compiler executes its job through several stages, here's a simplified workflow:
.rs input -> AST -> HIR -> HIR postprocessing -> MIR -> HIR postprocessing -> LLVM IR -> binary
The compiler reports a lifetime problem because it first executes the type checking phase in HIR postprocessing (which comprises lifetime analysis) and after that, if successful, executes borrow checking in the MIR postprocessing phase.

Resources