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です。自分の土俵で戦えると思ったから。
に、公開されてました。 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
色々な実例が揃っていて、cookbookの代わりになるなあ。助かります。
etc
OpenCVの所に、アフィン変換が出てきてた。オイラーは常識知らなかったのだな。