読者です 読者をやめる 読者になる 読者になる

右上➚

プログラミングに関するメモをのこしていきます

Rust でグラフ構造や木構造を作る

プログラムを書いていると何かしら木構造っぽいものやグラフっぽいものを作りたい場面が多々あると思います. Rust は所有権や Size の都合で,これらを作ろうと思うと地味にハマるのでまとめておきます.

Rust で木構造

最も単純な木構造は Rust だと

enum Tree<T> {
    Leaf(T),
    Node(Box<Tree<T>>, Box<Tree<T>>),
}

といった形で表せます. Rust では明示的に boxing してあげないと再帰的なデータ構造は作れないのでちょっと複雑に見えるかもしれませんが,まぁ単純です.

この木構造を書き換えたい場合は,ownership をとって書き換えた値を返すこともできますし,&mut Tree<T> をとって in-place に書き換えることもできます.

fn inc(&mut self) {
    match *self {
        Tree::Leaf(ref mut i) => *i = *i + 1,
        Tree::Node(ref mut left, ref mut right) => {
            left.inc();
            right.inc();
        }
    }
}

fn inc2(self) -> Tree<i32> {
    match self {
        Tree::Leaf(i) => Tree::Leaf(i + 1),
        Tree::Node(left, right) => Tree::Node(Box::new(left.inc2()), Box::new(right.inc2())),
    }
}

Rust で有向非巡回グラフ

有向非巡回グラフ構造は,グラフのエッジに向きがあり,かつ循環がない構造で,これは割りと単純に表現できます.

struct Node<T>(T, Vec<Rc<Node<T>>>);

Node は自身の値 T と,つながっているノードの Vec を持ちます.(対象問題によっては Vec ではなくて HashMap とか HashSet とか)

Rc は参照カウント方式のスマートポインタです. グラフでは,Node は複数の Node から参照される可能性があるので, Box は使えません.

これを変更可能にしたい場合はちょっと面倒ですが RefCell を使う必要があります.

Rust では基本的に mutable borrow は常にひとつしか存在できず,mutable borrow が生きている間は immutable borrow もつくることができません. Rc から mutable な参照を取り出すこともできません.

そこで RefCell を使うことで borrow check をランタイムに行うようにします. RefCell は immutable な参照から mutable な参照を取り出せるようにする働きをしますが, mutable な参照を取っている間に,さらに mutable な参照を作ろうとしたり immutable な参照を作ろうとすると,ランタイムに panic します. コンパイル時検査ではなくランタイム検査になるので,プログラマの責任できちんと管理しないと死にます.

RefCell 版がこちら

struct Node<T>(T, Vec<Rc<RefCell<Node<T>>>>);

impl Node<i32> {
    fn inc(&mut self) {
        self.0 += 1;
        for n in &self.1 {
            n.borrow_mut().inc();
        }
    }
}

Rust で巡回有向グラフ

循環がある場合は厄介です. Rust で参照を共有するための Rc は参照カウントなので,循環参照があるとリークします. したがって循環のある構造を表すために Rc は使えません.

木構造で親を参照するポインタを子に持たせておきたいといったケースでは,親は子を Rc で持ち,子は親を Weak で持つという形で対応できますが, グラフだとそういうわけにもいきません.

そこで出て来るのが Arena という方法です.

オブジェクトの実体は Arena の中に作り,グラフにはその ID を持たせて管理します.

type NodeId = usize;

struct NodeArena<T> {
    arena: Vec<Node<T>>,
}

impl<T> NodeArena {
    fn alloc(&mut self, value: T) -> NodeId {
        let id = self.arena.len();
        let node = Node(id, value, Vec::new());
        self.arena.push(node);
        id
    }
    fn get(&self, id: NodeId) -> &Node<T> {
        &self.arena[id]
    }
    fn get_mut(&mut self, id: NodeId) -> &mut Node<T> {
        &mut self.arena[id]
    }
}

struct Node<T>(NodeId, T, Vec<NodeId>);

こんな感じです.

こうすることでコンパイル時の borrow check を諦めることなくグラフ構造を作ることができます.
(ii+1 番目のノードを同時に mutable に参照したいとかは苦しいですが)

欠点としては単純に間接的な表現でめんどくさいというのもありますが,GC がないので参照されなくなったオブジェクトも Arena 上で生き続けてしまうことです. そのため,動的に要素が生きたり死んだりするケースには使いにくいです.

ノードグラフの構築が終わったら Arena をリフレッシュするみたいなことをすると良いのかもしれません. (構築中は VecArena を表現して,構造が固まったら HashMap を使った Arena に切り替えて参照されているidだけを残すみたいな)
それでも構築中はオブジェクトがあまりまくるので辛いですね...

mio で echo サーバメモ

Rust の非同期 IO ライブラリのなかでももっとも低レベルなレイヤーを担っている mio を使ってecho サーバを書いた。 echo サーバばっかり書いているような気がするけど,echo サーバやっておくと簡単な割にライブラリの使い方とかがちゃんと分かる気がするので好きです。

コード

extern crate mio;

use std::io::prelude::*;
use std::collections::HashMap;

use mio::*;
use mio::tcp::{TcpListener, TcpStream};

#[derive(Debug)]
struct ClientsHolder {
    table: HashMap<Token, TcpStream>,
    free_token: Vec<Token>,
    next_max_token: Token,
}

impl ClientsHolder {
    fn new_from(start_token: Token) -> Self {
        ClientsHolder {
            table: HashMap::new(),
            free_token: Vec::new(),
            next_max_token: start_token,
        }
    }

    fn next_token(&mut self) -> Token {
        if let Some(tok) = self.free_token.pop() {
            return tok;
        }
        let tok = self.next_max_token;
        self.next_max_token = Token(tok.0 + 1);
        tok
    }

    fn register(&mut self, tok: Token, client: TcpStream) {
        self.table.insert(tok, client);
    }

    fn get_mut(&mut self, tok: Token) -> Option<&mut TcpStream> {
        self.table.get_mut(&tok)
    }

    fn remove(&mut self, tok: Token) -> Option<TcpStream> {
        let result = self.table.remove(&tok);
        if result.is_some() {
            self.free_token.push(tok);
        }
        result
    }
}

// Setup some tokens to allow us to identify which event is
// for which socket.
const SERVER: Token = Token(0);

fn main() {

    let addr = "127.0.0.1:13265".parse().unwrap();

    // Setup the server socket
    let server = TcpListener::bind(&addr).unwrap();

    // Create an poll instance
    let poll = Poll::new().unwrap();

    // Start listening for incoming connections
    poll.register(&server, SERVER, Ready::readable(), PollOpt::edge())
        .unwrap();

    // Create storage for events
    let mut events = Events::with_capacity(1024);
    let mut clients = ClientsHolder::new_from(Token(1));

    loop {
        poll.poll(&mut events, None).unwrap();

        for event in events.iter() {
            match event.token() {
                SERVER => {
                    // Accept and drop the socket immediately, this will close
                    // the socket and notify the client of the EOF.
                    let (stream, _) = server.accept().unwrap();
                    let tok = clients.next_token();
                    poll.register(&stream, tok, Ready::readable(), PollOpt::edge()).unwrap();
                    clients.register(tok, stream);
                }
                tok => {
                    let mut close = false;
                    if let Some(mut stream) = clients.get_mut(tok) {
                        let mut buf = [0; 1024];
                        let n = stream.read(&mut buf).unwrap();
                        if n == 0 {
                            poll.deregister(stream).unwrap();
                            close = true;
                        } else {
                            stream.write(&buf[0..n]).unwrap();
                        }
                    }
                    if close {
                        clients.remove(tok);
                    }
                }
            }
        }
    }
}

面倒だったので unwrap まみれですが。

やったこと

mio の全体の流れとしては,Poll 型の値がイベントを監視する役割を担います. Poll に監視対象を登録していき,Poll::poll でイベントの発火を待ちます. 発火したイベント一覧が Events 型の Events::iter で取れるので,対応していけばよいです.

mio では Token という型の値を使って監視対象を識別しています. 監視対象には TcpListenerTcpStreamSender,などなどいろんなものがあるので,統一的に扱うために PollToken だけを保持します. Token と監視対象の紐付けはユーザ側の責任でやってくれという感じみたいです.

echo サーバではクライアントの数は不特定なので,「空いている Token を探す」と「Token に対応するクライアント (TcpStream) を探す」がうまくできる必要があります. そこで,ClientsHolder を定義しました. こいつが,空いている Token を返すのと Token をキーに TcpStream を返す仕事をします. remove されたらその Token は再利用します.

気になるところ

mio はファイルに対する抽象化は提供しない方針のようです. STDIN / STDOUT も同様です.

ファイル IO もノンブロッキングにしたい場合はどうしたらいいんでしょう?よくわかっていない. mio::unix 以下に UNIX 限定の拡張がおいてあって,EventedFd という file descriptor を扱う実装はあるので,UNIX 限定なら力技でなんとかなるのかもしれない.

あと mio は関係ないんですが,実装の部分で,

let mut close = false;
if let Some(mut stream) = clients.get_mut(tok) {
    let mut buf = [0; 1024];
    let n = stream.read(&mut buf).unwrap();
    if n == 0 {
        poll.deregister(stream).unwrap();
        close = true;
    } else {
        stream.write(&buf[0..n]).unwrap();
    }
}
if close {
    clients.remove(tok);
}

というのがあるんですが,これどうやったらスマートなんでしょう. close = true としている部分で clients.remove(tok); をやるのが普通だと思うんですが,if let Some(mut stream) = clients.get_mut(tok) { のところで clients は borrow されているから mutable borrow はこれ以上作れないのです.

NCurses の Crystal binding を作った

この記事は、 Crystal Advent Calendar 2016 の8日目の記事です。 qiita.com

ncurses という CUI を作るためにスクリーンやキー入力を扱う有名なライブラリの Crystal binding を作りました。

github.com

ほとんど C の ncurses と同じ感じで使えるようになっていると思います. RubyPythoncurses ライブラリを参考にしつつ,なるべく使用感がかわらないようにしています.

examples

require “ncurses”

NCurses.open do
  NCurses.cbreak
  NCurses.noecho
  NCurses.move(x: 30, y: 20)
  NCurses.addstr(“Hello, world!”)
  NCurses.refresh

  NCurses.notimeout(true)
  NCurses.getch
end

NCurses.addstr とか NCurses.move とかは ncurses で言う addstrmove に当たる関数で,stdscr に対して waddstr とか wmove するやつです.

w = NCurses::Window.new(height: height, width: width) とすることで subwindow が作れます. w.addstrw.move という形で w prefix な関数たちが呼べるようになっています.

padattron / attroff などなども使えます.

詳細な例は example/ 以下のにおいてあります

なぜ作ったのか

実は ncurses bindings for Crystal はすでにあります.(https://github.com/jreinert/ncurses-crystal)
curses が Crystal の標準ライブラリから外されることが決まったときの CHANGELOG を見ると,今後はそっちを使ってねと書いてあったりします.

じゃあなんでわざわざ別で作ったのかという話ですが,API が足りない & 提供する API の形を変えたいと思ったからです.

単純に bind されている API が少なくてやりたいことができなかったので,最初は追加して PR を出そうと思っていたのですが,すでに提供されている API が割と高級になっていて 1 : 1 で C API と対応していない感じでした. 個人的には C library の wrapper にはなるべく薄くなっていてもらって基本的な API は覚え直さないでも使えるようになっていてほしいというふうに思ったので,C API と 1 : 1 で対応した形の API を提供する wrapper を作ってみようという経緯で新しく作ることにしました.

おまけ

C bindings を書くときに,wrapper API として LibC::Int が登場しちゃうのがなんとなく嫌で,LibC::Int を C が要求してくる関数を呼ぶ wrapper 関数には型指定をあえてしないという選択をしたんですがどうなんでしょう.

lib LibNCurses
  fun wmove(w : Window, y : LibC::Int, x : LibC::Int) : Result
end
class Window
  def move(y, x)
     LibNCurses.wmove(@win, y, x)
  end
end

みたいな感じです.(多少簡略化しています)

これどうなんですかね.なるべく外に見せる API には型を明記するようにしたかったのですが,LibC:Int 系は環境によって異なるのでそういうわけにも行かず…

y : LibC::Int, x : LibC::Int とかは別に良いんですが,ncurses は文字と属性をくっつけた chtype なる型を持っていてこれが結構厄介というか混乱を招くのではと思っています. chtypechar ではなく unsigned int で,文字と属性を bitor でくっつけたものになっています.addch のように char をとることを連想させる関数の引数が実は chtype = unsigned int でしかも Crystal の文字型 Char は 32bit なのでものすごく混乱します…

C は型変換を勝手にやってくれるので,unsigned int を返す関数から受け取った値を short を受け取る関数に渡すみたいなことをよくやっていて,Crystal のような型変換を暗黙にやらない言語から使おうとすると難しいんだなぁと思いました. なにか良い方法があればぜひ知りたいです.

Rust で Box に包まれた構造体の所有権分解

ちょっとはまったのでメモ

struct A {
    foo: Vec<i32>,
    bar: Vec<bool>,
}

こんな構造体があったとする。 普通、A の所有権を分解して foobar にしたいときは

fn xxx(x: A) -> (Vec<i32>, Vec<bool>) {
    let A { foo, bar } = x;
    (foo, bar)
}

とやれば良い(この例だともっと簡単に書ける気もするけど)

一方、Box<A> から foobar に分解したい場合は話が変わってくる。

fn error1(x: Box<A>) -> (Vec<i32>, Vec<bool>) {
    (x.foo, x.bar)
}

fn error2(x: Box<A>) -> (Vec<i32>, Vec<bool>) {
    let A { foo, bar } = *x;
    (foo, bar)
}

これらは両方共コンパイルできない。 人間から見ると,Box<A>A の所有権を持っているのだから、A -> foo/bar に分解できるなら Box<A> も同様にできる気がする。

実際にはこのようにするとコンパイルが通る。

fn success(x: Box<A>) -> (Vec<i32>, Vec<bool>) {
    let x = *x;
    let A { foo, bar } = x;
    (foo, bar)
}

うーん、エラーになるケースだと Deref トレイトの機能を経由している感じになるのかな? Deref 経由で foo の所有権をとるとその時点で Box<A> の所有権は奪われちゃうから bar の所有権が取れないということなのだと想像した。 success のようなコードが突然出てきたら混乱しそうだ。

日本語の改行を適当にいい感じにするツールを作りました

必要に迫られて、HTML ページ内の改行位置をいい感じにするツールを作ってみました。

github.com

HTMLに長文を書くと、親 DOM のサイズの制約上、適宜改行がぶちこまれます。 しかし、改行位置は文節を考慮などせずにごりっと挿入されるので、多くの問題が生じることが報告されています。 最も有名な問題として、今すぐダウンロー
ド問題が挙げられます。

japawrap を使うと、それっぽく日本語を解釈して <span> でくくるみたいなことができます。inline-block を適用すれば改行がそれっぽく入るようにできます。

Install

go get github.com/agatan/japawrap/...

japawrap コマンドが使えるようになります。

Usage

CLI

ファイル名を指定するか標準入力から流し込みます。

$ echo "今日も元気です" | japawrap
<span class="wordwrap">今日も</span><span class="wordwrap">元気です</span>

このように適宜いい感じに wrap してくれます。

オプションとして -open string-close string をサポートしているので、

$ echo "今日も元気です" | japawrap -open '<span style="display: inline-block;">' -close "</span>" 
<span style="display: inline-block;">今日も</span><span style="display: inline-block;">元気です</span>

みたいなことができます。

Library

一応 japawrap はライブラリとしても使用できるようになっています。

w := japawrap.New(open, close)
s := "今日も元気です"
fmt.Println("%s => %s", s, w.Do(s))

これだけです。

Example

では実際に使った結果を下に記したいと思います。 文章は、上の方に自分で書いた文章をそのまま使います。


before

HTMLに長文を書くと、親DOM のサイズの制約上、適宜改行がぶちこまれます。 しかし、改行位置は文節を考慮などせずにごりっと挿入されるので、多くの問題が生じることが報告されています。 最も有名な問題として、今すぐダウンロード問題が挙げられます。

after

HTMLに長文を書くと、DOM のサイズの制約上、適宜改行がぶちこまれます。 しかし、改行位置は文節を考慮などせずにごりっと挿入されるので、多くの問題が生じることが報告されています。 最も有名な問題として、今すぐダウンロード問題が挙げられます。


こんな感じになります。猛烈に効果がわかりにくくて驚いていますが、一応効果はちゃんと出ているのではないでしょうか? HTMLを直接見ていただければどうなっているかはわかると思います。

次に青空文庫から夏目漱石「こころ」の序文を抜粋してみたのが下の画像たちです。

f:id:agtn:20161029131814p:plainf:id:agtn:20161029131817p:plainf:id:agtn:20161029131820p:plain

レスポンシブ!それなりになっている気がします。

あとがき

この問題へのアプローチとして、https://github.com/google/budou が有名だと思います。

budou は Cloud Natural Language API を内部で使っていて、しっかり日本語の文章を構文解析しているようです。 なので非常に精度は高いと思うのですが、今僕の GCP アカウントがごにょごにょしていてぱぱっと試せる状況ではなかったので自作しました。

budou と違ってしっかり構文解析などはしていなくて、形態素解析した後、それっぽく分割しているだけです。なので精度は落ちると思います。 一方、budouGCP の credentails が必要だったりと準備が必要になるので、お手軽に試せるというのは悪くないかなと思っています。

Rust の Result と Iterator

Rust には失敗するかもしれない値を表す Result<T, E> という型があります。 std::result::Result

そして iterate できることを表す Iterator という trait があります。 std::iter::Iterator

また、Iterator trait は要素型を表す関連型を持ちます。例えば StringIterator<Item=char>impl しています。これは char 型を要素にもつ Iterator であることを意味します。 ここ間違っていました。String が直接 Iteratorimpl しているのではありませんでした。

たまに Iterator<Item=Result<T, E>> のようになっている型を見かけます(T, E にはなにかしら具体的な型が入っていると思ってください)。 例えば、std::io::stdin().bytes() の返り値である std::io::BytesIterator<Item=Result<u8>>impl しています。 (ちょっとわかりにくいのですがここでの Resultstd::result::Result ではなくて std::io::Result です。std::io::Resultstd::result::Result<T, std::io::Error>エイリアスです。)

さて、このような Iterator からすべての要素が Ok(_) であれば Ok<Vec_>> を、Err(_) があれば Err<_> を返すような処理を書きたいということは割りとよくあります。 で、これを一生懸命実装しようとしていたのですが、標準ライブラリの範囲内ですでに実装されていました。べんり。

let result = std::io::stdin().bytes().collect::<Result<Vec<_>, _>>();

これだけです。これで要件を満たす Result<Vec<_>, _> が返って来ます。すばらしい。

タネは簡単な話で ResultFromIterator trait を impl しているので collect で変換が可能であるというお話でした。 std::iter::FromIterator

値と参照について

「値」と「参照」という言葉があります。 このへんの言葉について、今の理解をまとめておこうと思います。 言葉の定義や理解が誤っている部分があればご指摘ください。

まず、前提として以下では、「値」ベースの言語として C, C++, Rust などを、「参照」ベースの言語として Java, C#, Ruby などを想定しています。 (もちろん言語によってはハイブリッドなものもあります: Crystal, Go, D, ...)

そもそも「値」「参照」とは

「値」は「実体」、「参照」は「実体へのポインタ」です。

int の配列みたいなものを考えてみます。 [1, 2, 3] をメモリ上にどう表現できるでしょうか。

配列ですから、単純にメモリ上のどこかに以下のような領域を作ればよさそうです。

+---+
| 1 |
+---+
| 2 |
+---+
| 3 |
+---+

これが「値」であり配列の実体です。 そして、実体の配置されたメモリ領域へのポインタが「参照」です。

スタック上の表現

さて、プログラム上ではこの配列のようなオブジェクトを、ローカル変数としてスタック上で表現したり、関数に引数として渡したりします。 では、スタック上での配列の表現はどうなっているのか考えてみます。

ここで、「値」と「参照」という言葉が重要になります。

「値」ベースな言語では、スタック上に

+---+
| 1 |
+---+
| 2 |
+---+
| 3 |
+---+

をそのままべたっと配置します。

一方で、「参照」ベースな言語では、ヒープ上に

+---+
| 1 |
+---+
| 2 |
+---+
| 3 |
+---+

を配置し、スタック上では実体へのポインタという表現になります。 (必ずしもヒープに置くとは限らない?処理系や最適化によってはスタックに置くこともありえる?とにかくローカル変数などの表現としては実体へのポインタという形をとるということ)

値渡し・参照渡し

値渡しとか参照渡しという言葉があります。

以下に擬似コードを一つ書いてみます。(C 風に書いていますが C ではないと思ってください)

void inc_age(Person p) {
  p.age++;
}

Person john = Person { "john", 20 };
inc_age(john);
print(john.age); // => ??

このコード、処理系が「値」ベースか「参照」ベースかで結果が異なります。

「値」ベースの場合

値ベースの言語の場合、inc_age の引数に john を渡した時には、inc_age 内のローカル変数(引数) p のために、john のコピーが作られます。
inc_age 内で p.age++ としていますが、pjohn のコピーであって john ではありませんから、inc_age から戻ってきて john.age を参照しても 20 のまま変化が無いはずです。

したがって結果として 20 が出力されます。

「参照」ベースの場合

参照ベースの言語の場合、john 変数のメモリ上での表現は、ヒープに置かれた Person { "john", 20 } というオブジェクトへのポインタになります。
inc_age にこれを引数として渡すと、ポインタの値がコピーされますから、pjohn は同じオブジェクトを参照しているポインタになります。
p.age++ とすると p が参照するオブジェクトが変更されます。これは john が参照するオブジェクトと同一ですから、john.age も 21 に変化します。

したがって結果として 21 が出力されます。

C の場合

C の場合、ポインタが直接表現できますから、参照渡しの挙動を模倣することができます。

void inc_age(Person *p) {
    p->age++;
}

Person john = Person { "john", 20 };
inc_age(&john);
print(john.age); // => 21

pPerson* 型であること、そして inc_agejohn のアドレスを渡していることに注目してください。 この場合、pjohn を参照するポインタですから、結果は 21 になります。

C++ の場合

C++ の場合、言語機能として「参照渡し」という機能があります。

void inc_age(Person& p) {
    p.age++;
}

Person john = Person { "john", 20 };
inc_age(john);
print(john.age); // => 21

pPerson& 型であること、inc_age には john をそのまま渡しているように見えることに注目してください。 これは C++ の提供する機能で、コンパイルすると、Person& は実質 Person* と同じ表現になります。 p->age ではなく p.age と書けること、&john ではなく john のままで参照渡しが実現できるようになっています。 単純なポインタを使っても同じことが出来ますが、ポインタと違って nullptr になることがないという特徴があります。

参照のハマりやすい点

個人的に参照ベースの言語でハマりやすいなと感じるのは以下のようなコードです。

void some_function(Person p) {
    p.age++;
    p = new Person("bob", 30);
}

Person p = new Person("john", 20);
some_function(p);
println(p.name); // => john
println(p.age); // => 21

p.age++ の部分は呼び出し元のオブジェクトに反映されるのに、p = new Person(...) の部分はなんで反映されないの!ってなります。(なりません?) 本質的にポインタの値渡しにすぎないんだということを理解していればまぁ納得なのですが...

(ちなみに C++ や D の参照渡しだと p = new Person(...) 的なコードも呼び出し元に反映されます。)

値のハマりやすい点

ハマりやすいというか、気がつかないままパフォーマンスが悪くなりやすいのが値ベースの言語の弱点でしょう。

void print_object(HugeObject obj) {
   printf("%s\n", obj.name);
}
HugeObject obj = ...;
print_object(obj);

このようなコードを書くと、ただ名前を表示するだけの関数が激重になる可能性があります。 値ベースの言語では、引数として値を渡すとまるっとそのコピーをつくりますから、不要にもかかわらず巨大な値のコピーを作ってしまいます。

まとめ

「ムーブセマンティクス」とか「immutable と参照」とかについてまとめようと思ったのですが、前提として「値」「参照」についてまとめていないと書きにくいなと思ったのでまとめておきました。

内部表現を知ることでハマりやすい点の回避にもつながると思うので、この辺はきちんと理解しておきたいです。