概要
- 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
を紹介しましたが、皆さんも楽しんでお使いください。