【Rust】Tauriで簡易的なアプリを作ってみる
こんにちは、しきゆらです。
ここ数回投稿しているTauriを使ったサンプルを写経しながら簡単なアプリを作ってみました。
今回は、その中で詰まった部分と解消方法を合わせてメモしておきます。
なお、写経元はこちら。
ここ最近のTauri記事はこの辺を参照。
【Rust】Tauriを使ってみる
【Rust】Tauriでフロントからバックエンドの処理を呼び出す
手元で環境を作ったときにインストールされたバージョンは以下の通り。
[package]
name = "kanban"
version = "0.0.0"
description = "A Tauri App"
authors = ["you"]
license = ""
repository = ""
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies]
tauri-build = { version = "1.4", features = [] }
[dependencies]
tauri = { version = "1.4", features = ["shell-open"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
sqlx = { version = "0.7.1", features = ["runtime-tokio-rustls", "sqlite", "migrate"] }
tokio = { version = "1.30.0", features = ["full"] }
futures = "0.3.28"
directories = "5.0.1"
[features]
# this feature is used for production builds or when `devPath` points to the filesystem
# DO NOT REMOVE!!
custom-protocol = ["tauri/custom-protocol"]
ただ、一点sqlx
パッケージのバージョンアップで破壊的変更が入った影響で動かない部分があったので、その辺を調べつつ解消していきます。
躓いたのは「SQLiteにデータを保存する」の以下の部分。
この中で、以下のコードのexecute
の部分でエラーが出た。
sqlx::query("INSERT INTO cards (id, title, description) VALUES (?, ?, ?)")
.bind(card.id)
.bind(card.title)
.bind(card.description)
.execute(&mut tx) // ここでエラー
.await?;
Githubにある写経元のパッケージを見ると、sqlxのバージョンが0.6.1でした。
最初に書いた通り手元では0.7.1なので、0.6~0.7の間に破壊的な変更があったとみてリリースノートを見てみるとありました。
Rustのことはよくわかりませんが、ExecutorをTransactionから削除されていました。
変更方法も記載されており、&mut transaction
-> &mut *transaction
とするだけとのことで、以下のように修正して事なきを得ました。
sqlx::query("INSERT INTO cards (id, title, description) VALUES (?, ?, ?)")
.bind(card.id)
.bind(card.title)
.bind(card.description)
.execute(&mut *tx) // <= ここを変更
.await?;
これに伴い、txを渡している部分が軒並みおかしくなりました。
Rustはよくわかっていないですが、参照が悪いと踏んでその辺のRustのリファレンスを見てみました。
この中で、&
を付けるとその値ではなく、値があるメモリの場所を渡すことができ、これを参照という。
参照から値を取得するには*
を付けることで可能、とのこと。
次に、&mut *tx
は何を表しているか、を知る必要があります。 sqlx
の実装を簡単に追ってみます。
まずは、txは以下のように宣言しており、コメントにも「トランザクションを開始」とあるのでトランザクションとして追ってみます。
pub(crate) async fn insert_card(pool: &SqlitePool, card: Card, pos: CardPos) -> DbResult<()> {
// トランザクションを開始
let mut tx = pool.begin().await.unwrap(); // <= txの宣言
sqlx::query("INSERT INTO cards (id, title, description) VALUES (?, ?, ?)")
.bind(card.id)
.bind(card.title)
.bind(card.description)
.execute(&mut *tx) // <= 変更箇所
.await?;
// column_cardsテーブルにカードの位置を表す情報を挿入する
insert_card_potition(&mut tx, pos.column_id, card.id, pos.position).await?;
// トランザクションをコミット
tx.commit().await?;
Ok(())
}
トランザクションの実装はこうなっていました。
impl<'c, DB> Deref for Transaction<'c, DB>
where
DB: Database,
{
type Target = DB::Connection;
#[inline]
fn deref(&self) -> &Self::Target {
&self.connection
}
}
impl<'c, DB> DerefMut for Transaction<'c, DB>
where
DB: Database,
{
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.connection
}
}
この中にfn deref
があり、ここでTransaction
の中にあるconnection
を返しているので、&mut *tx
はconnection
の参照を指していることになります。
では、connection
の参照を渡すように変更しましょう。
例えば、insert_card_potition
の場合。
// columns_cardsにカードの位置情報を挿入する
async fn insert_card_potition(
tx: &mut Transaction<'_, Sqlite>,
column_id: i64,
card_id: i64,
position: i64
) -> DbResult<()> {
update_card_positions(&mut *tx, column_id, position,|pos| pos + 1).await?;
sqlx::query("INSERT INTO columns_cards (column_id, card_id, position) VALUES (?, ?, ?)")
.bind(column_id)
.bind(card_id)
.bind(position)
.execute(&mut *tx) // ここがエラーになる
.await?;
Ok(())
}
ここでもinsert_card
と同じくSQLのクエリを実行しているところ。 したがってexecute
が求めているのはConnection
になります。
一方で、この関数が受け取っているのはTransaction
の参照になります。 必要なのはTransaction
の中にあるconnection
なので一段階ラップに包まれている状態。
なので、insert_card
よりもう1つ参照を外してあげればよいわけです。 同じ変数名なのでわかりにくいので書き換えてみます。insert_card_potition
のtx
はTransaction
の参照なのでtx_p
とでもしてみます。
async fn insert_card_potition(
tx_p: &mut Transaction<'_, Sqlite>,
column_id: i64,
card_id: i64,
position: i64
) -> DbResult<()> {
update_card_positions(&mut *tx_p, column_id, position,|pos| pos + 1).await?;
sqlx::query("INSERT INTO columns_cards (column_id, card_id, position) VALUES (?, ?, ?)")
.bind(column_id)
.bind(card_id)
.bind(position)
.execute(&mut **tx_p) // <= 参照を2重で外す
.await?;
Ok(())
}
insert_card_potision
の引数tx_p
はTransaction
への参照なので、まずは*tx_p
でTransaction
の参照からTransaction
自体を取得。
その後、Transaction
の参照外しからConnection
を取得する形です。Transaction
の参照 ⇒ *
をつけて Transaction
自体⇒ さらに*を付けてConnection
、の順番でほしい値を取得すればよいですね。
以前もRustの所有権をふ~ん、と読み飛ばしていたので ここで改めて調べたり考えるきっかけになったので良かったといえばよかったです。
あとは、SQLのタイポがないかを確認しながら写経すれば動くはずです。
まとめ
今回は、Tauriを使ったサンプルプログラムを動かす過程でライブラリの破壊的変更に当たったので
原因を追いつつ対応してみました。
Rustの所有権よくわからんなぁと思っていましたが、今回のコードを追う中で苦手意識は少し減りました。
今回は、ここまで。
おわり
ディスカッション
コメント一覧
まだ、コメントがありません