Rust (2)

rust-gdb

rustの右も左も分からないうちに、一丁前にgdbです。試したのは、前回実習で取り上げた時間計測の例。プロジェクト名をclockって付けてます。環境は、OpenBSDの

ob$ rustc --version
rustc 1.46.0

emacsから、M-x gdb で起動して、下記のように走らせるgdbをrust-gdbにしてます。

Run gdb (like this): rust-gdb -i=mi target/debug/clock
  :
Reading symbols from /tmp/clock/target/debug/clock...done.
(gdb) l
1       use quanta::Clock;
2       use std::time::Instant;
3
4       fn main() {
5           let clock = Clock::new();
6           const N: u32 = 1_000_000;
7
8           let istart = Instant::now();
9           let mut istop = istart;
10          for _ in 1..N {
(gdb) b 9
Breakpoint 1 at 0x19852: file src/main.rs, line 9.

いきなりmainにBPを置くとld.soが出てきてしまうので、一度リストして、程よい場所にBPを起きました。

(gdb) r
Starting program: /tmp/clock/target/debug/clock

Breakpoint 1, clock::main::hff2d7789411e7a3c () at src/main.rs:9
9           let mut istop = istart;
(gdb) bt
#0  clock::main::hff2d7789411e7a3c () at src/main.rs:9
#1  0x00000ef1f2ce5cde in std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::h4\
c1168b2cc63bbe2 () at /usr/obj/ports/rust-1.46.0/rustc-1.46.0-src/src/libstd/rt\
.rs:67
#2  0x00000ef1f2cfc834 in std::rt::lang_start_internal::h74f326e50ec8a6d9 ()
#3  0x00000ef1f2ce5cb8 in std::rt::lang_start::hb1b500340060868d (main=0xef1f2c\
e57f0 <clock::main::hff2d7789411e7a3c>, argc=1, argv=0x7f7ffffc8908) at /usr/ob\
j/ports/rust-1.46.0/rustc-1.46.0-src/src/libstd/rt.rs:67
#4  0x00000ef1f2ce5c5b in main ()
(gdb) p istart
$1 = std::time::Instant (std::sys::unix::time::inner::Instant {t: std::sys::uni\
x::time::Timespec {t: libc::unix::timespec {tv_sec: 4145, tv_nsec: 690796033}}}\
)

btを見ると分かるんだけど、ユーザーが定義したmain(正確には、clock::main)は、std::rtの中から呼ばれているんですね。思わぬ拾い物をしたと言うか何と言うかですね。

Rustでライブラリのデバッグをする なんて言う記事を見つけましたよ。

ob$ rust-gdb target/debug/clock
GNU gdb (GDB) 7.12.1
  :
Reading symbols from target/debug/clock...done.
(gdb) b clock::main::hff2d7789411e7a3c
Breakpoint 1 at 0x197fb: file src/main.rs, line 5.
(gdb) r
Starting program: /tmp/clock/target/debug/clock

Breakpoint 1, clock::main::hff2d7789411e7a3c () at src/main.rs:5
5           let clock = Clock::new();

BPを置くのに、clock::main:: まで入力して、後はTABを叩く。そうすればgdbは勝手にhff2d7789411e7a3cを補完してくれる。Cフラフラ語の流儀で、同名の関数でも引数のタイプ等で、一意になるように(勝手に関数名を追加)してるんで、面倒臭いぞ。

#0  quanta::Clock::new::hfefcfb82919bafa3 ()
    at /home/sakae/.cargo/registry/src/github.com-1ecc6299db9ec823/quanta-0.7.2/
src/lib.rs:303
#1  0x000002ec949b6808 in clock::main::hff2d7789411e7a3c () at src/main.rs:5
#2  0x000002ec949b6cde in std::rt::lang_start::_$u7b$$u7b$closure$u7d$$u7d$::h4c
1168b2cc63bbe2 ()
    at /usr/obj/ports/rust-1.46.0/rustc-1.46.0-src/src/libstd/rt.rs:67
(More stack frames follow...)
warning: (Internal error: pc 0x2ec949b734c in read in psymtab, but not in symtab
.)

(gdb) l
298     impl Clock {
299         /// Creates a new clock with the optimal reference and source clocks
.
300         ///
301         /// Support for TSC, etc, are checked at the time of creation, not c
ompile-time.
302         pub fn new() -> Clock {
303             let reference = Monotonic::new();
304             let constant_tsc = has_constant_or_better_tsc();
305             let inner = if constant_tsc {
306                 let source = Counter::new();
307                 let calibration = GLOBAL_CALIBRATION.get_or_init(|| {
warning: (Internal error: pc 0x2ec949b734c in read in psymtab, but not in symtab
.)

stepして、ライブラリィーに潜り込んでみた。

ob$ ls -l target/debug/clock target/release/clock
-rwxr-xr-x  2 sakae  wheel  1280960 Mar 13 14:43 target/debug/clock*
-rwxr-xr-x  2 sakae  wheel   350392 Mar 13 14:45 target/release/clock*

cargo build –release して、正式版も作ってみた。随分とdebug情報がくっついていますね。

ob$ target/debug/clock
std::time:Instant::now() overhead = 1.736340934s
quanta::clock::now() overhead = 175.021676ms
ob$ target/release/clock
std::time:Instant::now() overhead = 1.656117958s
quanta::clock::now() overhead = 29.826478ms

quanta(TSC)を使った時、顕著な差が出てるな。まあ、仮想PC上だから、何とも言えないけどね。

debian:clock$ target/debug/clock
std::time:Instant::now() overhead = 1.478870251s
quanta::clock::now() overhead = 1.512766381s
debian:clock$ target/release/clock
std::time:Instant::now() overhead = 1.393443108s
quanta::clock::now() overhead = 1.435792702s

一応リアルPCなdebian(32Bit)でも確認。TSCを使う方が遅いな。って、セレロンな石にTSCなんて積んでいたっけ?

それから、debianでgdbやってみたんだけど、OpenBSDのそれとは挙動が違った。普通のgdbっぽい。ちゃんとmainにBP置いて、debug出来る。

emacs for rust

勢いを駆って、難しいemacs(前回リンクしておいた)に挑戦。

$ rustup component add rls
$ curl -L https://github.com/rust-analyzer/rust-analyzer/releases/latest/download/rust-analyzer-linux -o ~/.cargo/bin/rust-analyzer

サーバーとお話する必要が有るので、どちらかを入れれおけとな。OpenBSDには残念ながらrustupが入っていない。んで、debianで実験。

;; rust-mode
(setq exec-path (cons (expand-file-name "~/.cargo/bin") exec-path))
(use-package rust-mode
  :ensure t
  :custom rust-format-on-save t)
(use-package cargo
  :ensure t
  :hook (rust-mode . cargo-minor-mode))
(use-package lsp-mode
  :ensure t
  :hook (rust-mode . lsp)
  :bind ("C-c h" . lsp-describe-thing-at-point)
  :custom (lsp-rust-server 'rls))   ;; or 'rust-analyzer (64Bit)
(use-package lsp-ui
  :ensure t)

emacsの設定は、引き写しです。

LSP :: rls:3047 initialized successfully

こんな案内がemacsのmini-bufferに出て来た。ソースの要所にカーソルを移動すると、色々な説明が出てきた。 rust-analzerの方も試そうと思ったら、64Bitしかサポートしてなかった。今頃32Bit機なんて後生大事に使ってるなんて、この貧乏人めと言う態度ですよ。

大体rustでggると、macではってのばかりがヒットするぞ。rustはブルジョア御用達な言語なんだな。悔しかったら、アプル・シリコン入りのMACを調達してみろ、ですかね。何処かに、宝くじでも落ちていないかなあ。それとも、お馬様に投資して、ビキナーズラックに掛けてみる?

貧乏人は辛いよの事例が、お勧めされてた cargo-edit を入れる時に発生した。200個を超えるクレートを永遠とコンパイル始めた。さっぱり終わらない。32Bit機だから遅いんだよと自分を慰めつつ、OpenBSD(64Bit)でも、やってみた。

豪華に4CPUをフルに使ってコンパイルしてたけど、それでも200個となると、とてつもない時間がかかった。ほとんどがrustcを呼び出しているんだけど、時にccを使う事もあって、コンパイラー系総動員って感じ。

これじゃ、かの昔にやったjuliaとかghcのコンパイルと同じじゃん。特にghcは図体が大きく、細切れのモジュールが多くて、こりゃ使うものじゃないと封印したんだった。

rustでも、同じ傾向が見て取れる。まあ、機能が似てくれば、そうなるわな。世の中に『銀の弾丸は無い』って事です。

gnuplot by rust

立派な自習書が有るのに、それには目もくれず、放浪の旅です。次なる獲物は、gnuplotです。自分の土俵で戦えると思ったから。

RustGnuplot

に、公開されてました。 gnuplotを呼び出すドライバーって感じです。色々なサンプルが付属してたので、レポジトリー毎DLしてきました。サンプルはどうやって動かす? 試行錯誤の末、下記の方法を発見(多分、自習書には解説されてるでしょうけど)。

ob$ cd RustGnuplot/gnuplot/
ob$ cargo run --example example2
ob$ cargo run --example lines_points_3d

それはいいんだけど、実践投入はどうやる?

ob$ cargo new ifgp
     Created binary (application) `ifgp` package
ob$ cd RustGnuplot/gnuplot/examples/
ob$ cp readme_example.rs /tmp/ifgp/src/main.rs
ob$ cp common.rs /tmp/ifgp/src/
ob$ cd /tmp/ifgp/

ifgpってプロジェクトを始めてみる。 一番簡単なサンプルをmain.rsに上書き。commpn.rsって言う共通ファイルもコピー。

ob$ vi Cargo.toml
  :
[dependencies]
gnuplot = "0.0.37"
ob$ cargo build
    Updating crates.io index
  Downloaded gnuplot v0.0.37
  Downloaded 1 crate (34.2 KB) in 1.81s
   Compiling byteorder v1.4.3
   Compiling gnuplot v0.0.37
   Compiling ifgp v0.1.0 (/tmp/ifgp)
error[E0432]: unresolved import `argparse_rs`
 --> src/common.rs:2:5
  |
2 | use argparse_rs::*;
  |     ^^^^^^^^^^^ use of undeclared type or module `argparse_rs`

error[E0433]: failed to resolve: use of undeclared type or module `ArgParser`
  --> src/common.rs:53:18
   :
Some errors have detailed explanations: E0432, E0433.
For more information about an error, try `rustc --explain E0432`.
error: could not compile `ifgp`.

To learn more, run the command again with --verbose.

gnuplotに依存してるんで、それをCargo.tomlに記述。そしてbuild。出たなエラー。–verboseを付けるといいよとの事。

ob$ cargo build  --verbose
       Fresh byteorder v1.4.3
       Fresh gnuplot v0.0.37
   Compiling ifgp v0.1.0 (/tmp/ifgp)
     Running `rustc --crate-name ifgp --edition=2018 src/main.rs --error-format=
json --json=diagnostic-rendered-ansi --crate-type bin --emit=dep-info,link -Cemb
ed-bitcode=no -C debuginfo=2 -C metadata=2795113e4d19dc2f -C extra-filename=-279
5113e4d19dc2f --out-dir /tmp/ifgp/target/debug/deps -C incremental=/tmp/ifgp/tar
get/debug/incremental -L dependency=/tmp/ifgp/target/debug/deps --extern gnuplot
=/tmp/ifgp/target/debug/deps/libgnuplot-9e9c7376bf3ad043.rlib`
error[E0432]: unresolved import `argparse_rs`
  :

大して解決の糸口になりそうな情報は無い。じっとエラーを眺める。。。と、隠れクレートが必要なんだな。探してみる。

ob$ cargo search argparse_rs
argparse-rs = "0.1.0"    # A simple argument parser, meant to parse command line
 input. It is inspired by the Python ArgumentPars…
 :

プログラム上では、 argparse_rs なんだけど、名前は、ダッシュなのねとか言いながら、Cargo.tomlに追加。

ob$ cargo build
    Updating crates.io index
   Compiling argparse-rs v0.1.0
   Compiling ifgp v0.1.0 (/tmp/ifgp)
    Finished dev [unoptimized + debuginfo] target(s) in 5.87s

buildに成功したので、走らせてみる。

ob$ cargo r
    Finished dev [unoptimized + debuginfo] target(s) in 0.04s
     Running `target/debug/ifgp`
terminal:None
no-show:Some("false")
help:Some("false")
extension:None

ちゃんとグラフが表示された。後は、ソースを読むだけーーー。 で、ただ読めじゃ辛いんで、下記のようにしてドキュメントを作成させ、ブラウザーに表示させればよい。中々、良く出来ている。

ob$ cargo doc --open
    Checking byteorder v1.4.3
 Documenting byteorder v1.4.3
    Checking argparse-rs v0.1.0
 Documenting argparse-rs v0.1.0
    Checking gnuplot v0.0.37
 Documenting gnuplot v0.0.37
 Documenting ifgp v0.1.0 (/tmp/ifgp)
    Finished dev [unoptimized + debuginfo] target(s) in 10.51s
     Opening /tmp/ifgp/target/doc/ifgp/index.html

興味深いのは、下記のようにイメージ表現にsvgが使われていた事。rustの母体であるモジラ組なればこそだな。綺麗なイメージ好きですか? ならば、favicon.svgを見ればよい。自転車の駆動用歯車(とRustの屋号のR)が良い塩梅で表示されるぞ。なんでも、rustの創始者はチャリダーだったとか。それで、歯車をアイコンにしたそうな。wheel.svgは、後輪に付く歯車をイメージしてるっぽい。

debian:doc$ wc *svg
   0   48  455 brush.svg
   0   35  510 down-arrow.svg
  24  193 4298 favicon.svg
   0   52 3764 wheel.svg

また、Cargo.lockファイルが自動作成され、使うクレートが固定される。これで、このプロジェクトの再現性が、未来永劫保障される(かも)。Haskellのライブラリィー地獄を上手く回避してるな。

ob$ cargo tree
ifgp v0.1.0 (/tmp/ifgp)
├── argparse-rs v0.1.0
└── gnuplot v0.0.37
    └── byteorder v1.4.3

部品集め

新しい言語を始める時、必ず血圧管理プログラムを書いてみる事にしてる。CSVフィアルからデータ読み込み。起きた時と寝る時のデータに分離。そのデータをgnuplotに送り込んでグラフ表示。 実用にしてるのは、2019/04/10 に発表したgo製のnbld.goだ。看護婦さんや薬剤師さんの評判も上々(と、少し自慢しとく)。

その一歩として、色々なコード片を集めてみた。

文字列を数値に変換

いつも右往左往するのがこれ。goの時も悩んだ記憶がある。

fn main() {
    let parsed: i32 = "5".parse().unwrap();
    let turbo_parsed = "10".parse::<i32>().unwrap();

    let sum = parsed + turbo_parsed;
    println!("Sum: {:?}", sum);
}

最初の例では parsed: i32 = … ってなってる。このi32は、型注釈って言うそうな。5なら色々な型(int, long, float等)になりうるから、ユーザーがあえて指示するんだな。2例目も同様(ちと面倒くさい)。

5_000_000 とか、0xfff みたいな表現は、どうやって数値化出来るの?

カンマ区切りを分解

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

collect()って何?

let a = [1, 2, 3];

let doubled: Vec<i32> = a.iter()
                         .map(|&x| x * 2)
                         .collect();

assert_eq!(vec![2, 4, 6], doubled);

これを見ると、mapってのはlispで言うlambdaだな。collectはappendって趣だ。lisp屋からみると、実にまどろっこしい。

待て、mapの代わりにsplitが来てて、a.iterの代わりに分解出来る要素(としても文字列)が有るとすれば、公式通りの使い方って事になる。lisp脳からrust脳へチェンジする必要が有りそうだな。

joinせよ

分解したら戻したい。直交性問題です。

Rustで文字列イテレータを連結するときに便利な itertools::join は結構遅い

色々調べて下さった方がおられるので、参考にしましょう。

fn main() {
    let s = ["21031505", "123", "64", "55"].join(", ");
    println!("{:?}", s);
}

ファイルから1行づつ読み込み

use std::fs::File;
use std::io::{self, BufRead};
use std::path::Path;

fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
    P: AsRef<Path>,
{
    let file = File::open(filename)?;
    Ok(io::BufReader::new(file).lines())
}

fn main() {
    if let Ok(lines) = read_lines("./current.csv") {
        for line in lines {
            if let Ok(al) = line {
                println!("{}", al);
            }
        }
    }
}

指定したファイルが読めれば、1行づつ読み込んで出力する。ファイルのPathが、自分が居る所って指定なんで、target/debug/の置いておくんかと思ったら、違った。Cargo.tomlが有る階層で良かった。

何気ない事だけど、これはありがたい。だって、cargo cleanしたら、target以下が無くなっちゃうはずだからね。それにしても、whereだとかOk(に包まれたメッセージ)が出てきたり、そのOkを外す方法が出て来たり、これはもう、Haskellの香りがプンプンするな!

ああ、数字への変換の所に出て来たunwrapは、積極的なOk外しなのね。だんだん繋がって来た感じがする。

Rustでファイルの入出力 も見て桶。別解が解説されているぞ。

pipe

パイプ (Rust by Example 日本語版)

色々な実例が揃っていて、cookbookの代わりになるなあ。助かります。

etc

Rustは何が新しいのか(基本的な言語機能の紹介)

Rust Language Cheat Sheet

初心者向けTellus学習コース

OpenCVの所に、アフィン変換が出てきてた。オイラーは常識知らなかったのだな。


This year's Index

Home