Rustでアプリ
音声配信ビジネス
上記題名の本を借りてきた。今のトレンドらしい。何故って、おうちで仕事、YouTubeを見てたら仕事にならない。聴き流しなら大丈夫って訳。ラジコもいいけど、それじゃつまらんと言う需要が有るらしい。
ユーチューバーでやってくのは設備が大変、世間に顔出し出来る程、良い顔してない。でも、音声なら大丈夫。設備もスマホにアプリを入れるだけ。これで、私設のラジオのごとく拡散出来るってのが受けたんでしょうな。
聴く方は、
https://stand.fm/ (拡散は自分でRSSフィードするなり、ツイターでって方針みたい)
こういう所へ行って、好きな番組を選べば良い(中には有料も有るから気を付けて)。
ipadは長寿命
ipadをソファーから落としてしまった。画面が割れてお陀仏したかと思ったら、壊れたのはカバーだった。カバーが盾になって本体を守ってくれたのね。
このカバーは2代目だ。初代のやつはipadと同時に購入したイタリア製だったかのおしゃれな奴。それがボロボロになったので、日本製の安いやつに買い替えたんだった。
カバーが有るんで、炬燵に立てて使う事が出来たんだけど、カバー無しの本体だけだと、そうもいかない。困った時の100均頼りとばかりに、何か代用品が無いかと物色に出かけた。
そしたら有ったよ。針金を曲げ加工したタブレット置きスタンドが。なんか、段々とチープな物に変わって行くなあ。まあ、それでもいいんだけど。
このipad、使い始めてから8年目に突入する。電池がヘタって、途中で一度ぐらいは交換するかなあと思っていたけど、今の所至って元気だ。信じられないくらい。
この間、10年使った携帯を新しくした。この10年で、電池を4回も交換してるよ。それを思えば、ipadはよく出来てるな。電池がヘタるまで使って、2代目ipadは、アプル・シリコン入りのやつにしたいものだ。
そうそう、問題が一つ有った。長年の使用で充電器のケーブルのコネクタ近くで断線の兆候が有るんだ。そんな事もあろうかと、一応スリーブで保護はされているんだけど、そのスリーブが硬いため、ケーブルの曲げがスリーブの端に集中しちゃってる。保護テープでも巻いて、固定しておくかな。同じような事、誰かの携帯の充電器でも発生してたって言うから、以外なウィークポイントなんだな。
Rustの資料
ちまちまと資料集めをしている。本も買ってないし勉強会に出るわけでもないので、気が付いた時にメモしてるんだ。
程よい分量で、適切な例が載ってたので、よく分かった(つもり)。トレイト境界なんて説明が出てきてた。ちょっとコードを引用させてもらうと
// Tという型を用意し、TはCarを実装している型に限定する。 // Carを実装している型は、run()とrefuel()を実装していることが保証されるため、具体的な型名は問わない。 fn drive<T>(mut car: T, distance: u32) -> T where T: Car, // TはCarを実装した型に限定する → トレイト境界 { ... }
Tはトラックとかスポーツカーとかのcar族に限定されるって宣言だな。同じ考え方がhaskellにも有ったな。
Prelude> :t sqrt sqrt :: Floating a => a -> a
sqrtは、aの型を受け取ってaの型を返す。aは浮動小数ねって制約。
Prelude> sqrt 10 3.1622776601683795 Prelude> sqrt 4 2.0
出力は必ず浮動小数になるって事だな。入力が整数であっても、これは浮動小数の一種って解釈してるんだな。
Rustに影響を与えた言語たち good
文字列でのcsv取り込み
前回CSVファイルのデータを読み込む方法を調べた。最終的には、読んだデータをgnuplot用に書き出すんで、文字列のまま処理したい。但し、起床時と就寝時に分解したい。 そんな訳で、下記のようにした。
use std::fs::File; use std::io::{BufRead, BufReader}; fn main() -> std::io::Result<()> { let mut am: Vec<Vec<String>> = Vec::new(); let mut pm: Vec<Vec<String>> = Vec::new(); for line in BufReader::new(File::open("aa.csv")?).lines() { let ap = line?.split(',').map(String::from).collect::<Vec<_>>(); if &ap[0][6..7] == "0" { am.push(ap); } else { pm.push(ap); } } println!("{:?}", am); Ok(()) }
am/pmの判定は、6文字目を見ると言う安易な方法なんだけど、これをap[0][6]のようにしちゃうと、怒られる。整数による文字列の参照は出来ませんですって。そんな訳で、スライスに変えた(実質1文字しか取り出していない)。全く、トラップが各所に仕込まれているな。
ファイルへの書き出し
昔のgo製プログラムでは、gnuplotへのデータ渡しにファイルを使ってた。そして、gnuplotのスクリプトもファイル渡ししてた。そんな訳で、今度は書き出しだ。
use std::io::{BufRead, BufReader, Write}; fn main() -> std::io::Result<()> { : dump4gp("am.dat", am); dump4gp("pm.dat", pm); : } fn dump4gp(dname: &str, seed: Vec<Vec<String>>) -> std::io::Result<()> { const HOWLONG: usize = 3; let mut file = File::create(dname)?; for m in seed.len() - HOWLONG..seed.len() { writeln!(file, "{} {} {}", seed[m][0], seed[m][1], seed[m][2])?; } file.flush()?; Ok(()) }
constで宣言する変数名は大文字にするのが流儀らしい(debug中なんで3データ分にしてる。実戦では70データ分)。 そして、取り出すデータは直近の物って事で、(インデックスの)スライスを採用した。
これで動いた。けど
warning: unused `std::result::Result` that must be used --> src/main.rs:17:5 | 17 | dump4gp("am.dat", am); | ^^^^^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_must_use)]` on by default = note: this `Result` may be an `Err` variant, which should be handled
こんな注意が出てきてた。どうしろと?
それより、気になる記事を見つけた。バッファリングしながら書き出すと性能が4倍にあがるとな。
use std::fs::OpenOptions; use std::io::{BufWriter, Write}; fn main() { let file = OpenOptions::new() .append(true) .create(true) .open("./sample.txt").expect("ファイル作成エラー"); let mut buf_writer = BufWriter::new(file); for _ in 0..1_000_000 { writeln!(buf_writer, "hello").expect("書き出しエラー"); } }
see also: Rustで高速な標準出力 in Posts
有名な方に遭遇してしまったぞと。
gnuplot用のスクリプトを用意して、起動するぞ
次はgnuplot関係。goの時はgnuplotでも統計の計算を出来る事に気が付き、それを利用する事にした。但しスクリプトを本体と別にしちゃうと、シングルバイナリーにならないので、スクリプトをgoのアプリ内に保持しておいて、使う時に展開する事にしたんだった。
スクリプトファイルの自動作成
use std::fs::File; use std::fs::OpenOptions; use std::io::{BufRead, BufReader, BufWriter, Write}; use std::process::Command; fn main() { : scr4gp("topdf.plt"); run4gp(); // std::fs::remove_file("topdf.plt"); }
main側での呼び出し関係とuse一式。stdだけを使うすっきり仕様だ。
fn scr4gp(sname: &str) { let gsrc = "# blood graph for gnuplot set encoding utf8 set terminal pdfcairo mono font \",20\" size 21cm, 29cm set output \"./zzz.pdf\" #set output \"Desktop\\\" . when . \".pdf\" stats \"am.dat\" using 2:3 amh = sprintf(\" 最高血圧(平均=%.1f 偏差=%.1f)\", STATS_mean_x, STATS_stddev_x) aml = sprintf(\" 最低血圧(平均=%.1f 偏差=%.1f)\", STATS_mean_y, STATS_stddev_y) "; let file = OpenOptions::new() .append(true) .create(true) .open(sname) .expect("ファイル作成エラー"); let mut buf_writer = BufWriter::new(file); writeln!(buf_writer, "{}", gsrc).expect("書き出しエラー"); }
gssrcってのは、gnuplot用スクリプトの一部だ。昔のgoのソースから引っ張ってきての書き込みテスト用だ。
スクリプトのソースは御多聞に漏れず、文字列を表すダブルクォーテーションが多用されてる。 rustcの文字列もダブルクォーテーションで囲む約束になってるので、文字列中のそれは、いちいちエスケープしないとならない。
goとかpythonとかは、文字列を囲む記号に、シングルクォーテーションを使えたりして便利。rustも是非、こういう気配り宜しく。静的解析で、文字列って判定は容易なはずなんで、任意の記号も採用出来るはず。後、ヒアドキュメントも検討宜しく。って、スクリプターからの強い要求だからね。
それから、何気にUTF-8の表現だけども、問題無かった。トラブルが出るかと(期待?)してたんだけどね。
gnuplotの実行
最後は、gnuplotを走らせる部分。goでどうなってたか、確認しとく。
func ag(datafile string) string { // for gnuplot option : return fmt.Sprintf("when=%s", line[:6]) } exec.Command("gnuplot", "-e", ag("am.dat"), "topdf.plt").Run()
gnuplotにスクリプトを渡せばいいんだけど、-eで、コマンドも渡している。when=210110 みたいな奴。これスクリプト中へ渡す引数だ。出来上がるpdfファイル名に使ってる。 そんな事を思い出しながら、
fn run4gp() { let rs = Command::new("gnuplot") .arg("-e \"when=210110\"") .arg("topdf.plt") .output() .expect("Failed to execute command"); println!("{:?}", rs); }
引数が複数有る場合は、上記のように .arg(…) を重ねるか .args(&["-c", "hello"])のようにすれば良い。Struct std::process::Command
Module::processの例を見てて飽き足らず、Stuructsにある、
Command A process builder, providing fine-grained control over how a new process should be spawned.
をダブルクリックして、案内された次第。
実行結果はrsに帰って来るので、表示してみた。
まずは、期待値
sakae@pen:/tmp/bld$ gnuplot -e "when=210110" topdf.plt * FILE: Records: 3 Out of range: 0 : Correlation: r = 0.2402 Sum xy: 2.201e+04
次は、rustcが作ったアプリから
sakae@pen:/tmp/bld$ target/debug/bld Output { status: ExitStatus(ExitStatus(0)), stdout: "", stderr: "unrecognized option -e \"when=210110\"\n\n* FILE: \n Records: 3\n Out of range: : r = 0.2402\n Sum xy: 2.201e+04\n\n" }
なんか、許されないオプションって言ってるけど何だろう。それを無視すれば、ちゃんとgnuplotとやり取り出来ているな。
.args(&["-e", "when=210110", "topdf.plt"])
指示の仕方が間違ってた。引数は、ちゃんと区切ってやらないと駄目みたい。
まとめ
ごちゃごちゃソースを載せてきたので、一本にしたものを載せておきます。
使う時は、main.rsにファイル名を変更して。stdモジュールしか使っていない清いやつです。
これをコンパイルして、同階層にcurrent.csvファイルを置いてください。実行結果は、zzz.pdfってやつになります。
whenに年月日の数値を与えるようにして、gnuplotスクリプトを少々改変すれば、ちゃんとした日付のpdfになります。面倒なんでやっていない。
go本が有ったのでrustの文法と比べているんだけど、rustの方がオイラーにはしっくり来るなあ。haskellと似てるからかな?
etc
なかなか良さげなまとめだなあ。何とか理解出来る頭になってきたぞ。
https://zenn.dev/topics/rust?page=1
この方も有名だな。あれ? 色々な人が寄稿してる。rustの集積場か。