Rustでアプリ

音声配信ビジネス

上記題名の本を借りてきた。今のトレンドらしい。何故って、おうちで仕事、YouTubeを見てたら仕事にならない。聴き流しなら大丈夫って訳。ラジコもいいけど、それじゃつまらんと言う需要が有るらしい。

ユーチューバーでやってくのは設備が大変、世間に顔出し出来る程、良い顔してない。でも、音声なら大丈夫。設備もスマホにアプリを入れるだけ。これで、私設のラジオのごとく拡散出来るってのが受けたんでしょうな。

聴く方は、

https://voicy.jp/

https://www.himalaya.com/jp/

https://rec.audio/

https://radiotalk.jp/

https://www.spooncast.net/jp/

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における「厳密な型付け」という文化

本家Rustコンパイラのソースを読もうとしてみる

RustでFunctor書いてみる

Rustに影響を与えた言語たち good

RUSTチュートリアルをとにかくわかりやすくまとめる会

文字列での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"])

指示の仕方が間違ってた。引数は、ちゃんと区切ってやらないと駄目みたい。

まとめ

ごちゃごちゃソースを載せてきたので、一本にしたものを載せておきます。

bld4main.rs

使う時は、main.rsにファイル名を変更して。stdモジュールしか使っていない清いやつです。

これをコンパイルして、同階層にcurrent.csvファイルを置いてください。実行結果は、zzz.pdfってやつになります。

whenに年月日の数値を与えるようにして、gnuplotスクリプトを少々改変すれば、ちゃんとした日付のpdfになります。面倒なんでやっていない。

go本が有ったのでrustの文法と比べているんだけど、rustの方がオイラーにはしっくり来るなあ。haskellと似てるからかな?

etc

Rust プログラミング・メモ

なかなか良さげなまとめだなあ。何とか理解出来る頭になってきたぞ。

https://zenn.dev/topics/rust?page=1

この方も有名だな。あれ? 色々な人が寄稿してる。rustの集積場か。

ファイルの入出力


This year's Index

Home