ChatGPTに介護されながらRustでSQLiteを操作

ChatGPTにあれこれ質問しながらRustでSQLiteのデータベース作ってレコード作成→読み取りまでやってみた

まずは質問してみる

Q RustでSQLiteCRUD操作をしたい

A rusqliteを使って実装する

rusqliteとは

GitHub - rusqlite/rusqlite: Ergonomic bindings to SQLite for Rust

RustでSQLiteへ接続する時のあれこれをやってくれるクレート
ORMではなく単なるドライバ

Cargo.tomlのdependenciesに追加しておこう

[dependencies]
rusqlite = { version = "0.29.0" }

サンプルコード

ChatGPTが↓のようなコードを書いてくれた

use rusqlite::{params, Connection};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let conn = Connection::open("example.db")?;
    conn.execute(
        "CREATE TABLE IF NOT EXISTS person (
                  id              INTEGER PRIMARY KEY,
                  name            TEXT NOT NULL,
                  age             INTEGER NOT NULL
                  )",
        [],
    )?;

    let name = "Alice";
    let age = 42;
    conn.execute(
        "INSERT INTO person (name, age) VALUES (?1, ?2)",
        params![name, age],
    )?;

    let mut stmt = conn.prepare("SELECT name, age FROM person WHERE id = 1")?;
    let person: (String, i32) = stmt.query_row([], |row| Ok((row.get(0)?, row.get(1)?)))?;

    println!("Found person: {:?}", person);

    Ok(())
}

わからないところを聞く

↑のコードを読んだときに浮かんだのはdynって何だっけ?mutは再代入禁止?...という初歩的な疑問ばかりで全然読めなかった
なのでRustのドキュメント使って調べる、ではなくAIに質問(脳死)

Q Result<(), Box>のdynって何?

A dynはトレイトオブジェクトを宣言する識別子
トレイトオブジェクトはトレイトを実装したインスタンスを表す
トレイトはインターフェースのようにある構造体で実装すべきメソッド/フィールドを定義する

要するにOOPポリモーフィズムを実現するための機能で、今回の場合エラーの戻り値にstd:error:Errorと同じメソッドが実装されている何かしらのオブジェクトが来るということを表している(と理解した)

Q 関数の戻り値の後ろについてる"?"の意味は?

A Result型のハンドリングを簡便化するシンタックスシュガー

fn foo() -> Result<i32, String> {
    let x = bar()?; // bar()がErrを返す場合、foo()はそのErrを返す
    Ok(x)
}

bar()がOKを返す場合は変数xにその値が代入され、Errを返す場合はその場でfoo()もそのErrを返すようになる

Q 引数を|で囲い|row|ってしてるけど何?

A その引数がクロージャとして扱われることを表している

クロージャとは無名関数(jsでいうアロー関数とか)のことであり、クロージャのスコープ内の変数はキャプチャ(そのスコープ内で固定)される

fn main() {
    let x = 10;
    let y = 20;

    let add = || {
        x + y
    };

    println!("The result is {}", add());
}

例えばこのようなコードだと、変数addにx + yを返す無名関数が代入される
そして、xとyはキャプチャされているのでaddをいつどこで呼び出しても30が返ってくる

サンプルコードでは以下のようにstmt.query_row()の第二引数クロージャを使っている

let person: (String, i32) = stmt.query_row([], |row| Ok((row.get(0)?, row.get(1)?)))?;

大分わかりにくい形になっているが、やっていること自体はrowを引数にとりrowのインデックス0と1の値(=personテーブルのnameとage)をタプルとして返すだけの関数

実行する

cargo runで実行してみよう

※環境によっては= note: /usr/bin/ld: cannot find -lsqlite3: No such file or directoryというエラーが発生するかもしれない
これは以下のようにしてsqlite3のライブラリをインストールしてやればOK

sudo apt-get install libsqlite3-dev

実行してみてFound person: ("Alice", 42)と表示されれば成功
また、SQLiteなのでデータベースがファイル出力される(サンプルコードそのままだとexample.dbという名前)