5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rust 1.75.0リリースでasync fnをtraitで書けるように

Posted at

概要

  • Rust 1.75.0がリリースされた
  • traitメソッドの戻り値をimpl Traitにできるようになった
  • それにより、async fnのメソッドもネイティブで書けるようになった

引用:https://blog.rust-lang.org/2023/12/28/Rust-1.75.0.html

async fnをtraitで書けるようになった

このリリースの目玉機能は、traitのメソッドに戻り値がimpl Hogeにできるようになったことです。

これまで長く待ち望まれた機能でしたが、実現するのにいくつか問題がありました。特に、dyn Traitの動的オブジェクトを使っている時のVtableにジェネリックが入ってしまうとどうすれば関数の呼ぶ場所を特定できるだろうか、これまでのRustだと不可能でした。

fn method_a() -> impl Hoge書けるようになって、さらにasync fn method_a() -> usize`のようなメソッドも書けるようになりました。

これは、async fn foo() -> usizeは、構文糖であり、実際にはfn foo() -> impl Future<Output = usize>で実際のステートエンジンがコンパイラによって生成されます。

なので、これがステーブルRustでできるようになったのはとても重要かつ重大な進歩であり、Async Rustにとって大きな勝利でもあります。Async Rustのコミュニティの長く掲げていた目標の一つでした。

アップデートして使ってみよう

まず、アップデートは以下のコマンドでできます:

rustup update stable

それから任意のプロジェクトで書いてみましょう。

筆者の場合は、MySQLのasync fnをずっと描きたかったのです。

trait Insert: Into<mysql_async::Params> + Send + Sync {
    async fn insert_stmt<T>(conn: &mut T) -> Result<mysql_async::Statement, mysql_async::Error>
    where
        T: mysql_async::prelude::Queryable,
        Self: Sized;

    async fn insert<T>(self, conn: &mut T) -> Result<u64, mysql_async::Error>
    where
        T: mysql_async::prelude::Queryable,
    {
        let stmt = Self::insert_stmt(&mut *conn).await?;

        conn.exec_drop(stmt, self).await?;

        let id = conn
            .exec_first("SELECT LAST_INSERT_ID();", ())
            .await?
            .unwrap();

        Ok(id)
    }

    async fn batch<T>(v: Vec<Self>, conn: &mut T) -> Result<(), mysql_async::Error>
    where
        T: mysql_async::prelude::Queryable,
        Self: Sized,
    {
        let stmt = Self::insert_stmt(&mut *conn).await?;

        conn.exec_batch(stmt, v.into_iter()).await
    }
}

これでINSERT INTO ....のステートメントだけ書けば、似たようなロジックを書かずにモデルがかけます!

pub struct List {
    pub list_id: u64,
    pub name: String,
    pub family_id: u64,
    pub items: Option<Vec<Item>>,
}

impl Into<Params> for List {
    fn into(self) -> Params {
        Params::Positional(vec![
            self.name.into(),
            self.family_id.into(),
            self.list_id.into(),
        ])
    }
}

impl Insert for List {
    async fn insert_stmt<T>(conn: &mut T) -> Result<mysql_async::Statement, mysql_async::Error>
    where
        T: mysql_async::prelude::Queryable,
        Self: Sized,
    {
        conn.prep("INSERT INTO lists (`name`, family_id, list_id) VALUES (?, ?, ?);")
            .await
    }
}

そうするとこのように使えます。

let mut conn: mysql_async::Conn = state.pool.get_conn().await.unwrap();
let list_id = list.insert(&mut conn).await.unwrap();

またもっと楽しいこともできます:

async fn insert_bulk_transaction<T>(conn: &mut Conn, v: Vec<T>) -> Result<(), mysql_async::Error>
where
    T: Insert,
{
    let mut t = conn.start_transaction(TxOpts::default()).await?;

    let stmt = match v.first() {
        Some(obj) => <T as Insert>::insert_stmt(&mut t).await?,
        None => return Ok(()),
    };

    t.exec_batch(stmt, v.into_iter()).await?;

    t.commit().await
}

まとめ

以上、Rust 1.75.0の目玉商品async trait fnを紹介しましたが、皆さんも楽しんでお使いください。

5
5
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
5
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?