Open6

Rustのtokioをコードリーディングする

nobnob

私は入門書を読み終えたレベルでサンプルプログラム以上のものは作ったことがない程度です
まだtokioと聞いてアイドルグループが頭に浮かぶレベルの者ですが、いろいろ学んでみたいと思います

↓以下、リポジトリなど
[repository] https://github.com/tokio-rs/tokio
[contributing] https://github.com/tokio-rs/tokio/blob/master/CONTRIBUTING.md
[readme] https://github.com/tokio-rs/tokio/blob/master/README.md
[docs] https://docs.rs/tokio/latest/tokio/

nobnob

まずは、実行して動作をみました
コードの上部コメントにある指示通り実行。(macの場合はncatncに変更)

> nc -l 6142

この時点では何も表示されません
新しいターミナルセッションを立ち上げて、次のコマンドを実行します

>  cargo run --example hello_world
    Finished dev [unoptimized + debuginfo] target(s) in 0.38s
     Running `/Users/sato-nobuaki/dev/roger-sato/sandbox/rust-study/tokio/target/debug/examples/hello_world`
created stream
wrote to stream; success=true

実行が終わると、ncを実行していたターミナルの方にhello worldが表示されました

>  nc -l 6142
hello world
nobnob

そもそもexampleオプションとはなんぞやとなって調べました
どうやら、exampleディレクトリにあるファイルを実行できるコマンドみたいでした(参考)
これは便利そう

nobnob

早速サンプルコードを見ていきます

#![warn(rust_2018_idioms)]

macro attributeでrustの古い文法に対して警告を出すようにしているみたいです
こんなことができるのですね

use tokio::io::AsyncWriteExt;
use tokio::net::TcpStream;

use std::error::Error;

コードで使用するライブラリを読み込んでいます

#[tokio::main]

main関数の前に置かれているこれが何かわかりません
とりあえず公式の説明

Marks async function to be executed by the selected runtime. This macro helps set up a Runtime without requiring the user to use Runtime or Builder directly.

"選択した非同期関数を指定することで、RuntimeやBuilderなしでRuntimeをセットアップできる"と書いてありますが、何を言っているのか依然としてわかりません
公式にRuntimeとBuilderの説明があったので読んでみます
まずは、Runtimeから

The Tokio runtime.
The runtime provides an I/O driver, task scheduler, timer, and blocking pool, necessary for running asynchronous tasks.
Instances of Runtime can be created using new, or Builder. However, most users will use the #[tokio::main] annotation on their entry point instead.

I/Oドライバ、タスクスケジュールなどのtokioを動かすための基盤となる機能を提供しているのですね
Builderやnewで作ることもできますが、#[tokio:main]でも使えると書いています
同時にBuilderのイメージも大体掴みましたが、念の為説明を読んでみます

Builds Tokio Runtime with custom configuration values.
Methods can be chained in order to set the configuration values. The Runtime is constructed by calling build.
New instances of Builder are obtained via Builder::new_multi_thread or Builder::new_current_thread.
See function level documentation for details on the various configuration settings.

Builderパターンを利用してRuntimeを作成できるということですね
ちなみにサンプルもありました

use tokio::runtime::Builder;

fn main() {
    // build runtime
    let runtime = Builder::new_multi_thread()
        .worker_threads(4)
        .thread_name("my-custom-name")
        .thread_stack_size(3 * 1024 * 1024)
        .build()
        .unwrap();

    // use runtime ...
}

Runtimeを作成すると聞くと難しそうなイメージですが、思ったよりもわかりやすいですね

nobnob

Runtimeの意味がわかったので、次は#[tokio::main]の実装を見ていこうと思います

    cfg_rt! {
        #[cfg(feature = "rt-multi-thread")]
        #[cfg(not(test))] // Work around for rust-lang/rust#62127
        #[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
        #[doc(inline)]
        pub use tokio_macros::main;

        #[cfg(feature = "rt-multi-thread")]
        #[cfg_attr(docsrs, doc(cfg(feature = "macros")))]
        #[doc(inline)]
        pub use tokio_macros::test;

        cfg_not_rt_multi_thread! {
            #[cfg(not(test))] // Work around for rust-lang/rust#62127
            #[doc(inline)]
            pub use tokio_macros::main_rt as main;

            #[doc(inline)]
            pub use tokio_macros::test_rt as test;
        }
    }

ここのコードのpub use tokio_macros::main;が読み出しもとっぽいですね

#[cfg(not(test))] // Work around for rust-lang/rust#62127

の部分に書いてあるコメントが気になったので、調べてみました
https://github.com/rust-lang/rust/issues/62127

どうやら、testコードでmainという識別子のproc macroを実行すると名前衝突が発生してしまうそうです
この回避策として、testをターゲットするときのコンパイルではここを読み込まないようにしてるんですね