Rust ≒ Haskell

Rustの気になるあれ

rustの宣伝文句で気になってた事が有ったのでggしてみた。

cross compile

goでは簡単に異なるOS用のバイナリーが作成出来た。ほなら、Rustは?

RustでWindows⇔Linuxをクロスコンパイルする

こういうのが出来るそうだ。リナで開発して、それをWindowsへ持って行くと言う一方通行で、オイラーは十分と思われる。だって、Windowsって何かと不便だから、その上での開発は御免被りたい。

single binary

goでは成果物がシングル・バイナリーになってた。だからそれだけ持って行けば、普通に動いた。じゃrustは? 宣伝文句の一つに取り上げたられていたけど。。。

RustでLinux用シングルバイナリを作るまで

チャレンジされて、ドッカーファイルにまとめてしまった方がいる。確かに、安全確実な方法ではあるけど。で、その方が課題としてあげていた。

高度なリンキング  で、軽いlibcを使いましょうと言う提案。で、その軽いlibcは、 musl libc と言う代物だそうだ。一時流行った newlibc と、何が違うのだろうか?

bench

ベンチマークも気になる所。どうやるか調べてみた。

Rust で n が m で割り切れるか判定(ベンチマーク)

stable版じゃなくて、+nightly版を使えとな。.rustupの容量が倍増するぞ。でも、先端を楽しめる。

Stable Rustでベンチマークを統計的に評価する

こちらは、+nightly版を使わず、独自のクレートを使ってる。グラフが出て来て通っぽい。

install rust on debian(64Bit

) 辛抱たまらず、64Bit機にも入れてしまった。下記はstable版のみを入れた状態。

sakae@pen:~$ du -sh .cargo/
159M    .cargo/
sakae@pen:~$ du -sh .rustup/
990M    .rustup/

そして、rust-analyzerも入れて、emacsから使ってみた。

(use-package lsp-ui
  :ensure t
  :custom (lsp-ui-doc-enable nil))  ;;(lsp-ui-doc-mode)

fnとかにカーソルが乗ると、バックグランドが緑色で、説明が出てくる。非常に鬱陶しいので、前回のinit.elを少々変更した。最初は lsp-ui-doc-mode を無効にしてる。

C-c h で、メソッドの定義が参照出来るのは嬉しい事だ。C-c C-d で、dbg! してくれる。これも便利だ。see also: Rust のデバッグチートシート

cargo-miri ?

https://github.com/rust-lang/miri

うーん、インタープリター? Bug発見器? 微妙な立ち位置だな(オイラーに取ってはね)

plotters

https://github.com/38/plotters

miriを検索してて、たまたま見つけた奴。juliaのあれとそっくりな、データ表示器。

便利そうな Rust の crate メモ

元ネタはこちらに有ったもの。こういうのを人のふんどしで相撲を取るとか言うんですかね。 自分でうじうじと探すより、楽でよさそう。

Option and Result

ちまちまと教本を読んでいたらEnumの節に、有名なやつが載ってた。

enum Option<T> {
    Some(T),
    None,
}

ある型のものが有る、もしくは何も無いってのの表現。Haskell語で言うMaybeだな。

enum Result<T, E> {
    Ok(T),
    Err(E),
}

成功か失敗かの表現。これもHaskell語に有ったな。Eitherか。LeftとRightに引っかけてつかってたな。

もうRustってHaskellじゃん。Haskellで苦労させられたな。外界から入ってきた不浄なデータを内部に持ち込ませてくれないんで、慣れるまで随分と苦労した。そしてすっかり忘れてしまった。忘れていない便利なやつに、zip君が居たな。rust語にも居るのかな? (有ったよ!)

スライス

rustの教本をちまちまと見ていたんだ。そしたらスライスが使えるよ、だってさ。簡単な実験をしてみた。

fn main() {
    let mut v: Vec<&str> = "21031505,123,64,55".split(',').collect();
    v[0] = &v[0][0..6];
    println!("{:?}", v);
}

そして、その結果。

sakae@pen:/tmp/hoge$ cargo r
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/hoge`
["210315", "123", "64", "55"]

csv-read

上でやったスライスを実データに適用しようとすると、それは出来ないとか言われる。実データってのは、ファイルから読み込んだデータの事だ。足元掬われた気分。やりたい事ははっきりしてるのに、それが出来ないHaskellみたいだ。

で、どなたかが書かれていたコードを頂いてきた。

use std::fs::File;
use std::io::{BufRead, BufReader};

pub fn csv_read(filename: &str) -> (Vec<i32>, Vec<i32>, Vec<i32>) {
    let f = File::open(filename).unwrap();
    let buf = BufReader::new(f);
    let mut x: Vec<i32> = Vec::new();
    let mut y: Vec<i32> = Vec::new();
    let mut z: Vec<i32> = Vec::new();
    for line in buf.lines() {
        let l: &str = &line.unwrap();
        let p: Vec<&str> = l.trim().split(",").collect();
        x.push(p[0].parse().unwrap());
        y.push(p[1].parse().unwrap());
        z.push(p[2].parse().unwrap());
    }
    (x, y, z)
}

fn main() {
    let x = csv_read("aa.csv");
    //    let (dt, hi, lo) = x;
    //    println!("{:#?}", hi);
    println!("{:?}", x.0);
}

csvファイルから一行づつ読み込み、カンマで分解。それを数値に変換して、Vecにまとめている。必要なのは、計測日時と最高血圧と最低血圧。それぞれをVecにしてる。

main側では、タプルを分解してから使うか(コメントした部分)、haskellのタプルの作法、fst,sndみたいに、x.0のようにして取り出せばよい。

debian:bld$ cargo r
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/bld`
[21030921, 21031005, 21031021, 21031105, 21031121]

other way

読み込んだデータを数値に変換しないで、そのまま取り込む時は、下記のようにするそうな。

use std::fs::File;
use std::io::{BufRead, BufReader};

fn main() -> std::io::Result<()> {
    let mut data: Vec<Vec<String>> = Vec::new();
    for line in BufReader::new(File::open("aa.csv")?).lines() {
        data.push(line?.split(',').map(String::from).collect::<Vec<_>>());
    }

    println!("{:?}", data);

    Ok(())
}

味噌は、Vec<Vec<String>>の部分。今まで訳も分からずVec<Vec<&str>>してた。この&strは、参照なのでブロックを抜けると参照とライフタイムの関係で消滅してしまう(line)。そこで、Stringを使って所有とするとな。

debian:qq$ cargo r
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/qq`
[["21030921", "122", "63", "59"],
 ["21031005", "126", "67", "50"],
 ["21031021", "129", "63", "56"],
 ["21031105", "134", "71", "47"],
 ["21031121", "131", "72", "64"]]

見易いように、ちと変形してる。

let cadr = &data[1][0];
println!("{:?}", cadr);

ちと変形して、上のVecからcadr相当をやってみる。

"21031005"

このデータの一部を取り出そうとすると、

error[E0277]: the size for values of type `str` cannot be known at compilation time
   --> src/main.rs:10:5
    |
10  |     println!("{:?}", cadr[0..6]);
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
    |

確かに、コンパイラー君の言う通りなんだけど、抜け道は無いものか?

fn type_of<T>(_: T) -> &'static str {
    std::any::type_name::<T>()
}

こんな型を調べる関数を追加してから、スライスしたのを調べると、&str になってた。スライス無しだとStringって事だった。

let cadr = String::from(&data[1][0][0..6]);
println!("{:?}", cadr);

これ、Rust by Example のConversionの部に載ってた秘伝の方法。

"210310"

fromに対しての逆演算、intoも有るのね。

use std::convert::From;

#[derive(Debug)]
struct Number {
    value: i32,
}

impl From<i32> for Number {
    fn from(item: i32) -> Self {
        Number { value: item }
    }
}

fn main() {
    let int = 5;
    // Try removing the type declaration
    let num: Number = int.into();
    println!("My number is {:?}", num);
}

下記が実行結果

My number is Number { value: 5 }

zip

上でzipが出てきたので、使い方を調べてみた。

use std::collections::HashMap;

fn main() {
    let keys: Vec<String> = vec![String::from("ruby"), String::from("rust")];
    let values: Vec<usize> = vec![1995, 2010];

    let map: HashMap<_, _> = keys.iter().zip(values.iter()).collect();

    println!("map -> {:#?}", map);
}

keyとvalueの配列からハッシュを作る例。keyとvalueをそれぞれ取り出してきてhashを構築している。

map -> {
    "rust": 2010,
    "ruby": 1995,
}

データは、言語の公開年。rustはまだひよっこだな。

fn main() {
    let a1 = [1, 2, 3];
    let a2 = [4, 5, 6];
    let a3 = [7, 8, 9];

    let out = a1
        .iter()
        .zip(a2.iter())
        .zip(a3.iter())
        .map(|(x, y, z)| x + y + z)
        .collect::<Vec<i32>>();
    println!("{:?}", out);
}

こういう事は出来ないのか。Haskellとは違うんだな(当たり前だろ!)。

error[E0308]: mismatched types
  --> src/main.rs:10:15
   |
10 |         .map(|(x, y, z)| x + y + z)
   |               ^^^^^^^^-
   |               |       |
   |               |       expected due to this
   |               expected a tuple with 2 elements, found one with 3 elements
   |
   = note: expected tuple `((&{integer}, &{integer}), &{integer})`
              found tuple `(_, _, _)`

一応Haskellでも確認してみる。

debian:~$ ghci
GHCi, version 8.4.4: http://www.haskell.org/ghc/  :? for help
Prelude> zip [1,2,3] [4,5,6]
[(1,4),(2,5),(3,6)]
Prelude> zip [1,2,3] [4,5,6] [7,8,9]

<interactive>:2:1: error:
    • Couldn't match expected type ‘[Integer] -> t’
                  with actual type ‘[(Integer, Integer)]’
    • The function ‘zip’ is applied to three arguments,
      but its type ‘[Integer] -> [Integer] -> [(Integer, Integer)]’
      has only two
      In the expression: zip [1, 2, 3] [4, 5, 6] [7, 8, 9]
      In an equation for ‘it’: it = zip [1, 2, 3] [4, 5, 6] [7, 8, 9]
    • Relevant bindings include it :: t (bound at <interactive>:2:1)
Prelude> :t zip
zip :: [a] -> [b] -> [(a, b)]

ああ、Rustの挙動と一緒。まあ、Rustの方はmapで引っかかってきたけど。

gosh> (use scheme.list)
gosh> (zip '(1 2 3) '(4 5 6))
((1 4) (2 5) (3 6))
gosh> (zip '(1 2 3) '(4 5 6) '(7 8 9))
((1 4 7) (2 5 8) (3 6 9))

zipが複数の引数を取れるってのは、goshの麻薬的な機能なんだな。これが漏れ出していたんか。世の中、そんなに甘く無いって事だな。

Rustも早くtypeofとかを実装して下さい。まて、そんなのインタープリターが動いてこその便利物だろう。それまでは、 zipの心 でも見て桶。

fn zip<U>(self, other: U) -> Zip<Self, <U as IntoIterator>::IntoIter> where
    U: IntoIterator, 

impl<A, B> Iterator for Zip<A, B> where
    A: Iterator,
    B: Iterator, 

zipのソース

web上でsrcを展開すると、細かい物が見えて来るんだけど、手元に有るrustのソース一式上では、それが何処に位置してる? 折角の機会なので、探ってみる。

debian:rust$ find . -name '*.rs' | xargs grep ZipImpl
./library/core/src/iter/adapters/zip.rs:        ZipImpl::new(a, b)
./library/core/src/iter/adapters/zip.rs:        ZipImpl::next(self)
./library/core/src/iter/adapters/zip.rs:        ZipImpl::size_hint(self)
./library/core/src/iter/adapters/zip.rs:        ZipImpl::nth(self, n)
./library/core/src/iter/adapters/zip.rs:        // SAFETY: `ZipImpl::__iterator_get_unchecked` has same safety
./library/core/src/iter/adapters/zip.rs:        unsafe { ZipImpl::get_unchecked(self, idx) }
./library/core/src/iter/adapters/zip.rs:        ZipImpl::next_back(self)
./library/core/src/iter/adapters/zip.rs:trait ZipImpl<A, B> {
./library/core/src/iter/adapters/zip.rs:impl<A, B> ZipImpl<A, B> for Zip<A, B>
./library/core/src/iter/adapters/zip.rs:impl<A, B> ZipImpl<A, B> for Zip<A, B>

随分とWebに掲載されてる物と違うな。Web上のものはすっきりしてる。見るならWebでってのが、正しいrust道と思われる。

それでもソースをぶらぶらは楽しいぞ。例えば、core/src/iter/traits/collect.rs

/// use std::iter::FromIterator;
///
/// let five_fives = std::iter::repeat(5).take(5);
///
/// let v = Vec::from_iter(five_fives);
///
/// assert_eq!(v, vec![5, 5, 5, 5, 5]);

頭についてるコメント記号 /// は、cargo doc が使うドキュメント用って意味だ。 ここで一つ文句が有る。5,5って同じ数字を重ねてるけど、どっちがどっちに影響してるか、不明確(修行中の、オイラーの感想です)。ここは、repeat(5).take(4)とかにしてくれないかなあ(気配りが足りないぞ)。

イテレータ

みんな大好き。forの追放だ。これで関数言語ぽくなるぞ。 一連の要素をイテレータで処理する

これらをまとめて下さった方がおられる。

Rustのイテレータの網羅的かつ大雑把な紹介

こういう風に説明されると、益々Haskellを思い出すな。

let vec = [1,2,3];

let iter = vec.iter();
let mapped = iter.map(|i| i*2)
                .inspect(|i| println!("i = {}", i))
                .map(|i| i*0)
                .collect::<Vec<i32>>();

println!("{:?}", mapped);

mapの中でprintln!は使えないとか。で、その代わりにinspectを使えとな。

関数型プログラマのためのRust

こんな記事も見つけた。

etc

https://github.com/uutils/coreutils/

unixのコア・ユーティリティをRustで書くプロジェクト。面白いな。

rust-lang-nursery / rust-cookbook (can read offline)

Cookin' with Rust


This year's Index

Home