もぐてっく

人は1つ歳をとるたび、1ビットづつ大きくなれると信じてた。

Rustで値を変更してない変数にmutを付けろと怒られてムッとした話

もぐの。齢(undefined)歳にして、Rustに入門しました

なんかLinux拡張機能とかドライバまで書けちゃうと言うことで最近異常な盛り上がりを見せているRust。
衝動的に興味がわいて、勢いで入門してみたもぐのです。

このRustさん。色々特徴はあるんですが特筆すべきはエラーメッセージ。

「こんな書き方させてごめんね。君はこんなコードを書きたかったんだよね?こう書くと良いよ(イケボ)」

って言うのを5行くらいかけて説明してくれるので比較的すらすらとソースが書けます。

そんなこんなで試しにPostgreSQLからデータを取ってくるプログラムを作っていたのですが、 その最中に少々納得のいかない事象に出くわしました。

ざっくり、以下のようなコードです。

use postgres::{Client, NoTls, Error};

fn main() -> Result<(), Error> {
    let client = Client::connect("postgresql://localhost/test", NoTls)?;
    let _rows = client.query("select name from sample", &[])?;

    Ok(())
}

出たエラーはこちら。

error[E0596]: cannot borrow `client` as mutable, as it is not declared as mutable
 --> src/main.rs:5:17
  |
4 |     let client = Client::connect("postgresql://localhost/test", NoTls)?;
  |         ------ help: consider changing this to be mutable: `mut client`
5 |     let _rows = client.query("select name from sample", &[])?;
  |                 ^^^^^^ cannot borrow as mutable

「変数clientがイミュータブルだ。お前が扱ってるPostgres::Clientのインスタンスはミュータブルだから変数に"mut"を付けろこのビ〇グソ野郎(汚い声)」

とのこと。

はぁ?いやいや。見る限り変数clientに入ってるインスタンスに書き込みなんてしてないですやん?
何を根拠にそんなこと言ってるんです???酷くないです??ちょっとー。

気になりだしたら止まらなくなったので、同じような使い方の構造体を用意して色々試してみることにしました。

検証開始!

その1:空っぽの構造体のインスタンスを代入

→成功。そらそうですね。

pub struct Moguno {
}

fn get_moguno() -> Moguno {
    Moguno {}
}

fn main() {
    let _moguno = get_moguno();
}

検証2:フィールドを定義した構造体のインスタンスを代入

→成功。これもそりゃそう。

pub struct Moguno {
    field :i32 ←←←←←←←←←←
}

fn get_moguno() -> Moguno {
    Moguno {field:1}
}

fn main() {
    let moguno = get_moguno();

    println!("{}", moguno.field);
}

検証3:フィールドに値を書き込むコードのある構造体のインスタンスを代入

→OK。へ~。

てっきり「クラス…ゲフンゲフン…構造体の中にフィールドへの代入処理があるかどうか?」が条件だと思ってたのでちょっと意外。

pub struct Moguno {
    field :i32
}

impl Moguno {
    pub fn cool(&mut self) {
        self.field = 100;       ←←←←←←←←←←
    }
}

fn get_moguno() -> Moguno {
    Moguno {field:1}
}

fn main() {
    let moguno = get_moguno();

    println!("{}", moguno.field);
}

検証4:フィールドに値を書き込むメソッドを持つ構造体のインスタンスを代入してから、そのメソッドを呼び出す

→再現。

おおー!ちゃんと書き込みメソッドを呼んでるかまでチェックしてるのか。すごい。

pub struct Moguno {
   field :i32
}

impl Moguno {
   pub fn cool(&mut self) {
       self.field = 100;
   }
}

fn get_moguno() -> Moguno {
   Moguno {field:1}
}

fn main() {
   let moguno = get_moguno();

   moguno.cool(); ←←←←←←←←←←←

   println!("{}", moguno.field);
}

検証5:フィールドに書き込むコードはないが、引数が&mut selfのメソッドを持つ構造体を代入してから、そのメソッドを呼び出す

→再現。

Ok(大体わかった)

pub struct Moguno {
    field :i32
}

impl Moguno {
    pub fn cool(&mut self) {
        // nop ←←←←←←←←←←
    }
}

fn get_moguno() -> Moguno {
    Moguno {field:1}
}

fn main() {
    let moguno = get_moguno();

    moguno.cool(); ←←←←←←←←←←

    println!("{}", moguno.field);
}

結論

つまりこういうことですね。

第一引数が&mut selfなメソッドを呼び出している変数にはmutが必要。

あーすっきりした。

まとめ!

  • 僕の知ってるC++とかで長年の課題だった「変数にconstを付けてもオブジェクトの中身は変更し放題だよね」問題が解決している

  • その解決策が「メソッドの第一引数に自分のオブジェクト取る言語あるやん?あれにmut付いてたらフィールド変更不可にできるんちゃう?」とめっちゃエレガント

  • Rustのこれはあくまで構造体なので?インスタンスを生成するnewメソッドは自分で作らないと存在しない。これで小一時間ハマったりするから注意だ!