🛞

axum の次のリリースの移行が大変そう

2023/05/22に公開

こんにちは,なっふぃです.

久しぶりに axumCHANGELOG.md を読んでみたら,今年の 4 月以降に #Unreleased セクションに追加された項目がかなり重そうでした.次のリリースのタイミングでは多くの修正作業が必要になると思われます.

この記事は,2023-04-30 のコミット #b663072504c7846556adcc0dea304c59f70c986a での最新の CHANGELOG.md に基づいています.

アイキャッチは axum というラテン語(英語:axle)から取りました.

大きな修正

私の理解した限りでは,Unreleased は

  • Body を表す型パラメータの削除
  • hyper v1.0 への追従

がメインとなっています.

前者はより user-friendly な設計を目指した修正ですが,現行バージョン (v0.6.x) からの移行が大変そうです.

後者は,現在開発中の hyper 1.0 が多くの breaking changes(後方互換性の無い変更)を含んでいるので,それらへの対応になります.
hyper 1.0 について詳しくは hyper 1.0 Roadmap を確認してください.

以下,主要な変更点を見ていきます.

Body を表す型パラメータの削除

#1751/#1778/#1789
(also related to hyper 1.0)

PR #1751 によると,主に次の理由で,型パラメータ B を削除しています:

  • B を変えるようなミドルウェア(例:DefaultBodyLimit)を使用するときに,使い方が難しい.
  • 新規ユーザの学習コストが高い.

元々,axum では多くの型やトレイトが B という型パラメータを持っていました.
たとえば Router<S, B>FromRequest<S, B>Next<B> などです.

この B は「リクエストの body の型」を表していて,

Request<B1> -> Request<B2> -> Request<B3> -> ...

のように変換されていくことが想定されていました.

実際に axum を使っている 1 ユーザとしては,これは非常に鬱陶しく,「B1 != B2 だからコンパイルが通らない」ということが何度もありました.
その度に

fn my_router() -> Router<Body> {
}

とすべきか(B に具体的な型 hyper::Body を入れる),

fn my_router<B: HttpBody + Send + 'static>() -> Router<B> {
}

とすべきか(B に関して generic),頭を悩ませています.
後者は B: HttpBody のような trait bound が必要になることもあり,複雑で冗長になりがちです.

それならいっそ B という型パラメータは削除して,B = axum::body::Body のみを許すことにしよう,という話です.

なお,これによってパフォーマンスが低下する恐れはあります.型 B1, B2, ... を最適化することができず,axum::body::Body へ変換する必要があるからです.
ただし,この影響は無視できると述べられています (#1751):

The performance impact should be negligible since Body prevents double boxing (https://github.com/tokio-rs/axum/blob/v0.7.0/axum-core/src/body.rs#L103)

文中のリンクはすでにマージ & 削除済のブランチ(PR #1583)を指しているので,リンク切れになっています.
おそらくこちら のことだと思います.

関連する変更

#1789

型パラメータ B の削除に伴い,PR #1789 でいくつかの変更が行われました.
こちらは機械的な書き換えで対応できるので,そこまで影響は無いと思われます.

Extractor やレスポンスで直接 body を扱っている場合には,ドキュメントをしっかり確認した上で直す必要がありそうです.
それ以外の場合は,コンパイラのエラーメッセージに従って修正するだけで足りるのではないでしょうか.

hyper 1.0 への対応

#1325/#1583/#1868

現在 hyper という crate は,バージョン 1.0 へ向けて開発が進められています.
hyper 1.0 の詳しい変更点は hyper 1.0 Roadmap で説明されていますが,今後の stability を見据えていくつかの機能が削除されます.

そのうち axum に影響を与えそうなものは,以下の変更です (#1325):

  • tower::Service の削除,
  • hyper::Server の削除,
  • hyper::Body の削除.

依存する crates のアップデート待ちなど,ブロッキングな作業もあるみたいです.(#1882)

tower::Service の削除

tower-service への依存をやめ,独自の hyper::Service へ移行されます.

新しい hyper::Service の機能は tower::Service と同じですが,poll_ready() メソッドを消して簡略化しています.

これにより axum の内部実装は影響を受ける一方,ユーザは引き続き tower のエコシステムを利用できるはずだと思っているのですが,どうなんでしょう.

hyper::Server の削除

hyper::Server は,将来の前方互換性のため hyper-util crate へ移動されます.

axum では,代わりに axum::serve() 関数を提供するようです.(#1868)

- axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
-     .serve(app.into_make_service())
-     .await
-     .unwrap();
+ let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
+ axum::serve(listener, app).await.unwrap();

tokio::net::TcpListener を使うには tokio/net feature が必要です.

hyper::Body の削除

私の理解する限りでは,Body を削除しなければいけない積極的な理由は分かりませんでした.
HTTP 3 以降で body の新しい実装が必要になったときのためでしょうか.
hyper::Bodyhyper::body::Empty などいくつかの型に分割されます.

axum では,「Body を表す型パラメータの削除」で述べたようなように,axum::body::Body へ移行します.そのため直接的な影響はありませんが,hyper::Body へ依存しているユーザは注意が必要です.

所感

機械的な変更で済む部分もありますが,body に関する修正はしっかり確認しないとコケそうです.
ただ,B パラメータに悩まされてきたのは事実なので,ここで削除する方向に舵を切るのは良いのかもしれません.

axum::serve() は今までより簡単にサーバを起動できるようになるので楽しみです.

全体的に「移行するのは苦痛だけど,一度対応すれば長い目で見て楽になる」タイプの変更が多い印象なので,頑張って対応しようと思います.

Discussion