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の所に、アフィン変換が出てきてた。オイラーは常識知らなかったのだな。