Cargo dependencies

Feel free to skip this section, and just use features = ["full"] while learning. This section provides some examples of how and when to choose specific feature groups in Cargo.toml.

Often using all features is fine

When writing an app, we’ll often use all of Tokio’s features to accelerate our development time.

Cargo.toml:

[dependencies]
tokio = { version = "0.2", features = ["full"] }

For most apps, the additional compile time is hardly noticeable relative to the cognitive overhead of thinking about which features are needed.

Selecting features

When we develop protocol implementations that are used by client apps and servers, we want to limit the dependencies to decrease compile time – not just for us, but also for the users of our crates.

Let’s take a look at our tiny client app and see what features are needed.

tokio main

If we replace our src/main.rs with the following code (only adding the #[tokio::main] attribute to the default hello world generated by cargo new):

main.rs:

#[tokio::main]
async fn main() {
    println!("doing nothing yet");
}

Then we can choose whether to use threads (commonly used for servers):

tokio = { version = "0.2", features = ["macros", "rt-threaded"] }

or lightweight cooperative multi-tasking (often required for low-profile clients):

tokio = { version = "0.2", features = ["macros", "rt-core"] }

TcpStream connect

As we start to build the app, we’ll need more features. The line of code where we call TcpStream::connect requires two features:

# #![deny(deprecated)]
# #![allow(unused_mut)]
# #![allow(unused_variables)]
# use tokio::net::TcpStream;
# #[tokio::main]
# async fn main() {
    let mut stream = TcpStream::connect("127.0.0.1:6142").await.unwrap();
# }
  1. dns for converting the string 127.0.0.1:6142 to an IP address – Tokio will need to do a DNS lookup just in case.
  2. tcp for handling the TCP connection

Since our example uses a specific ip address, we can reduce dependendencies further by creating a std::net::SocketAddr:

# #![deny(deprecated)]
# #![allow(unused_mut)]
# #![allow(unused_variables)]
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use tokio::net::TcpStream;

#[tokio::main]
async fn main() {
  let addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 6142);
  let mut stream = TcpStream::connect(addr).await.unwrap();
}

Writing to the socket

The write method is defined in tokio::io::AsyncWriteExt which requires io-util.

# #![deny(deprecated)]
# #![allow(unused_variables)]
# use tokio::net::TcpStream;
# use tokio::prelude::*;
# #[tokio::main]
# async fn main() {
#    let mut stream = TcpStream::connect("127.0.0.1:6142").await.unwrap();
    let result = stream.write(b"hello world\n").await;
# }

The write method in our example is declared with use tokio::prelude::*. The tokio prelude follows the Rust prelude pattern to make using multiple types more convenient. We could be more explicit by declaring use tokio::io::AsyncWriteExt.

Learn more about features

The API reference indicates which features are needed for different APIs. If anything missing, please open an issue and one of the Tokio elves will figure out where it is declared and will fix it asap!