「Rust」カテゴリーアーカイブ

RUST勉強中、8/13の積み上げ

前回の続き。

indexにダミーデータを使用していましたが、

この部分を実際のDBのデータを表示するようにしています。

use actix_web::{get, web, App, HttpResponse, HttpServer, ResponseError};
use thiserror::Error;
use askama::Template;
use r2d2::Pool;
use r2d2_sqlite::SqliteConnectionManager;
use rusqlite::params;

struct TodoEntry {
    id: u32,
    text: String,
}

#[derive(Template)]
#[template(path = "index.html")]
struct IndexTemplate {
    entries: Vec<TodoEntry>,
}

#[derive(Error, Debug)]
enum MyError {
    #[error("Failed to render HTML")]
    AskamaError(#[from] askama::Error),

    #[error("Failed to get connection")]
    ConnectionPoolError(#[from] r2d2::Error),

    #[error("Failed SQL execution")]
    SqliteError(#[from] rusqlite::Error),
}

impl ResponseError for MyError {}

#[get("/")]
async fn index(db: web::Data<Pool<SqliteConnectionManager>>) -> Result<HttpResponse, MyError> {
    let conn = db.get()?;
    let mut statement = conn.prepare("SELECT id, text FROM todo")?;
    let rows = statement.query_map(params![], |row| {
        let id = row.get(0)?;
        let text = row.get(1)?;
        Ok(TodoEntry { id, text })
    })?;

    let mut entries = Vec::new();
    for row in rows {
        entries.push(row?);
    }

    let html = IndexTemplate { entries };
    let response_body = html.render()?;
    Ok(HttpResponse::Ok()
        .content_type("text/html")
        .body(response_body))
}



#[actix_rt::main]
async fn main() -> Result<(), actix_web::Error> {
    let manager = SqliteConnectionManager::file("todo.db");
    let pool = Pool::new(manager).expect("Failed to inithialize the connection pool.");
    let conn = pool.get().expect("Failed to get the connection from the pool.");
    conn.execute(
        "CREATE TABLE IF NOT EXISTS todo (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            text TEXT NOT NULL
        )",
        params![],
    )
    .expect("Failed to create a table 'todo'.");
    HttpServer::new(move || App::new().service(index).app_data(pool.clone()))
        .bind("0.0.0.0:8080")?
        .run()
        .await?;
    Ok(())
}

前回の記事で、

テキストではdata(pool.clone())を使用していましたが、
ワーニングが出るので、調べたところ、
推奨されていない処理なので、app_dataを使え、と言うことでした。

と書きましたが、

app_data()を使用するとうまく動作せず、

解決策が見つからないので、

ひとまずdata()に戻しています。

RUST勉強中、7/31の積み上げ

askamaクレートを使用することにより、htmlテンプレートを使用してWebページを作成することができます。

表示するデータは将来的にはデータベースを使用するのですが、

それはまた次回の話なので、今回はダミーデータを使用しています。

[package]
name = "todo"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-web = { version = "4.1" }
actix-rt = { version = "2.7" }
thiserror = { version = "1.0" }
askama = { version = "0.11" }
<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <title>Todo App</Applet></title>
</head>

<body>
    <div>
        {% for entry in entries %}
        <div>
            <div>id: {{ entry.id }}, text: {{ entry.text }}</div>
            <form action="/delete" method="post">
                <input type="hidden" name=""id" value="{{ entry.id }}">
                <button>delete</button>
            </form>
        </div>
        {% endfor %}
    </div>

    <form action="/add" method="post">
        <div>
            <input name="text">
        </div>
        <div>
            <button>add</button>
        </div>
    </form>
</body>
</html>
use actix_web::{get, App, HttpResponse, HttpServer, ResponseError};
use thiserror::Error;
use askama::Template;

struct TodoEntry {
    id: u32,
    text: String,
}

#[derive(Template)]
#[template(path = "index.html")]
struct IndexTemplate {
    entries: Vec<TodoEntry>,
}

#[derive(Error, Debug)]
enum MyError {
    #[error("Failed to render HTML")]
    AskamaError(#[from] askama::Error),
}

impl ResponseError for MyError {}

#[get("/")]
async fn index() -> Result<HttpResponse, MyError> {
    let mut entries = Vec::new();
    entries.push(TodoEntry {
        id: 1,
        text: "First entry".to_string(),
    });
    entries.push(TodoEntry {
        id: 2,
        text: "Second entry".to_string(),
    });

    let html = IndexTemplate { entries };
    let response_body = html.render()?;
    Ok(HttpResponse::Ok()
        .content_type("text/html")
        .body(response_body))
}



#[actix_rt::main]
async fn main() -> Result<(), actix_web::Error> {
    HttpServer::new(move || App::new().service(index))
        .bind("0.0.0.0:8080")?
        .run()
        .await?;
    Ok(())
}

RUST勉強中、7/24の積み上げ

エラーのハンドリング定義を追加します。

使用するクレートはthiserrorを使用します。

[package]
name = "todo"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-web = { version = "4.1" }
actix-rt = { version = "2.7" }
thiserror = { version = "1.0" }
use actix_web::{get, App, HttpResponse, HttpServer, ResponseError};
use thiserror::Error;

#[derive(Error, Debug)]
enum MyError {}

impl ResponseError for MyError {}

#[get("/")]
async fn index() -> Result<HttpResponse, MyError> {
    let response_body = "Hello world";
    Ok(HttpResponse::Ok().body(response_body))
}

#[actix_rt::main]
async fn main() -> Result<(), actix_web::Error> {
    HttpServer::new(move || App::new().service(index))
        .bind("0.0.0.0:8080")?
        .run()
        .await?;
    Ok(())
}

この様にMyError等の形にすれば、エラー内容によってことなるエラー型を扱う様にすることができます。

RUST勉強中、7/17の積み上げ

今週からWebフレームワークを使用したサンプルコードになります。

まずはお馴染みのHello world。

[package]
name = "todo"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
actix-web = { version = "4.1" }
actix-rt = { version = "2.7" }
use actix_web::{get, App, HttpResponse, HttpServer};

#[get("/")]
async fn index() -> Result<HttpResponse, actix_web::Error> {
    let response_body = "Hello world";
    Ok(HttpResponse::Ok().body(response_body))
}

#[actix_rt::main]
async fn main() -> Result<(), actix_web::Error> {
    HttpServer::new(move || App::new().service(index))
        .bind("0.0.0.0:8080")?
        .run()
        .await?;
    Ok(())
}

ちなみに、サンプルコードにはクレートの追加にcargo addコマンドを使用していますが、

WSLにはcargo addコマンドがないので、

cargo.tomlファイルの[dependencies]の蘭に記入する必要があります。

なお、バージョンが解らないときは、

cargo searchコマンドでリポジトリを探してくれます。

RUST勉強中、7/3の積み上げ

逆ポーランド記法計算機にanyhowクレートを実装していきます。

use anyhow::{bail, ensure, Context, Result};

use clap::Parser;
use std::fs::File;
use std::io::{stdin, BufRead, BufReader};

struct RpnCalculator(bool);

impl RpnCalculator {
    pub fn new(verbose: bool) -> Self {
        Self(verbose)
    }

    pub fn eval(&self, formula: &str) -> Result<i32> {
        let mut tokens = formula.split_whitespace().rev().collect::<Vec<_>>();
        self.eval_inner(&mut tokens)
    }

    fn eval_inner(&self, tokens: &mut Vec<&str>) -> Result<i32> {
        let mut stack = Vec::new();
        let mut pos = 0;

        while let Some(token) = tokens.pop() {
            pos += 1;

            if let Ok(x) = token.parse::<i32>() {
                stack.push(x);
            } else {
                let y = stack.pop().context(format!("invalid syntax at {}", pos))?;
                let x = stack.pop().context(format!("invalid syntax at {}", pos))?;
                let res = match token {
                    "+" => x + y,
                    "-" => x - y,
                    "*" => x * y,
                    "/" => x / y,
                    "%" => x % y,
                    _ => bail!("invalid token at {}", pos),
                };
                stack.push(res);
            }

            if self.0 {
                println!("{:?} {:?}", tokens, stack);
            }
        }

        ensure!(stack.len() == 1, "invalid synyax");

        Ok(stack[0])
    }
}

#[derive(Parser, Debug)]
#[clap(
    name = "My RPM program",
    version = "1.0.0",
    author = "Your name",
    about = "Super awesome sample RPM calculator"
)]
struct Opts {
    #[clap(short, long)]
    verbose: bool,

    #[clap(name = "FILE")]
    formula_file: Option<String>,
}


fn main() -> Result<()> {
    let opts = Opts::parse();

    if let Some(path) = opts.formula_file {
        // ファイルから読み出す
        let f = File::open(path)?;
        let reader = BufReader::new(f);
        run(reader, opts.verbose)
    } else {
        // 標準入力から読み出す
        let stdin = stdin();
        let reader = stdin.lock();
        run(reader, opts.verbose)
    }
}

fn run<R: BufRead>(reader: R, verbose: bool) -> Result<()> {
    let calc = RpnCalculator::new(verbose);

    for line in reader.lines() {
        let line = line?;
        match calc.eval(&line) {
            Ok(answer) => println!("{}", answer),
            Err(e) => eprintln!("{:#?}", e),
        }
    }

    Ok(())
}

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

    #[test]
    fn test_ok() {
        let calc = RpnCalculator::new(false);
        assert_eq!(calc.eval("5"), 5);
        assert_eq!(calc.eval("50"), 50);
        assert_eq!(calc.eval("-50"), -50);

        assert_eq!(calc.eval("2 3 +"), 5);
        assert_eq!(calc.eval("2 3 *"), 6);
        assert_eq!(calc.eval("2 3 -"), -1);
        assert_eq!(calc.eval("2 3 /"), 0);
        assert_eq!(calc.eval("2 3 %"), 2);
    }

    #[test]
    #[should_panic]
    fn test_ng() {
        let calc = RpnCalculator::new(false);
        assert!(calc.eval("").is_err());
        assert!(calc.eval("1 1 1 +").is_err());
        assert!(calc.eval("+ 1 1").is_err());
    }
}

RUST勉強中、6/26の積み上げ

前回の続き

今回はthiserrorクレートを使用したエラーハンドリングです。

[package]
name = "samplecli"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "3.1.6", features = ["derive"] }
anyhow = { version = "1.0" }
thiserror = { version = "1.0" }
use thiserror::Error;

#[derive(Error, Debug)]
enum MyError {
    #[error("failed to read string from {0}")]
    ReadError(String),
    #[error(transparent)]
    ParseError(#[from] std::num::ParseIntError),
}

fn get_int_from_file() -> Result<i32, MyError> {
    let path = "number.txt";

    let num_str = 
        std::fs::read_to_string(path).map_err(|_| MyError::ReadError(path.into()))?;

    num_str
        .trim()
        .parse::<i32>()
        .map(|t| t * 2)
        .map_err(MyError::from)
}

fn main() {
    match get_int_from_file() {
        Ok(x) => println!("{}", x),
        Err(e) => println!("{:#}", e),
    }
}

RUST勉強中、6/4の積み上げ

前回の続き。

anyhowクレートを使用してエラー処理をハンドリングするコードです。

[package]
name = "samplecli"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
clap = { version = "3.1.6", features = ["derive"] }
anyhow = { version = "1.0.57" }
use anyhow::{Context, Result};

fn get_int_from_file() -> Result<i32> {
    let path = "number.txt";

    let num_str = std::fs::read_to_string(path)
        .with_context(|| format!("failed to read string from {}", path))?;

    num_str
        .trim()
        .parse::<i32>()
        .map(|t| t * 2)
        .context("failed to parse string")
}

fn main() {
    match get_int_from_file() {
        Ok(x) => println!("{}", x),
        Err(e) => println!("{}", e),
    }
}

RUST勉強中、5/29の積み上げ

あ、今日肉の日じゃん。

use std::fmt;

enum MyError {
    Io(std::io::Error),
    Num(std::num::ParseIntError),
}

impl fmt::Display for MyError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            MyError::Io(cause) => write!(f, "I/O Error: {}", cause),
            MyError::Num(cause) => write!(f, "Parse Error: {}", cause),
        }
    }
}

fn get_int_from_file() -> Result<i32, MyError> {
    let path = "number.txt";

    let num_str = std::fs::read_to_string(path).map_err(|e| MyError::Io(e))?;

    num_str
        .trim()
        .parse::<i32>()
        .map(|t| t * 2)
        .map_err(|e| MyError::Num(e))
}

fn main() {
    match get_int_from_file() {
        Ok(x) => println!("{}", x),
        Err(e) => match e {
            MyError::Io(cause) => println!("I/O Error: {}", cause),
            MyError::Num(cause) => println!("Parse Error: {}", cause),
        }
    }
}

MyErrorという独自エラー定義を使用したパターンです。

もうここまで来ると内容が理解できません。

RUST勉強中、5/21の積み上げ

エラーのハンドリングの勉強。

こちらはResultを使ってハンドリングする書き方です。

fn get_int_from_file() -> Result<i32, String> {
    let path = "number.txt";

    let num_str = std::fs::read_to_string(path).map_err(|e| e.to_string())?;

    num_str
        .trim()
        .parse::<i32>()
        .map(|t| t * 2)
        .map_err(|e| e.to_string())
}

fn main() {
    match get_int_from_file() {
        Ok(x) => println!("{}", x),
        Err(e) => println!("{}", e),
    }
}

?演算子は直前の処理のResult型を評価する書き方のようです。

RUST勉強中、5/14の積み上げ

エラーのハンドリングの勉強。

まずはハンドリングしない場合のコード。

fn get_int_from_file() -> i32 {
    let path = "number.txt";

    let num_str = std::fs::read_to_string(path).expect("failed to open the file.");
    let ret = num_str
        .trim()
        .parse::<i32>()
        .expect("failed to parse string to a number.");

    ret * 2
}

fn main() {
    println!("{}", get_int_from_file());
}
taki@DESKTOP-4VF3GEO:~/rust/samplecli$ echo 42 > number.txt
taki@DESKTOP-4VF3GEO:~/rust/samplecli$ cargo run --bin err_panic
   Compiling samplecli v0.1.0 (/home/taki/rust/samplecli)
    Finished dev [unoptimized + debuginfo] target(s) in 0.38s
     Running `target/debug/err_panic`
84
taki@DESKTOP-4VF3GEO:~/rust/samplecli$ echo hoge > number.txt
taki@DESKTOP-4VF3GEO:~/rust/samplecli$ cargo run --bin err_panic
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/err_panic`
thread 'main' panicked at 'failed to parse string to a number.: ParseIntError { kind: InvalidDigit }', src/bin/err_panic.rs:8:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
taki@DESKTOP-4VF3GEO:~/rust/samplecli$