Rust Day 6: Tokio — Simple TCP Client
Today, I am trying out tokio, an async runtime for rust. I think it is similar to Promise in js or Future in Java.
Also, I haven’t started using Cargo much. I think this project would pave a good path to understanding the capability of the dependency manager.
Cargo Commands:
Crate vs mod:
From Rust Docs: A crate is a compilation unit in Rust. Whenever rustc some_file.rs
is called, some_file.rs
is treated as the crate file. If some_file.rs
has mod
declarations in it, then the contents of the module files would be inserted in places where mod
declarations in the crate file are found, before running the compiler over it. In other words, modules do not get compiled individually, only crates get compiled.
A crate can be compiled into a binary or into a library. By default, rustc
will produce a binary from a crate. This behavior can be overridden by passing the --crate-type
flag to lib
.
Cargo: Lib vs Bin:
From SO: A binary crate should generate an executable (or multiple) that can be installed in the user’s path and can be executed as usual.
The purpose of a library crate on the other hand is not to create executables but rather to provide functionality for other crates to depend on and use.
Examples in Cargo:
From Rust Docs: Files located under the examples
directory are example uses of the functionality provided by the library. When compiled, they are placed in the target/debug/examples
directory.
By default, examples are executable binaries (with a main()
function). You can specify the crate-type
field to make an example be compiled as a library:
[[example]]
name = "foo"
crate-type = ["staticlib"]
You can run individual executable examples with the cargo run
command with the --example <example-name>
option.
RUST Pounds (#) aka Attributes:
Document vs Comment:
Lints:
Idiom Lints:
- To declare
rust_2018_idioms
as awarning
, you can use try:
- To deny this idiom, use
- To allow, this idiom, use
Traits (Interfaces in Java)
- Here
Tweet
class implementsSummary
Interface. - We see how to invoke
summarize()
function
Dyn Keyword:
In java, it would be something like
MemoryHeap<Debug> x; // ignore MemoryHeap for now
Box:
From FasterThanLI
Without Box:
With Box:
Result:
Error:
A wrapper class similar to Exception in java.
Async Await:
Here, Tokio
is our executor.
Writing async code:
Await: We won’t run the code inside async (Future) until await is called. Can be thought of w.r.t Big Data Frameworks like Spark or Flink, where they don’t do execution of DAG, until the execute or print is invoked in the Driver.
? in RUST:
It is used to propagate error when it occurs. Think of it as more of like making a Runtime
exception (unchecked) in java, as a checked Exception
.
- I got a connection refused error with
?
- But without
?
, I went to the Stream Writer on the connection, which resulted in a fatal error
Netcat:
Netcat is a networking utility that reads and writes data across network connections, using the TCP/IP protocol.
We will be using it as a Server
nc -l 6142
RUST CODE:
Async Hello World Client: This is a program taken from tokio_examples.
- Cargo.toml
[package]
name = "tokio_sample"
version = "0.1.0"
edition = "2021"
[dependencies]
tokio = { version = "1.17.0", features = ["full"] }
[[example]]
name = "hello_world"
path = "src/examples/hello_world.rs"
Here you can see,
- tokio
dependency
added to the manifest. - adding an
example
(hello_world) section in the manifest.
- hello_world.rs
All the topics covered above can be seen in this simple hello_world code.
//!
is used for documentation.#![warn(rust_2018_idioms)]
Warn the user, when he uses 2018 rust syntax.#[tokio::main]
: the main function is not allowed to beasync
. We need to use the attribute to help the compiler understand that, it is supported by tokio.async
: To return Future and write async code..await
: Suspend execution until the result of a Future is ready.?
: Use to throw the runtime exception.let mut stream = TcpStream::connect(“127.0.0.1:6142”).await?;
Here we are opening a new TCP Stream. We await until that stream is opened. If there is an error in connecting, then we throw that error using ?
.
TcpStream::connect(“ip4:port#”) is provided by tokio_net package.
NOTE: In mac, we are running
nc -l 6142
to open a listening server. Our code above is more like a client connecting to that server.
8. let result = stream.write(b”hello world\n”).await;
Here we are writing bytes to the stream. We are able to write, by importing
use tokio::io::AsyncWriteExt;
Here we are not throwing the error to the main. Probably because we anyways reached almost the end.
9. {:?}
is used to debug print.
10. result.is_ok()
will return a boolean, if written or not.
11. Ok(())
is used to signify a Positive response, with an empty body.
Conclusion:
This was a lengthy one, as I was kind of introducing every simple concept of async rust programming. In the next article, we will see how we can replace Netcat, with our own server, Rc vs Arc, and things like that. Until then, have a great day!
Found it Interesting?
Please show your support by 👏.