たなしょのメモ

日々勉強していることをつらつらと

Rust勉強してみた 10日目

改行ありの長い文章

改行ありの長い文章を代入したい場合は、"\で書き始める。

let contents = "\
Rust:
safe, fast, productive.
pick three.";

cargo build

cargo buildは下記の様に二つあるあって、コンパイラが異なるプロファイルを使用する。

cargo build
cargo build --release

devとreleaseではopt-levelが異なる。
opt-levelは最適化の度合いを制御している。
opt-levelが0だとコンパイルは速いが動作は遅い。
opt-levelが3だとコンパイルは遅いが動作は速い。

[profile.dev]
opt-level = 0

[profile.release]
opt-level = 3

cardo doc --open

下記のように///を記載するとドキュメンテーションコメントになる。

cargo doc --open

上記コマンドでHTMLファイルを生成する。
便利

開発環境をLinuxにしたはなし

Lubuntu 20.04 LTSにした話

掲題の通りLinux生活にした。
今の所これといって生活に支障をきたすこともなく順応していると思う。
もともとゲーミングPCを買ったのはいいがゲームもせずもっぱらwsl2を起動してUbuntu環境で遊んでいて、
よく使うソフトウェアもvim(nvim),byobu,make,cargo,w3m,chrome(firefox),git,gcc,bashくらいしか使っていなかったので
半分Linuxを使っていたのもあるのかなと。
そういうのもあり今後はプログラミングで遊ぶPCはもっぱらLubuntuになりそうだ。

困ったこと

wifiが頻繁に落ちる問題が今はそれも解消したみたい。
下記のサイトを見たら解消できた。
これ

こんな感じでdefault-wifi-powersave-on.confを修正したことでwifiの件は解消した。

$cat /etc/NetworkManager/conf.d/default-wifi-powersave-on.conf
[connection]
#wifi.powersave = 3
wifi.powersave = 2

どうもwifi省エネモードみたいなものにはいっていたらしいそんなものいらんのよ(´・ω・`))
こういう小さな落とし穴がたくさんあるからLinuxを人に進められないんだよね...

今後やりたいこと

とりあえずはプログラミングをしてみてその都度必要になったソフトウェアを使っていくと思う。
/etc内にあるソフトウェアは興味があるときにでも触ってみようと思う。
優先度低いけどzshへの移行もやりたいね。

Rust勉強してみた 9日目

ファイルを分割した。

minigrepのファイルを分割した。
main.rs

extern crate minigrep;

use std::env;
use std::process;

use minigrep::Config;

fn main() {
    let args: Vec<String> = env::args().collect();

    let config = Config::new(&args).unwrap_or_else(|err| {
        println!("Problem parsing arguments: {}", err);
        process::exit(1);
    });

    println!("Searchng for {}", config.query);
    println!("In file {}", config.filename);

    if let Err(e) = minigrep::run(config) {
        println!("Application error: {}", e);

        process::exit(1);
    }
}

lib.rs

use std::error::Error;
use std::fs::File;
use std::io::prelude::*:

pub fn run(config: Config) -> Result<(), Box<Error>> {
    let mut f = File::open(config.filename)?;

    let mut contents = String::new();
    f.read_to_string(&mut contents)?;

    println!("With text:\n{}", contents);

    Ok(())
}

pub struct Config {
    query: String,
    filename: String,
}

impl Config {
    fn new(args: &[String]) -> Result<Config, &'static str> {
        if args.len() < 3 {
            return Err("not enough arguments");
        }

        let query = args[1].clone();
        let filename = args[2].clone();

        Ok(Config { query, filename })
    }
}

引数の個数が違う場合はエラーを出すようにしたり、分割した関数や構造体を使いたいのでpubを付けてみたり、
clone関数でも引数の文字をコピーさせたり、process関数を使ってexitさせてみたりした。

関数の分割の際に勉強になる。

ろぐ

https://github.com/jacoloves/lab/commit/79d6f499dac3c573b41f46c1889b0105b0774520

Rust勉強してみた 8日目

テスト

#[should_panic]

記載するとテストケースでpanicに入ると正常終了になる(テストが)。

expectedに文字を入れるとFAILEDしたときにその文言が出力される。

#[should_panic(expected = "Guess value must be less than or equal to 100")]

[ignore]をつけるとテストケースを無視できる。

無視されるテストのみ実行するなら

cargo test -- --ignored

extern

externを使うこと自作したクレートを呼び出すことができる。
adder2/src/lib.rs

pub fn add_two(a: i32) -> i32 {
    internal_adder(a, 2)
}

adder2/tests/integration_test.rs

extern crate adder2;

#[test]
fn it_adds_two() {
    asser_eq!(4, adder2::add_two(2));
}

testsディレクトリは特別で#[cfg(test)]をつけなくてもテストが実行できる。

--test

cargo test --test integration_test

上記のように--testにあとにテストを実行したいファイル名を記載すると、そのファイルだけテストを実行できる。

コマンドライン引数を取得

env::args().collect()を使うとコマンドライン引数を取得することができる。CLIツールを作るときに便利そう。

let args: Vec<String> = env::args().collect();

各引数の値をとりたいとき&args[x]で取得する。

let query = &args[1];
let filename = &args[2];
println!("Searchng for {}", query);
println!("In file {}", filename);

ファイルオープン

ファイルオープンにはFile::open()を使用する。

let mut f = File::open(filename).expect("file not found");

ファイルを読み込む、もしファイルがないなら「file not found」を表示させる。

ファイルの中身を格納する

空のStringを宣言してread_to_string()で宣言したStringのポインタにデータを格納する。何も記載されていない場合expect内が文字列が出力される。

let mut contents = String::new();
f.read_to_string(&mut contents)
    .expect("something went wrong reading the file");

Rust勉強してみた 7日目

時計をアプリを作成する

思い付きで時計アプリを作成しようと思い立ったのでその際に調べたことを書いていく。

今回のdependencies

今回のライブラリクレートこちら

[dependencies]
termion = "*"
chrono = "0.4"

一つ目はtermionというライブラリクレート。 ターミナルを操作するのに使っている。

https://github.com/redox-os/termion

この時計アプリでは画面をクリアして、カーソルを座標(1,1)にセットしている。

eprint!("{}{}", termion::clear::All, termion::cursor::Goto(1, 2));

二つ目のchronoは現在時刻を扱うために使用している。

https://docs.rs/chrono/0.4.19/chrono/

全体的なつくりついて

はじめに可変長のint型とstring型の変数を3つずつ定義する。

let mut hour = 0;
let mut min = 0;
let mut sec = 0;

let mut hour_str;
let mut min_str;
let mut sec_str;

loopに画面初期化とカーソル(1,1)移動させる。

loop {
    eprint!("{}{}", termion::clear::All, termion::cursor::Goto(1, 2));

10より小さい場合は0をつけて、そうでなければそのまま出力するようにする。(時、分、秒共通)

// hour translate
if hour < 10 {
    hour_str = format!("{}{}", "0", hour);
} else {
    hour_str = format!("{}", hour);
}

// minute translate
if min < 10 {
    min_str = format!("{}{}", "0", min);
} else {
    min_str = format!("{}", min);
}

// second translate
if sec < 10 {
    sec_str = format!("{}{}", "0", sec);
} else {
    sec_str = format!("{}", sec);
}

format!マクロで文字列をひとまとめにeprint!マクロで出力。(なぜeprint!マクロを使うのかというとprint!マクロではなぜか文字列が出力されないため。)

let display_clock = format!("{}:{}:{}",hour_str, min_str, sec_str);

eprint!("{}", display_clock);

secに+1して、secが60ならminを+1してsecをリセット、minが60ならhourを+1してminをリセットする。 (余談だがRustにはsec++mのようなインクリメント演算子は存在しない) そして1秒間sleepする。

sec += 1;
// time calculate
if sec == 60 {
    sec = 0;
    min += 1;
}
if min == 60 {
    min = 0;
    hour += 1;
}
thread::sleep(Duration::from_secs(1));

こんな感じでいい感じに時間が表示される。 f:id:bonashochang:20210929004046p:plain

今後つけたいもの

  1. 現在時刻を取得して表示させたい。
  2. 2進数でドット絵を作成して表示させたい。

余談

Cargo.tomlを下記のようにcargo runで別々のモジュールを実行できる。

[[bin]]
name = "main"
path = "src/main.rs"

[[bin]]
name = "test"
path = "src/test.rs"

これで

cargo run --bin main

と実行すればsrc/main.rsを実行できる。

Rust勉強してみた 6日目

ライフタイム

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {

引数と返り値ともに同じライフタイムを持たせたい場合は寿樹のように書く。

これはresultをかっこの前に宣言してるのでライフタイムの影響でエラーになる。

fn smp_main2() {
    let string1 = String::from("long string is long");
    let result;
    {
        let string2 = String::from("xyz");
        result = longest(string1.as_str(), string2.as_str());
    }
    println!("The longest string is {}", result);
}

xにだけライフタイムを付与してもコンパイルには通る。

fn longest2<'a>(x: &'a str, y: &str) -> &'a str {
    x
}

これは共通のライフタイムのものを返してないからエラーになる。

fn longest3<'a>(x: &str, y: &str) -> &'a str {
    let result = String::from("really long string");
    result.as_str();
}

ジェネリックな型引数、トレイト境界、ライフタイムを指定した記法らしい。少ししか読めない(´・ω・`)

fn smp_main5() {
    use std::fmt::Display;

    fn longest_with_an_announcement<'a, T>(x: &'a str, y: &'a str, ann: T) -> &'a str
    where
        T: Display,
    {
        println!("Announcement! {}", ann);
        if x.len() > y.len() {
            x
        } else {
            y
        }
    }
}

閑話休題

lib.rsの作成を今まで

nvim lib.rs

として作成していたが実は下記のように記載するらしい。 ライブラリプロジェクトという。

cargo new adder --lib

自動生成されたテストモジュールの中身。テストで使う?

#[cfg(test)]
mod tests {
    #[test]
    fn it_works() {
        assert_eq!(2 + 2, 4);
    }
}

テスト

メソッドのテストは下記のようにできる。

#[derive(Debug)]
struct Rectangle {
    width: u32,
    height: u32,
}

impl Rectangle {
    fn can_hold(&self, other: &Rectangle) -> bool {
        self.width > other.width && self.height > other.height
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn larger_can_hold_smaller() {
        let larger = Rectangle {
            width: 8,
            height: 7,
        };
        let smaller = Rectangle {
            width: 5,
            height: 1,
        };

        assert!(larger.can_hold(&smaller));
    }

    #[test]
    fn smaller_cannot_hold_larger() {
        let larger = Rectangle {
            width: 8,
            height: 7,
        };
        let smaller = Rectangle {
            width: 5,
            height: 1,
        };

        assert!(!smaller.can_hold(&larger));
    }
}

fn main() {}

asser_eq!は値が等しいか比較する。

pub fn add_two(a: i32) -> i32 {
    a + 2
}

-- omit
    #[test]
    fn it_adds_two() {
        assert_eq!(4, add_two(2));
    }

ろぐ

https://github.com/jacoloves/lab/commit/ba05cc6019222427e954504a9f9b8dcdfc5711ed

Rust勉強してみた 5日目

HashMap

let text = "hello world wonderful world";
let mut map = HashMap::new();
for word in text.split_whitespace() {
    let count = map.entry(word).or_insert(0);
    *count += 1;
}

これはtextに記載されている文字列から空白を削除して、単語を数える処理。 word変数がキーになり、count変数が出現数になる。 初めにcount=0を代入して、出現するたびにcountを+1していく。

panic

この記述でpanicを呼び出せる。

panic!("crash and burn");

RUST_BACKTRACE=1を入力して実行すると、panicになるまでの関数が使われた関数がわかる。

RUST_BACKTRACE=1 cargo run

全然関係ないがmatch関数を使うとファイルが存在しなときに自動で作成してくれたりして便利。

fn file_open_error() {
    let f = File::open("hello.txt");

    let f = match f {
        Ok(file) => file,
        Err(ref error) if error.kind() == ErrorKind::NotFound => match File::create("hello.txt") {
            Ok(fc) => fc,
            Err(e) => {
                panic!("Tried to create file but there was a problem: {:?}", e)
            }
        },
        Err(error) => {
            panic!("There was a problem opening the file: {:?}", error)
        }
    };
}

expect関数はpanic!のエラーメッセージをこちらで決められる。

    let f = File::open("hello2.txt").expect("Failed to open hello2.txt");

ファイルを開けたら→ファイルの中身を読み込む一連の処理を書き方次第で短くできる。

第一段階(match処理で愚直に分岐を書く)

fn read_username_from_file() -> Result<String, io::Error> {
    let f = File::open("hello2.txt");

    let mut f = match f {
        Ok(file) => file,
        Err(e) => return Err(e),
    };

    let mut s = String::new();

    match f.read_to_string(&mut s) {
        Ok(_) => Ok(s),
        Err(e) => Err(e),
    }
}

第二段階(?を使ってエラーかどうかを確認する)

fn read_username_from_file2() -> Result<String, io::Error> {
    let mut f = File::open("hello.txt");
    let mut s = String::new();
    f.read_to_string(&mut s)?;
    Ok(s)
}

第三段階(?を二つ使ってオープンの判定と記載の判定をする。ここまでくると呪文。可読性はよいのだろうか?)

fn read_username_from_file3() -> Result<String, io::Error> {
    let mut s = String::new();

    File::open("hello.txt")?.read_to_string(&mut s)?;

    Ok(s)
}

panic!使う前にまずResultで返すことを念頭に置いて設計するといいらしい。

ジェネリクス

値の型が一つのときは、

struct Point2<T> {
    x: T,
    y: T,
}

値の型が二つあるときは、

struct Point<T, U> {
    x: T,
    y: U,
}

値が増えたらメンテナンスが大変そうだから、長くなった場合はソースを見直す必要がある。