κeenのHappy Hacκing Blog | Lispエイリアンの狂想曲

Rustでstructのmutableなfieldあれこれ

κeenです。人々にRustを薦めておきながら本人は昨日ようやく入門しました。その時に困ったことをメモ。タイトルがルー語になってますが気にしない。

因みにこれはRust 1.0の情報です。

導入

Rustを知らない人のために説明すると、Rustの値はデフォルトでイミュータブルです。デフォルトで、というのはもちろんミュータブルにすることも出来ます。 標準ライブラリにも値がイミュータブルであることを要求するものもあります。 そしてミュータビリティはmutとして型にも現れます。厳密に同じかは知りませんがconstの逆、と思えばいいでしょう。

struct Point {
    x: isize,
    y: isize
}

fn double(p: &mut Point) {
    p.x = p.x * 2;
    p.y = p.y * 2;
}

fn main(){
    let mut p1 = Point{x: 1, y: 2};
    let p2 = Point{x: 1, y: 2};
    double(&mut p1);
    double(&mut p2); // error! p2 is immutable
}

イミュータビリティは継承します。親のstructがイミュータブルなら子もイミュータルになります。因みにフィールドにmutを指定することは出来ないようです。

use std::collections::HashMap;

struct IntHashMap {
    hash: HashMap<isize, isize>
}
fn main(){
    let h = IntHashMap{hash: HashMap::new()};
    h.hash.insert(1, 2);        // error! h.hash is immutable
}

最後に、少し本筋とずれますがtraitについて。他の言語でいうインターフェースのようなものです。今回これで困ったので。

例えばHTTPライブラリのhyperでは次のようなトレイトを実装しているstructをrequest handlerとして登録できます。

pub trait Handler: Sync + Send {
    fn handle<'a, 'k>(&'a self, Request<'a, 'k>, Response<'a, Fresh>);

    fn check_continue(&self, _: (&Method, &RequestUri, &Headers)) -> StatusCode { ... }
}

これを見て下さい。

fn handle<'a, 'k>(&'a self, Request<'a, 'k>, Response<'a, Fresh>);

selfmutがついてませんね。つまりhandlerはイミュータブルな値として渡されます。例えば先の例のようにフィールドにハッシュマップを持っていても更新出来ません。ちょっと困りますね。

Cell/RefCell

ということでフィールドにミュータビリティを入れるのが std::cell::{Cell, RefCell} です。この辺のブログを参考に。

#rustlang における構造体のmutabilityとCell/RefCell - snyk_s log

で、喜び勇んで使ったのですが次なるエラーが。

struct MyHandler {
    cache: RefCell<HashMap<String, Vec<u8>>>
}

impl Handler for MyHandler {
    fn handle<'a, 'k>(&'a self, Request<'a, 'k>, Response<'a, Fresh>){
        ....
    }
}
the trait `core::marker::Sync` is not implemented for the type `core::cell::UnsafeCell<...

どうも、hyperは複数スレッドでも動かせるのでハンドラにスレッドセーフであることが要求されるようです。そしてRustコンパイラはRefCellがスレッドセーフでない事を知っているのでコンパイルを弾きます。怖いですね。

Mutex

無理っぽいので最早別の手段を捜し始めます。

これはちょっと無理がありそうですね。

しかし、別の方法があるようでした。std::sync::Mutexです。

struct MyHandler {
    cache: Mutex<HashMap<String, Vec<u8>>>
}

こんな感じでlock().unwrap()するだけで使えます。

let mut cache = self.cache.lock().unwrap();

因みにロックの解除は不要です。Rustコンパイラは値の生存期間を知っているので値がこれ以上使われなくなった箇所にコンパイラがunlockを挟みます。(正確に言うとdrop(デストラクタ)が挿入され、dropがリソースの開放を行なう)

まとめ

  • rustのイミュータビリティは継承する
  • structのfieldに直接mutは指定出来ない
  • シングルスレッドでミュータブルなフィールドが欲しいならCell/RefCell
  • マルチスレッドならMutex
Written by κeen