1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Rustでasyncとsyncに対応したCrateを作成する方法

Posted at

ハイサイ!オースティン やいびーん。

概要

Cargoのフィーチャーフラグ機能を使って、asyncとsync(非同期と同期)の両方に対応したRustライブラリーを作成する方法を紹介します。

この紹介でwp_query_rsのソースコードを使います。

フィーチャーフラグ

Cargoの機能にはクレートの一部のコードを、指定しない限りコンパイルに含めないフィーチャー機能があります。これによって不必要なコードをクレートの使用者にコンパイルさせずに済むのでありがたい機能です。

例えば、同wp_query_rsには、php_ext_rsを使ったトレート実装が含まれていますが、php_ext_rsを使うと、Cargo.tomlに細かい設定が必要な上、コンパイルするパソコンにPHPをインストールしないといけないので、wp_query_rsの使い勝手が非常に悪くなってしまいます。

そこでそのphp_ext_rsを使うコードは、指定するまで含めないようにすると、使用者にはありがたいはずです。

フィーチャーの設定方法

フィーチャーを追加するにはクレートのCargo.toml[features]の項目を記載した上、提供したいフィーチャーを追加します。

今回は、asyncとsyncのフィーチャーを追加します。asyncがRust言語の独占記号になっているのでquery_asyncquery_syncにします。

Cargo.toml
query_sync = []
query_async = []

配列には、このフィーチャーが必要とする他のクレートのライブラリーと、さらにこのクレートの他のフィーチャーを入れます。同クレートのフィーチャーに依存するフィーチャーもあります。

フィーチャーの依存クレートを追加する

今回は、query_syncにはmysqlquery_asyncにはmysql_asyncのクレートにそれぞれに依存しますので、--optionalフラグで追加します。optionalの設定を有効にすると、そのクレートを必要とするフィーチャーを有効にしない限り、クレートに含まれないようにできます。これによってコンパイル時間を大幅に減らせます。

cargo add --optional mysql_async mysql

これでmysqlmysql_asyncCargo.tomlに追加されますが、まだフィーチャーとの紐付けがない状態です。

Cargo.toml
[dependencies]
...
mysql = { version = "24.0.0", optional = true }
mysql_async = { version = "0.32.2", optional = true }

それぞれのフィーチャーとの依存関係を示すためには、[features]の配列にクレート名を追加します。

Cargo.toml
[features]
query_sync = ["mysql"]
query_async = ["mysql_async"]

これでquery_syncが有効の時には、mysqlquery_asyncが有効の時にはmysql_asyncが使われるように指定することができます。

cfgでフィーチャー別のメソッドを実装する

上記のmysqlmysql_asyncを使って別々の実装を追加していきたいのですが、それを可能にするのはcfgのマクロです。

まず最初にquery_asyncWpQueryのメソッドを実装します。

src/lib.rs
impl WpQuery {
...
    #[cfg(feature = "query_sync")]
    pub fn with_connection<'a, T>(
        conn: &mut impl Queryable,
        params: T,
    ) -> Result<Self, mysql::Error>
    where
        T: Into<Params<'a>>,
    {
        let posts: Vec<WpPost> = Self::query(conn, params)?;

        Ok(Self { posts })
    }

    #[cfg(feature = "query_sync")]
    fn query<'a, T>(conn: &mut impl Queryable, params: T) -> Result<Vec<WpPost>, mysql::Error>
    where
        T: Into<Params<'a>>,
    {
        let query_builder::QueryAndValues(q, values) = QueryBuilder::new(params.into()).query();

        let stmt = conn.prep(q)?;

        conn.exec(stmt, values)
    }
}

次に、同メソッド名query_async版のメソッドを実装します。

src/lib.rs
impl WpQuery {
... 
    #[cfg(feature = "query_async")]
    pub async fn with_connection<'a, T>(
        conn: &mut mysql_async::Conn,
        params: T,
    ) -> Result<Self, mysql_async::Error>
    where
        T: Into<Params<'a>>,
    {
        let posts = Self::query(conn, params).await?;

        Ok(Self { posts })
    }

    #[cfg(feature = "query_async")]
    async fn query<'a, T>(
        conn: &mut mysql_async::Conn,
        params: T,
    ) -> Result<Vec<WpPost>, mysql_async::Error>
    where
        T: Into<Params<'a>>,
    {
        let query_builder::QueryAndValues(q, values) = QueryBuilder::new(params.into()).query();

        let stmt = conn.prep(q).await?;

        conn.exec(stmt, values).await
    }
}

これらの定義は実際には隣同士に置かれていても大丈夫です。

    #[cfg(feature = "query_sync")]
    pub fn with_connection<'a, T>(
        conn: &mut impl Queryable,
        params: T,
    ) -> Result<Self, mysql::Error>
    where
        T: Into<Params<'a>>,
    {
        let posts: Vec<WpPost> = Self::query(conn, params)?;

        Ok(Self { posts })
    }
    #[cfg(feature = "query_async")]
    pub async fn with_connection<'a, T>(
        conn: &mut mysql_async::Conn,
        params: T,
    ) -> Result<Self, mysql_async::Error>
    where
        T: Into<Params<'a>>,
    {
        let posts = Self::query(conn, params).await?;

        Ok(Self { posts })
    }

こうすると、非同期のメソッドも、同期のメソッドも同じ名前とAPIで提供することができます!

cfgのanyで共通するロジックを含める

上記のメソッドが共通して必要とするコードがあれば、cfg(any(...))というマクロを使うと、フィーチャーのどれかが有効になっていれば、使われるように指定できます。

src/lib.rs
#[cfg(any(feature = "query_sync", feature = "query_async"))]
use query_builder::QueryBuilder;

query_syncquery_asyncQueryBuilderを参照しているので、上記のようにuseすれば、コンパイラーに文句を言われずに済みます。

まとめ

以上、Rustのライブラリでasyncとsyncのメソッドを同様なAPIとして実装する方法を紹介しました!

macro_rules!を活用して、全くのコピペーにならないように工夫できたらいいですね。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?