use std::sync::mpsc;
use std::thread;
fn main()
{
let (tx, rx) = mpsc::channel();
thread::spawn(move || // What does the two pipe symbols '||' mean and what does 'move()' even do?
{
let val = String::from("Hello");
tx.send(val).unwrap(); // What does 'unwrap()' even mean?
});
let received = rx.recv().unwrap();
println!("{}", received);
}
So I am confused with a few things with this code. So inside the thread::spawn(move ||.. function, what does the move keyword even do and what does || even do inside the function?
For unwrap() in the link that you posted, it basically is used to return ok or not. So why is it when I comment it out the unwrap() in tx.send(val).unwrap(); I get an error?
|| is used to pass in arguments for closures which I understand, however why are we using || if we aren't passing in any arguments?
use std::sync::mpsc;
use std::thread;
fn main()
{
let (tx, rx) = mpsc::channel();
thread::spawn(move || // What does the two pipe symbols '||' mean and what does 'move()' even do?
{
let val = String::from("Hello");
tx.send(val).unwrap(); // What does 'unwrap()' even mean?
});
let received = rx.recv();//.unwrap();
println!("{}", received);
}
So this is my new code.
And this is the error:
error[E0277]: `std::result::Result<std::string::String, std::sync::mpsc::RecvError>` doesn't implement `std::fmt::Display`
--> src\main.rs:16:20
|
16 | println!("{}", received);
| ^^^^^^^^ `std::result::Result<std::string::String, std::sync::mpsc::RecvError>` cannot be formatted with the default formatter
|
= help: the trait `std::fmt::Display` is not implemented for `std::result::Result<std::string::String, std::sync::mpsc::RecvError>`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
= note: required by `std::fmt::Display::fmt`
= note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
error: aborting due to previous error
spawn() takes a function as a parameter. That is the function you want to run in it's own thread.
Normally we define functions with "fn someName (...) {...}" but in this case we don't need a name for the function and that "fn" just gets in the way.
So we just have an anonymous function body {...} to put in there as the parameter to spawn().
But we still need a way to indicate it is a function and give it parameters. That is done with "| |". Which sometimes might have parameters inside it.
That function, as well as possibly having parameters passed in "| |" may also need access to other variables in the scope of that spawn call. That is what "move" does. In your example that thread function needs access to the tx channel, so it needs to be moved in.
The unwrap() is there because tx.send() returns and error indication that Rust expects you to handle. If you don't want to handle it yourself then .unwrap() pulls the error code out of the return value and discards it. Unless there is actually and error, in which case there is a panic and your program dies.
Thing is rx.recv(); is not returning a simple value like a u32 or whatever. It returns and Result object. I think it is actually an enum. It can contain an error value if there is a failure of a String if success.
The compiler error message says that println! does not know how to print that complicated Result object.
That is why we have "unwrap()". It pulls the simple String value out of the result object (Unless there is actually an error, in which case it panics). println!() knows how to print strings.
First, not of all declared above, but of all used in closure. Second, without move closure will borrow by default (when it can) and move only if it needs to (i.e. if it have to move the value further), with move - only move.
When I remove the move keyword why do I get an error.
error[E0277]: `std::sync::mpsc::Sender<std::string::String>` cannot be shared between threads safely
--> src\main.rs:8:5
|
8 | thread::spawn(|| // What does the two pipe symbols '||' mean and what does 'move()' even do?
| ^^^^^^^^^^^^^ `std::sync::mpsc::Sender<std::string::String>` cannot be shared between threads safely
|
::: C:\Users\joe\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\src\libstd\thread\mod.rs:616:8
|
616 | F: Send + 'static,
| ---- required by this bound in `std::thread::spawn`
|
= help: the trait `std::marker::Sync` is not implemented for `std::sync::mpsc::Sender<std::string::String>`
= note: required because of the requirements on the impl of `std::marker::Send` for `&std::sync::mpsc::Sender<std::string::String>`
= note: required because it appears within the type `[closure@src\main.rs:8:19: 12:6 tx:&std::sync::mpsc::Sender<std::string::String>]`
error: aborting due to previous error
For more information about this error, try `rustc --explain E0277`.
I don't get why it can't be shared between threads safely?
Because authors of standard library decided so (note the !Sync impl). Why is this the case - well, maybe Sender holds some internal data and does not synchronize access to it, and so using it from multiple threads simultaneously would lead to data race.
Even if you use a type that can be shared between threads safely, the + 'static bound will prevent it from working without move, as the closure only has a reference to the channel, and the compiler cannot verify that this reference remains valid for the duration of the new thread, as it might outlive the thread it is spawned in.
There's nothing wrong with this conclusion per se, but you will find out that Rust also helps you reduce error-handling boilerplate by providing many useful functionalities — the ? operator, for example. Here's a demonstration using a Box<dynError> to capture all errors:
use std::error::Error;
use std::sync::mpsc;
use std::thread;
// convenience type alias
type Result<T, E = Box<dyn 'static + Error + Send + Sync>> = std::result::Result<T, E>;
fn main() -> Result<()> {
let (tx, rx) = mpsc::channel();
thread::spawn(move || -> Result<()> {
let string = "Hello".to_string();
tx.send(string)?;
Ok(())
});
let string = rx.recv()?;
println!("{}", string);
Ok(())
}
It kind of makes sense to me that a channel, like an mpsc, is intended to link to thread to another. One thread has the sender end of the channel the other thread has the receiver. There would be no point in having the sender or receiver channel ends owned by the same thread.
The whole point of these channels is to share data safely. In your example a string. Having the sender in one thread and the receiver in the other, exclusively is how channels implement that safe sharing.
Of course mpsc is a multi-producer system so you can have multiple sender ends. One for each producer thread. Perhaps one. I seem to remember that involves some cloning of the sender. Perhaps a single thread could own two senders. I don't know.
The way I see it when a function starts a thread that thread may well run for far longer than the function takes to execute. Potentially forever.
As such it makes no sense to loan out anything to the thread. The compiler has no way to know when the thread will terminate and the loan be paid back.
Imagine this:
Your function could spawn two threads and then terminate. When spawning it could give (move) a channel sender to one thread and a receiver to the other. Those threads could run forever.
What would it mean to only borrow those channels ends? There is nobody to give them back to if/when the threads terminate.
On a more concrete level, the owner of an object is responsible for allocating and cleaning up that object’s memory; in the case of a local variable, it’s stored on the stack. Any references to that variable, then, must be destroyed by the time the function returns. Otherwise, they’re referring to something that no longer exists.
If you want to make an object that lives beyond the end of the function that creates it, you have to transfer ownership of that object somewhere else. There are many ways to do this, such as:
You can transfer ownership to the calling function with return
You can store the object in a collection you have an &mut reference to
You can (sort of) give ownership to the operating system with Box::leak
You can send it to another thread with an mpsc::Sender
Regardless of the method you use, there can’t be any outstanding references to the object that’s being transferred because its location in memory might move in the process.