use actix_web::{get, post, web, App, HttpServer, HttpResponse};
use askama::Template;
use askama_actix::TemplateToResponse;
use sqlx::{Row, SqlitePool};
#[derive(Template)]
#[template(path = "hello.html")]
struct HelloTemplate {
name: String,
}
#[get("/hello/{name}")]
async fn hello(name: web::Path<String>) -> HttpResponse {
let hello = HelloTemplate {
name: name.into_inner(),
};
hello.to_response()
}
#[derive(Template)]
#[template(path = "todo.html")]
struct TodoTemplate {
tasks: Vec<String>,
}
#[derive(serde::Deserialize)]
struct Task {
id: String,
}
#[post("/update")]
async fn update(pool: web::Data<SqlitePool>, form: web::Form<Task>) -> HttpResponse {
let task = form.into_inner();
sqlx::query("DELETE FROM tasks WHERE task = ?")
.bind(&task.id)
.execute(pool.as_ref())
.await
.unwrap();
HttpResponse::Ok().finish()
}
#[get("/")]
async fn todo(pool: web::Data<SqlitePool>) -> HttpResponse {
let rows = sqlx::query("SELECT task FROM tasks;")
.fetch_all(pool.as_ref())
.await
.unwrap();
let tasks: Vec<String> = rows
.iter()
.map(|row| row.get::<String, _>("task"))
.collect();
let todo = TodoTemplate { tasks };
todo.to_response()
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let pool = SqlitePool::connect("sqlite::memory:").await.unwrap();
sqlx::query("CREATE TABLE tasks (task TEXT)")
.execute(&pool)
.await
.unwrap();
sqlx::query("INSERT INTO tasks(task) VALUES('task1')")
.execute(&pool)
.await
.unwrap();
sqlx::query("INSERT INTO tasks(task) VALUES('task2')")
.execute(&pool)
.await
.unwrap();
sqlx::query("INSERT INTO tasks(task) VALUES('task3')")
.execute(&pool)
.await
.unwrap();
HttpServer::new(move|| {
App::new()
.service(hello)
.service(todo)
.app_data(web::Data::new(pool.clone()))
})
.bind(("0.0.0.0", 8080))?
.run()
.await
}
Category: Rust
【Rust】Actixでaskama(template)の使い方
axumの方が優勢な気がしますが、actixでも問題なくルーティングとテンプレートを使った開発ができそうです。
use actix_web::{get, web, App, HttpServer, HttpResponse};
use askama::Template;
use askama_actix::TemplateToResponse;
#[derive(Template)]
#[template(path = "hello.html")]
struct HelloTemplate {
name: String,
}
#[derive(Template)]
#[template(path = "todo.html")]
struct TodoTemplate {
tasks: Vec<String>,
}
#[get("/hello/{name}")]
async fn hello(name: web::Path<String>) -> HttpResponse {
let hello = HelloTemplate {
name: name.into_inner(),
};
hello.to_response()
}
#[get("/")]
async fn todo() -> HttpResponse {
let tasks = vec!["task1".to_string(), "task2".to_string(), "task3".to_string()];
let todo = TodoTemplate { tasks };
todo.to_response()
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| App::new().service(hello).service(todo))
.bind(("0.0.0.0", 8080))?
.run()
.await
}
<html>
<head>
<title>ToDo</title>
</head>
<body>
<ul>
{% for task in tasks %}
<li>{{ task }}</li>
{% endfor %}
</ul>
</body>
</html>
【Rust】エラーハンドリングの基本
実行した時にエラーになりうる関数はResult
Ok(File), Err(Error)など
use std::fs::File;
fn main() {
let result = File::open("file.txt");
match result {
Ok(file) => {
println!("ファイルのオープンに成功しました!");
}
Err(error) => {
println!("ファイルのオープンに失敗しました!");
}
}
}
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.70s
Running `target/debug/app`
ファイルのオープンに失敗しました!
関数化すると
use std::fs::File;
#[derive(Debug)]
struct MyError {
message: String,
}
fn main() {
let result = open_file();
println!("{:?}", result);
}
fn open_file() -> Result<File, MyError> {
let result = File::open("file.txt");
match result {
Ok(file) => {
return Ok(file);
}
Err(error) => {
let error = MyError{ message: "ファイルのオープンに失敗しました!".to_string()};
return Err(error);
}
}
}
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.30s
Running `target/debug/app`
Err(MyError { message: “ファイルのオープンに失敗しました!” })
### エラーが発生した時、発生したエラーをそのまま返す
fn main() {
let result = open_file();
println!("{:?}", result);
}
fn open_file() -> Result<File, std::io::Error> {
let result = File::open("file.txt");
match result {
Ok(file) => {
return Ok(file);
}
Err(error) => {
return Err(error);
}
}
}
“?”とすれば、その時点でエラーを返す。下記の方が簡略して書ける。
fn main() {
let result = open_file();
println!("{:?}", result);
}
fn open_file() -> Result<(), std::io::Error> {
let result = File::open("file.txt");
let file = result?;
Ok(())
}
File::open(SCHEDULE_FILE).unwrap()のunwrap()はエラーだたった時、エラー内容を表示してそのまま終了してしまう。
Result<(), std::io::Error> と書いた場合は、エラーだったときに、終了せずにエラーを返す。
【Rust】Unit Testの実用的な書き方
use std::{fs::File, io::{BufReader, BufWriter}};
use serde::{Serialize, Deserialize};
use clap::{Parser, Subcommand};
use chrono::NaiveDateTime;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
struct Schedule {
id: u64,
subject: String,
start: NaiveDateTime,
end: NaiveDateTime,
}
impl Schedule {
fn intersects(&self, other: &Schedule) -> bool {
self.start < other.end && other.start < self.end
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
struct Calendar {
schedules: Vec<Schedule>,
}
const SCHEDULE_FILE : &str = "schedule.json";
#[derive(Parser)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
List,
Add {
subject: String,
start: NaiveDateTime,
end: NaiveDateTime,
}
}
fn main() {
let options = Cli::parse();
match options.command {
Commands::List => show_list(),
Commands::Add { subject, start, end }
=> add_schedule(subject, start, end),
}
}
fn show_list() {
let calendar : Calendar = {
let file = File::open(SCHEDULE_FILE).unwrap();
let reader = BufReader::new(file);
serde_json::from_reader(reader).unwrap()
};
println!("ID\tSTART\tEND\tSUBJECT");
for schedule in calendar.schedules {
println!(
"{}\t{}\t{}\t{}",
schedule.id, schedule.start, schedule.end, schedule.subject
);
}
}
fn add_schedule(
subject: String,
start: NaiveDateTime,
end: NaiveDateTime,
) {
let mut calendar : Calendar = {
let file = File::open(SCHEDULE_FILE).unwrap();
let reader = BufReader::new(file);
serde_json::from_reader(reader).unwrap()
};
let id = calendar.schedules.len() as u64;
let new_schedule = Schedule {
id,
subject,
start,
end,
};
for schedule in &calendar.schedules {
if schedule.intersects(&new_schedule) {
println!("エラー: 予定が重複しています");
return;
}
}
calendar.schedules.push(new_schedule);
{
let file = File::create(SCHEDULE_FILE).unwrap();
let writer = BufWriter::new(file);
serde_json::to_writer(writer, &calendar).unwrap();
}
println!("予定を追加しました。");
}
#[cfg(test)]
mod tests {
use super::*;
fn naive_date_time(
year: i32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
) -> NaiveDateTime {
chrono::NaiveDate::from_ymd_opt(year, month, day)
.unwrap()
.and_hms_opt(hour, minute, second)
.unwrap()
}
fn test_schedule_intersects(
h0: u32,
m0: u32,
h1: u32,
m1: u32,
should_intersects: bool
) {
let schedule = Schedule {
id: 0,
subject: "既存予定1".to_string(),
start: naive_date_time(2024, 1, 1, h0, m0, 0),
end: naive_date_time(2024, 1, 1, h1, m1, 0),
};
let new_schedule = Schedule {
id: 999,
subject: "新規予定".to_string(),
start: naive_date_time(2024, 1, 1, 19, 0, 0),
end: naive_date_time(2024, 1, 1, 20, 0, 0),
};
assert_eq!(should_intersects, schedule.intersects(&new_schedule));
}
#[test]
fn test_schedule_intersects_1(){
test_schedule_intersects(18, 15, 19, 15, true)
}
#[test]
fn test_schedule_intersects_2(){
test_schedule_intersects(19, 45, 20, 45, true)
}
}
$ cargo test
Compiling calendar v0.1.0 (/home/vagrant/dev/work/rusty/calendar)
Finished `test` profile [unoptimized + debuginfo] target(s) in 0.40s
Running unittests src/main.rs (target/debug/deps/calendar-867e0c10670d3913)
running 2 tests
test tests::test_schedule_intersects_2 … ok
test tests::test_schedule_intersects_1 … ok
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
なるほど、これはかなり参考になる。
【Rust】パッケージとモジュール
$ cargo new –lib my_package
src/lib.rs
pub fn hello(name: &str) {
println!("Hello, {}", name);
}
src/bin/bin.rs
use my_package::hello;
fn main() {
hello("bin_1");
}
$ cargo run –bin bin
【Rust】CLIプログラムの書き方
これはかなり面白い。というか、参考にさせていただきます!
use std::{collections::HashMap, fs::OpenOptions};
use chrono::NaiveDate;
use clap::{Args, Parser, Subcommand};
use csv::{Writer, Reader, WriterBuilder};
use serde::{Deserialize, Serialize};
#[derive(Parser)]
#[clap(version = "1.0")]
struct App {
#[clap(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
/// 新しい口座を作る
New(NewArgs),
/// 口座に入金する
Deposit(DepositArgs),
/// 口座から出金する
Withdraw(WithdrawArgs),
/// CSVからインポートする
Import(ImportArgs),
/// レポートを出力する
Report(ReportArgs),
}
#[derive(Args)]
struct NewArgs {
account_name: String,
}
impl NewArgs {
fn run(&self) {
let file_name = format!("{}.csv", self.account_name);
let mut writer = Writer::from_path(file_name).unwrap();
writer
.write_record(["日付", "用途", "金額"])
.unwrap();
writer.flush().unwrap();
}
}
#[derive(Args)]
struct DepositArgs {
account_name: String,
date: NaiveDate,
usage: String,
amount: u32,
}
impl DepositArgs {
fn run(&self) {
let open_option = OpenOptions::new()
.write(true)
.append(true)
.open(format!("{}.csv", self.account_name))
.unwrap();
let mut writer = Writer::from_writer(open_option);
writer
.write_record([
self.date.format("%Y-%m-%d").to_string(),
self.usage.to_string(),
self.amount.to_string()])
.unwrap();
writer.flush().unwrap();
}
}
#[derive(Args)]
struct WithdrawArgs {
account_name: String,
date: NaiveDate,
usage: String,
amount: u32,
}
impl WithdrawArgs {
fn run(&self) {
let open_option = OpenOptions::new()
.write(true)
.append(true)
.open(format!("{}.csv", self.account_name))
.unwrap();
let mut writer = Writer::from_writer(open_option);
writer
.write_record([
self.date.format("%Y-%m-%d").to_string(),
self.usage.to_string(),
format!("-{}", self.amount),
])
.unwrap();
writer.flush().unwrap();
}
}
#[derive(Args)]
struct ImportArgs {
src_file_name: String,
dst_account_name: String,
}
impl ImportArgs {
fn run(&self) {
let open_option = OpenOptions::new()
.write(true)
.append(true)
.open(format!("{}.csv", self.dst_account_name))
.unwrap();
let mut writer = WriterBuilder::new()
.has_headers(false)
.from_writer(open_option);
let mut reader = Reader::from_path(&self.src_file_name).unwrap();
for result in reader.deserialize() {
let record: Record = result.unwrap();
writer.serialize(record).unwrap();
}
}
}
#[derive(Serialize, Deserialize)]
struct Record {
日付: NaiveDate,
用途: String,
金額: i32,
}
#[derive(Args)]
struct ReportArgs {
files: Vec<String>,
}
impl ReportArgs {
fn run(&self) {
let mut map = HashMap::new();
for file in &self.files {
let mut reader = Reader::from_path(file).unwrap();
for result in reader.records() {
let record = result.unwrap();
let amount : i32 = record[2].parse().unwrap();
let date: NaiveDate = record[0].parse().unwrap();
let sum = map.entry(date.format("%Y-%m").to_string())
.or_insert(0);
*sum += amount;
}
}
println!("{:?}", map);
}
}
fn main() {
let args = App::parse();
match args.command {
Command::New(args) => args.run(),
Command::Deposit(args) => args.run(),
Command::Withdraw(args) => args.run(),
Command::Import(args) => args.run(),
Command::Report(args) => args.run(),
}
}
【Rust】コマンド入力とargs
fn main() {
let command_name = std::env::args().nth(0).unwrap_or("CLI".to_string());
let name = std::env::args().nth(1).unwrap_or("WORLD".to_string());
println!("Hello {} via {}!", name, command_name);
}
Running `target/debug/kakeibo arg1 arg2`
Hello arg1 via target/debug/kakeibo!
use clap::Parser;
#[derive(Parser)]
#[clap(version = "1.0")]
struct Args {
arg1: String,
arg2: String
}
fn main() {
let _args = Args::parse();
}
use clap::{Parser, Subcommand};
#[derive(Parser)]
#[clap(version = "1.0")]
struct App {
#[clap(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
/// 新しい口座を作る
New,
/// 口座に入金する
Deposit,
/// 口座から出金する
Withdraw,
/// CSVからインポートする
Import,
/// レポートを出力する
Report,
}
fn main() {
let _args = App::parse();
}
$ ./target/debug/kakeibo -h
Usage: kakeibo
Commands:
new 新しい口座を作る
deposit 口座に入金する
withdraw 口座から出金する
import CSVからインポートする
report レポートを出力する
help Print this message or the help of the given subcommand(s)
Options:
-h, –help Print help
-V, –version Print version
【Rust】字句解析(Lexical analysis)の初級
use std::io::stdin;
fn main() {
let mut memory = Memory {
slots: vec![0.0; 10],
}
let mut prev_result: f64 = 0.0;
for line in stdin().lines() {
let line = line.unwrap();
if line.is_empty() {
break;
}
let tokens: Vec<&str> = line.split(char::is_whitespace).collect();
let is_memory = tokens[0].starts_with("mem");
if is_memory && tokens[0].ends_with('+') {
add_and_print_memory(&mut memory, tokens[0], prev_result);
continue;
} else if is_memory && tokens[0].ends_with('-') {
add_and_print_memory(&mut memory, tokens[0], -prev_result);
continue;
}
let left = eval_token(tokens[0], &memory);
let right = eval_token(tokens[2], &memory);
let result = eval_expression(left, tokens[1], right);
print_output(result);
prev_result = result;
}
}
struct Memory {
slots: Vec<f64>,
}
fn add_and_print_memory(memory: &mut Memory, token: &str, prev_result: f64) {
let slot_index: usize = token[3..token.len() - 1].parse().unwrap();
memory.slots[slot_index] += prev_result;
print_output(memory.slots[slot_index]);
}
fn eval_token(token: &str, memory: &Memory) -> f64 {
if token.starts_with("mem") {
let slot_index: usize = token[3..].parse().unwrap();
memory.slots[slot_index]
} else {
token.parse().unwrap()
}
}
fn eval_expression(left: f64, operator: &str, right: f64) -> f64 {
match operator {
"+" => left + right,
"-" => left - right,
"*" => left * right,
"/" => left / right,
_ => {
unreachable!()
}
}
}
fn print_output(value: f64) {
println!(" => {}", value);
}
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.49s
Running `target/debug/app`
1 + 2
=> 3
mem1+
=> 3
3 + 4
=> 7
mem2-
=> -7
mem1 * mem2
=> -21
【Rust】ポーカー
このソース見た時、凄い感慨したんよね。絵柄と1~13の数字でdeckを作るところ。
pocker chaseの裏側ってどうやってんだろうずっと思ってたんだけど、これを見ると、カードの配り方や、最後の役完成時の判定をどうやってるかがわかる。素晴らしい!
use rand::seq::SliceRandom;
#[derive(Debug, Clone, Copy, PartialEq)]
enum Suit {
Club,
Diamond,
Heart,
Spade,
}
#[derive(Debug, Clone, Copy, PartialEq)]
struct Card {
suit: Suit,
rank: i32,
}
fn main() {
let mut deck: Vec<Card> = Vec::new();
let suits = [Suit::Club, Suit::Diamond, Suit::Heart, Suit::Spade];
for suit in suits {
for rank in 1..=13 {
deck.push(Card {suit, rank});
}
}
let mut rng = rand::thread_rng();
deck.shuffle(&mut rng);
let mut hand: Vec<Card> = Vec::new();
for _ in 0..5 {
hand.push(deck.pop().unwrap());
}
hand.sort_by(|a, b| a.rank.cmp(&b.rank));
for (i, card) in hand.iter().enumerate() {
println!("{:}: {:?} {:}", i +1, card.suit, card.rank);
}
println!("入れ替えたいカードの番号を入力してください(例: 1 2 3)");
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let numbers : Vec<usize> = input.split_whitespace()
.map(|x| x.parse().unwrap())
.collect::<Vec<usize>>();
for number in numbers {
hand[number - 1] = deck.pop().unwrap();
}
hand.sort_by(|a, b| a.rank.cmp(&b.rank));
for (i, card) in hand.iter().enumerate() {
println!("{:}: {:?} {:}", i +1, card.suit, card.rank);
}
let suit = hand.first().unwrap().suit;
let flash = hand.iter().all(|c| c.suit == suit);
let mut count = 0;
for i in 0..hand.len() - 1 {
for j in i + 1..hand.len() {
if hand[i].rank == hand[j].rank {
count += 1;
}
}
}
if flash {
println!("flash!");
} else if count >= 3 {
println!("スリーカード!");
} else if count == 2 {
println!("2ペア");
} else if count == 1 {
println!("1ペア");
} else {
println!("役なし...");
}
}
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.62s
Running `target/debug/app`
1: Spade 3
2: Spade 4
3: Club 5
4: Diamond 8
5: Spade 13
入れ替えたいカードの番号を入力してください(例: 1 2 3)
1 2 3 4
1: Diamond 10
2: Club 10
3: Diamond 12
4: Heart 12
5: Spade 13
2ペア
【Rust】dbg!によるデバッグ
fn main() {
println!("1 + 1 = ??");
println!("??の値を入力してください。");
let mut ans_input = String::new();
std::io::stdin().read_line(&mut ans_input).unwrap();
dbg!(ans_input);
}
Compiling app v0.1.0 (/home/vagrant/dev/rust/app)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.43s
Running `target/debug/app`
1 + 1 = ??
??の値を入力してください。
2
[src/main.rs:6:5] ans_input = “2\n”