Rust axumでhandlebarsの利用

Cargo.toml

[dependencies]
axum-template="0.14.0"
handlebars="4.3.6"

テンプレートファイルの用意
template/index.hbs

<body class="container">
	<h1 class="display-6 my-2">{{title}}</h1>

	<p class="my-2">{{ message }}</p>
</body>

main.rs

async fn handle_index() -> axum::response::Html<String> {
	let mut params = std::collections::HashMap::new();
	params.insert("title", "Index page");
	params.insert("message", "This is sample page message!");

	let mut handlebars = handlebars::Handlebars::new();
	handlebars
		.register_template_string("hello", include_str!("template/index.hbs"));

	let template = handlebars.render("hello", &params).unwrap();
	axum::response::Html(template)
}

Rust axum Teraを使いこなす

### {% %}の利用
テンプレート内での計算などができる

<body class="container">
	<h1 class="display-6 my-2">{{title}}</h1>
	<div class="border border-primary p-3 my-3">
		{% set v1 = value * 1.08 %}
		{% set v2 = value * 1.1 %}
		<p class="my-2">value: {{ value }}</p>
		<p class="my-2">value1: {{ v1 }}</p>
		<p class="my-2">value2: {{ v2 }}</p>
	</div>
</body>

main.rs

#[tokio::main]
async fn main() {
	let app = axum::Router::new()
		.route("/:value", axum::routing::get(handle_index));

    axum::Server::bind(&"192.168.56.10:8000".parse().unwrap())
    	.serve(app.into_make_service())
    	.await
    	.unwrap();
}

async fn handle_index(axum::extract::Path(value):
		axum::extract::Path<usize>
		)-> axum::response::Html<String> {
	let tera = tera::Tera::new("template/*").unwrap();

	let mut context = tera::Context::new();
	context.insert("title", "Index page");
	context.insert("value", &value);

	let output = tera.render("index.html", &context);
	axum::response::Html(output.unwrap())
}

{% raw %}で生の文を出力する
L HTMLタグはそのまま表示される

	<div class="border border-primary p-3 my-3">
		{% raw %}
		{% set v1 = value * 1.08 %}
		{% set v2 = value * 1.1 %}
		<p class="my-2">value: {{ value }}</p>
		<p class="my-2">value1: {{ v1 }}</p>
		<p class="my-2">value2: {{ v2 }}</p>
		{% endraw %}
	</div>

テキストで繋げる

	<div class="border border-primary p-3 my-3">
		{% set v1 = value * 1.08 %}
		{% set v2 = value * 1.1 %}
		<p class="my-2">value: {{ value }}</p>
		<p class="my-2">value1: {{ value ~ " * 1.08 = " ~ v1  }}</p>
		<p class="my-2">value2: {{ value ~ " * 1.1 = " ~ v2 }}</p>
	</div>

### ifを使う

	{% if value % 2 == 0 %}
	<div class="border border-primary p-3 my-3">
		<p class="my-2">value: {{ value ~ "は偶数です。" }}</p>
	</div>
	{% else %}
	<div class="border border-primary p-3 my-3">
		<p class="my-2">value: {{ value ~ "は奇数です。" }}</p>
	</div>
	{% endif %}

更に分岐したい場合

	{% if value % 3 == 0 %}
	<div class="border border-primary p-3 my-3">
		<p class="my-2">value: {{ value ~ "はグーです。" }}</p>
	</div>
	{% elif value % 3 == 1 %}
	<div class="border border-primary p-3 my-3">
		<p class="my-2">value: {{ value ~ "はチョキです。" }}</p>
	</div> 
	{% else %}
	<div class="border border-primary p-3 my-3">
		<p class="my-2">value: {{ value ~ "はパーです。" }}</p>
	</div> 
	{% endif %}

### 繰り返し表示
main.rs

async fn handle_index()-> axum::response::Html<String> {
	let data = [
		Myform {name:"taro".to_string(), mail:"taro@yamada".to_string()},
		Myform {name:"hanako".to_string(), mail:"hanako@flower".to_string()},
		Myform {name:"sachiko".to_string(), mail:"sachiko@happy".to_string()},
		Myform {name:"jiro".to_string(), mail:"jiro@change".to_string()},
	];
	let tera = tera::Tera::new("template/*").unwrap();

	let mut context = tera::Context::new();
	context.insert("title", "Index page");
	context.insert("data", &data);

	let output = tera.render("index.html", &context);
	axum::response::Html(output.unwrap())
}

view

	<ul class="list-group">
		{% for item in data %}
		<li class="list-group-item">
			{{item.name}} &lt;{{item.mail}}&gt;
		</li>
		{% endfor %}
	</ul>

### キー&バリューのコレクション
マップにHashMapインスタンスを代入。このHashMapをテンプレート側で処理する

async fn handle_index()-> axum::response::Html<String> {
	let mut map = std::collections::HashMap::new();
	map.insert("taro", ("taro@yamada", 39));
	map.insert("hanako", ("hanako@flower", 28));
	map.insert("sachiko", ("sachiko@happy", 17));

	let tera = tera::Tera::new("template/*").unwrap();

	let mut context = tera::Context::new();
	context.insert("title", "Index page");
	context.insert("data", &map);

	let output = tera.render("index.html", &context);
	axum::response::Html(output.unwrap())
}
	<ul class="list-group">
		{% for key, value in data %}
			<li class="list-group-item">
				[{{loop.index}}] {{ key }}({{value.1}}) &lt;{{value.0}}&gt;
			</li>
		{% endfor %}
	</ul>

loop.indexは繰り返し情報がまとめられたオブジェクト

### フィルターの利用
フィルターは、あらかじめRust側で定義した関数を使ってテンプレートの表示を変換する機能
フィルター用の関数は形が決まっている
fn 関数(引数1: &value, 引数2: &HashMap)-> Result{…}

作成したフィルター関数はTeraのインスタンスに登録する

main.rs

async fn handle_index()-> axum::response::Html<String> {

	let mut tera = tera::Tera::new("template/*").unwrap();
	tera.register_filter("hello", hello_filter);

	let mut context = tera::Context::new();
	context.insert("title", "Index page");
	context.insert("name", "山田タロー");

	let output = tera.render("index.html", &context);
	axum::response::Html(output.unwrap())
}

fn hello_filter(value: &tera::Value,
	_: &std::collections::HashMap<String, tera::Value>)
	-> tera::Result<tera::Value> {
		let s = tera::try_get_value!("hello_filter", "value", String, value);
		Ok(tera::Value::String(format!("こんにちは、{}さん!", s)))
	}

テンプレート

	<div class="alert alert-primary">
		<p class="my-2">{{ name | hello}}</p>
	</div>

### フィルターでオブジェクトを扱う場合

async fn handle_index()-> axum::response::Html<String> {

	let mut tera = tera::Tera::new("template/*").unwrap();
	tera.register_filter("sample", sample_filter);

	let mut context = tera::Context::new();
	context.insert("title", "Index page");
	context.insert("id", &1);

	let output = tera.render("index.html", &context);
	axum::response::Html(output.unwrap())
}

fn sample_filter(value: &tera::Value,
	_: &std::collections::HashMap<String, tera::Value>)
	-> tera::Result<tera::Value> {
		let data = [
			("taro", "taro@yamada", 39, "male"),
			("hanako", "hanako@flower", 28, "female"),
			("sachiko", "sachiko@happy", 17, "female"),
			("jiro", "jiro@change", 6, "male")
		];
		let n = tera::try_get_value!("sample_filter", "value", usize, value);
		let item = data[n];
		Ok(tera::Value::String(format!("{}({},{})<{}>", item.0, item.3, item.2, item.1)))
	}

### フィルターの属性を利用する

async fn handle_index()-> axum::response::Html<String> {

	let mut tera = tera::Tera::new("template/*").unwrap();
	tera.register_filter("calc", calc_filter);

	let mut context = tera::Context::new();
	context.insert("title", "Index page");

	let output = tera.render("index.html", &context);
	axum::response::Html(output.unwrap())
}

fn calc_filter(_: &tera::Value,
	map: &std::collections::HashMap<String, tera::Value>)
	-> tera::Result<tera::Value> {
		let price = map.get("price").unwrap().as_f64().unwrap();
		let tax = map.get("tax").unwrap().as_f64().unwrap();
		let res = price * tax;
		Ok(tera::Value::String(format!("price:{} * tax:{} = {}", price, tax, res)))
}
	<div class="alert alert-primary">
		<p class="my-2">{{ false | calc(price=1234, tax=1.1) }}</p>
	</div>

Rust axumテンプレートエンジンTera

Rustのaxumで利用できるテンプレートエンジンは幾つかある

handlerbars: JavaScriptのHandler.jsにインスパイアされて開発されたRust用のテンプレートエンジン
minijinja: PythonのJinja2の文法を採用して作られている。
Tera: Djangoのテンプレートエンジンなどの影響を強く受けたもので、フィルターやマクロなど非常に豊富な機能を持っている

### Teraの準備
Cargo.tomlのdependenciesに以下を追加

axum-template="0.14.0"
tera="1.17.1"

### テンプレート作成
template/index.html

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>{{title}}</title>
	<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.css" rel="stylesheet" crossorigin="anonymous">
</head>
<body class="container">
	<h1 class="display-6 my-2">{{title}}</h1>
	<p class="my-2">{{message}}</p>
</body>
</html>

### axumからTeraを利用
Teraのテンプレートパスを指定してTeraのインスタンス作成
テンプレートエンジンとの間でやりとりするデータの管理はContextを使用する
レンダリングの実行はtera.render, htmlインスタンスの返却はaxum::response::Html

#[tokio::main]
async fn main() {
	let app = axum::Router::new()
		.route("/", axum::routing::get(handle_index));

    axum::Server::bind(&"192.168.56.10:8000".parse().unwrap())
    	.serve(app.into_make_service())
    	.await
    	.unwrap();
}

async fn handle_index()-> axum::response::Html<String> {
	let tera = tera::Tera::new("template/*").unwrap();

	let mut context = tera::Context::new();
	context.insert("title", "Index page");
	context.insert("message", "これはサンプルです。");

	let output = tera.render("index.html", &context);
	axum::response::Html(output.unwrap())
}

### フォームの送信

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>{{title}}</title>
	<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.css" rel="stylesheet" crossorigin="anonymous">
</head>
<body class="container">
	<h1 class="display-6 my-2">{{title}}</h1>
	<div class="alert alert-primary">
		<p class="my-2">{{message}}</p>
	</div>
	<form method="post" action="/post">
		<div class="mb-3">
			<label for="name" class="form-label">
			Your name:</label>
			<input type="text" class="form-control" name="name" id="name">
		</div>
		<div class="mb-3">
			<label for="mail" class="form-label">
			Email address:</label>
			<input type="text" class="form-control" name="mail" id="mail">
		</div>
		<input type="submit" class="btn btn-primary" value="Submit">
	</form>
</body>
</html>

/postのルーティングを用意する

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct Myform {
	name: String,
	mail: String,
}

#[tokio::main]
async fn main() {
	let app = axum::Router::new()
		.route("/", axum::routing::get(handle_index))
		.route("/post", axum::routing::post(handle_post));

    axum::Server::bind(&"192.168.56.10:8000".parse().unwrap())
    	.serve(app.into_make_service())
    	.await
    	.unwrap();
}

// 省略

async fn handle_post(axum::Form(myform): axum::Form<Myform>)
	-> axum::response::Html<String> {
		let msg = format!("I am {}<{}>.", myform.name, myform.mail);
		let tera = tera::Tera::new("template/*").unwrap();

		let mut context = tera::Context::new();
		context.insert("title", "Index page");
		context.insert("message", &msg);

		let output = tera.render("index.html", &context);
		axum::response::Html(output.unwrap())
}

Rust axumの基本

RustのWebアプリケーションフレームワーク
– active-web: 高速なパフォーマンス
– Rocket: シンプルで直感的なAPIを持つフレームワーク、わかりやすいコーディングスタイル
– axum: シンプルなフレームワークでモジュラ方式でカスタマイズが容易
– warp: 関数型プログラミングの考え方に基づいたWebフレームワーク

Cargo.toml

[dependencies]
axum="0.6.9"
hyper = { version = "0.14", features = ["full"]}
tokio = { version = "1.25", features = ["full"]}
tower = { version = "0.4", features = ["full"]}

hyper: httpライブラリ、tokio:非同期処理ライブラリ、tower:抽象化レイヤーライブラリ

### axumの基本コード
RouterとServerを用意する必要がある

#[tokio::main]
async fn main() {
	let app = axum::Router::new()
		.route("/", axum::routing::get(|| async{ "Hello, World!"}));

    axum::Server::bind(&"192.168.56.10:8000".parse().unwrap())
    	.serve(app.into_make_service())
    	.await
    	.unwrap();
}

axum::Router::newでインスタンスを作成し、routeでルーティングを追加
|| async {表示するテキスト}
await.unwrap()を忘れると処理が終了してしまう

### 複数のルーティング
getで実行する関数は別に定義しておき、それをgetの引数に指定するようにする

#[tokio::main]
async fn main() {
	let app = axum::Router::new()
		.route("/", axum::routing::get(handler_top))
		.route("/other", axum::routing::get(handler_other));

    axum::Server::bind(&"192.168.56.10:8000".parse().unwrap())
    	.serve(app.into_make_service())
    	.await
    	.unwrap();
}

async fn handler_top() -> String {
	"Hello, World!".to_string()
}

async fn handler_other() -> String {
	"This is otehr page...".to_string()
}

### パラメータを利用

#[tokio::main]
async fn main() {
	let app = axum::Router::new()
		.route("/", axum::routing::get(handler_top))
		.route("/usr/:user_id", axum::routing::get(handler_param));

    axum::Server::bind(&"192.168.56.10:8000".parse().unwrap())
    	.serve(app.into_make_service())
    	.await
    	.unwrap();
}

async fn handler_top() -> String {
	"Hello, World!".to_string()
}

async fn handler_param(axum::extract::Path(user_id):
	axum::extract::Path<String>) -> String {
	format!("User ID: {}", user_id)
}

### 複数の値を渡す

async fn main() {
	let app = axum::Router::new()
		.route("/", axum::routing::get(handler_top))
		.route("/usr/:id/:user", axum::routing::get(handler_param));

// 省略

async fn handler_param(axum::extract::Path((id, user)):
	axum::extract::Path<(usize,String)>) -> String {
	format!("User ID: {}. name:{}", id, user)
}

### クエリパラメータの利用

async fn main() {
	let app = axum::Router::new()
		.route("/", axum::routing::get(handler_top))
		.route("/qry", axum::routing::get(handler_param));
// 省略
async fn handler_param(axum::extract::Query(params):
	axum::extract::Query<std::collections::HashMap<String, String>>) -> String {
	format!("id:{}, name:{}.", params["id"], params["name"])
}

### JSONデータの出力
「serde」というパッケージを使用する。serdeは、JSONなど各種データをRustの構造体に変換するための機能を提供する
Cargo.tomlに以下を追記する

serde = { version = "1.0", features = ["derive"]}
serde_json = "1.0"

serdeのSerializeとDeserializeを指定することで、構造体のシリアライズ/デシリアライズが可能になる。
jsonを返すときは、async fn 関数() -> axum::Json{…}

### json形式で指定IDのデータを出力する

use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize, Debug)]
struct Mydata {
	name:String,
	mail:String,
	age:u32,
}


#[tokio::main]
async fn main() {
	let app = axum::Router::new()
		.route("/", axum::routing::get(handler_top))
		.route("/json/:id", axum::routing::get(handler_json));

    axum::Server::bind(&"192.168.56.10:8000".parse().unwrap())
    	.serve(app.into_make_service())
    	.await
    	.unwrap();
}

async fn handler_top() -> String {
	"Hello, World!".to_string()
}

async fn handler_json(axum::extract::Path(id):
	axum::extract::Path<usize>) -> axum::Json<serde_json::Value>{
	let data:[Mydata;3] = [
		Mydata {name:String::from("Taro"),
		mail:String::from("taro@yamada"), age:39},
		Mydata {name:String::from("Hanako"),
		mail:String::from("hanako@flower"), age:28},
		Mydata {name:String::from("Sachiko"),
		mail:String::from("sachiko@happy"), age:17},
	];
	let item = &data[id];
	let data = serde_json::json!(item);
	axum::Json(data)
}

Rust eguiでグラフィックの利用

eguiのPainterという構造体を利用する

fn main() {
    let native_options = eframe::NativeOptions::default();
    native_options.default_theme = eframe::Theme::Light;
    native_options.initial_window_size = Some(egui::Vec2 {x:400.0, y:300.0});

    let _ = eframe::run_native("My egui App", native_options,
    	Box::new(|cc| Box::new(MyEguiApp::new(cc))));
}


#[derive(Default)]
struct MyEguiApp {
	pub value:bool,
}

impl MyEguiApp {
	fn new(_cc: &eframe::CreationContext<'_>)-> Self {
		Self::default()
	}
}

impl eframe::App for MyEguiApp {
	fn update(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame){
		egui::CentralPanel::default().show(ctx, |ui|{
			ui.heading("Hello World!");
			plot(ui);
		});
	}
}

fn plot(ui: &mut egui::Ui){
	
}
fn plot(ui: &mut egui::Ui){
	ui.painter().rect_filled(
		egui::Rect::from_min_max(
			egui::Pos2::new(50.0, 50.0),
			egui::Pos2::new(150.0, 150.0)
		),
		egui::Rounding::same(20.0),
		egui::Color32::RED
	);
	ui.painter().rect_stroke(
		egui::Rect::from_min_max(
			egui::Pos2::new(100.0, 100.0),
			egui::Pos2::new(200.0, 200.0)
		),
		egui::Rounding::none(),
		egui::Stroke::new(10.0, egui::Color32::GREEN)
	);
}

円を表示する

fn plot(ui: &mut egui::Ui){
	let pos_1 = egui::Pos2::new(100.0, 100.0);
	ui.painter().circle_filled(pos_1, 50.0, egui::Color32::RED);
	let pos_2 = egui::Pos2::new(150.0, 150.0);
	let stroke_2 = egui::Stroke::from((10.0, egui::Color32::GREEN));
	ui.painter().circle_stroke(pos_2, 50.0, stroke_2);
}

直線を描く

fn plot(ui: &mut egui::Ui){
	let pos_1 = egui::Pos2::new(50.0, 50.0);
	let pos_2 = egui::Pos2::new(200.0, 200.0);
	let stroke_1 = egui::Stroke::new(5.0, egui::Color32::RED);
	let stroke_2 = egui::Stroke::new(5.0, egui::Color32::GREEN);
	ui.painter().vline(50.0, std::ops::RangeInclusive::new(50.0, 200.0), stroke_1);
	ui.painter().hline(std::ops::RangeInclusive::new(50.0, 200.0), 50.0, stroke_1);
	ui.painter().line_segment([pos_1, pos_2], stroke_2);
}

テキストを表示

fn plot(ui: &mut egui::Ui){
	ui.painter().text(
		egui::Pos2 {x:50.0, y:50.0},
		egui::Align2::LEFT_CENTER,
		"Hello!",
		egui::FontId::proportional(24.0),
		egui::Color32::RED
	);
	ui.painter().text(
		egui::Pos2 {x:50.0, y:100.0},
		egui::Align2::LEFT_CENTER,
		"sample message.",
		egui::FontId::proportional(36.0),
		egui::Color32::BLUE
	);
}

シェイプの利用

fn plot(ui: &mut egui::Ui){
	let data = vec![
		egui::Pos2::new(50.0, 100.0),
		egui::Pos2::new(250.0, 100.0),
		egui::Pos2::new(75.0, 225.0),
		egui::Pos2::new(150.0, 50.0),
		egui::Pos2::new(225.0, 225.0)
	];
	let stroke_1 = egui::Stroke::new(5.0, egui::Color32::RED);

	let mut shape_1 = eframe::epaint::PathShape::line(data, stroke_1);
	shape_1.closed = true;
	ui.painter().add(shape_1);
}

クリックした位置に描く

impl eframe::App for MyEguiApp {
	fn update(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame){
		egui::CentralPanel::default().show(ctx, |ui|{
			ui.heading("Hello World!");
			let resp = ui.allocate_response(egui::vec2(400.0, 300.0), egui::Sense::click());
			if resp.clicked(){
				let p = resp.interact_pointer_pos().unwrap();
				self.click_pos.push(p);
			}
			plot(ui, &self.click_pos);
		});
	}
}

fn plot(ui: &mut egui::Ui, pos: &Vec<egui::Pos2>){
	for p in pos {
		ui.painter().circle_filled(*p, 25.0,
			egui::Color32::from_rgba_premultiplied(255, 0, 0, 100));
	}
}

Rust eguiで主なeguiを利用

fn main() {
    let native_options = eframe::NativeOptions::default();
    native_options.default_theme = eframe::Theme::Light;
    native_options.initial_window_size = Some(egui::Vec2 {x:400.0, y:200.0});

    let _ = eframe::run_native("My egui App", native_options,
    	Box::new(|cc| Box::new(MyEguiApp::new(cc))));
}

impl Default for MyEguiApp {
	fn default() -> MyEguiApp {
		MyEguiApp {
			value:0,
		}
	}
}

#[derive(Default)]
struct MyEguiApp {
	pub value:usize,
}

impl MyEguiApp {
	fn new(_cc: &eframe::CreationContext<'_>)-> Self {
		Self::default()
	}
}

impl eframe::App for MyEguiApp {
	fn update(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame){
		egui::CentralPanel::default().show(ctx, |ui|{
			ui.heading("Hello World!");

			ui.spacing();

			let msg = format!("click {} times.", self.value);
			let label_txt = egui::RichText::new(msg)
				.size(32.0);
			let label = egui::Label::new(label_txt);
			ui.add(label);

			ui.separator();

			let btn_txt = egui::RichText::new("Click!")
				.font(egui::FontId::proportional(24.0));
			let btn = egui::Button::new(btn_txt);
			let resp = ui.add_sized(egui::Vec2{x:150.0, y:40.0}, btn);
			if resp.clicked() {
				self.value += 1;
			}
		});
	}
}

チェックボックス

impl eframe::App for MyEguiApp {
	fn update(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame){
		egui::CentralPanel::default().show(ctx, |ui|{
			ui.heading("Hello World!");

			ui.spacing();

			let msg = format!("checked = {}.", self.value);
			let label_txt = egui::RichText::new(msg)
				.size(32.0);
			let label = egui::Label::new(label_txt);
			ui.add(label);

			ui.separator();

			let check_txt = egui::RichText::new("checkbox")
				.size(24.0);
			let check = egui::Checkbox::new(&mut self.value, check_txt);
			let _resp = ui.add(check);
		});
	}
}

ラジオボタン、スライダー、ドラッグバリュー、選択ラベル、テキストエディット、パスワードなども

Rust eguiの基本

RustのフレームワークにはTauri, Druid, egiなどがある

### eguiを準備
Cargo.tomlのdependenciesを修正
eframeとeguiの2つのパッケージをインストールする
eframeはアプリケーション開発の基本機能を提供、eguiはguiライブラリ

[dependencies]
eframe = "0.21.0"
egui = "0.21.0"

アプリケーションの実行はeframeのrun_nativeを使う
eframe::run_native(name, , )
nativeOptionsはオプション設定などの情報管理
AppCreatorは構造体
AppCreatorのBox生成

### アプリの実行

fn main() {
    let native_options = eframe::NativeOptions::default();
    let _ = eframe::run_native("My egui App", native_options,
    	Box::new(|cc| Box::new(MyEguiApp::new(cc))));
}

#[derive(Default)]
struct MyEguiApp {}

impl MyEguiApp {
	fn new(_cc: &eframe::CreationContext<'_>)-> Self {
		Self::default()
	}
}

impl eframe::App for MyEguiApp {
	fn update(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame){}
}
impl eframe::App for MyEguiApp {
	fn update(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame){
		egui::CentralPanel::default().show(ctx, |ui|{
			ui.heading("Hello World!");
		})
	}
}

テーマを変える

fn main() {
    let native_options = eframe::NativeOptions::default();
    native_options.default_theme = eframe::Theme::Light;
    let _ = eframe::run_native("My egui App", native_options,
    	Box::new(|cc| Box::new(MyEguiApp::new(cc))));
}

ラベルのテキストサイズを変更

impl eframe::App for MyEguiApp {
	fn update(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame){
		egui::CentralPanel::default().show(ctx, |ui|{
			ui.heading("Hello World!");
			let label_txt = egui::RichText::new("This is sample message.")
				.font(egui::FontId::proportional(32.0));
			let label = egui::Label::new(label_text);
			ui.add(label);
		});
	}
}
impl eframe::App for MyEguiApp {
	fn update(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame){
		egui::CentralPanel::default().show(ctx, |ui|{
			ui.heading("Hello World!");
			let label_txt = egui::RichText::new("This is sample message.")
				.size(32.0)
				.color(egui::Color32::from_rgba_premultiplied(255,0,0,100))
				.italics();
			let label = egui::Label::new(label_text);
			ui.add(label);
		});
	}
}

Rustモジュールの作成

クレート: 配布するライブラリなどの配布単位となるもの。ライブラリ群はクレートとしてまとめられている。
モジュール: クレート内に用意されているライブラリ群
useを使い、クレートとその中にある利用したいモジュールをインポートすることで利用可能になる

$ cargo new –lib mymodule

use mymodule::add;

fn main() {
	let x = 10;
	let y = 20;
	let res = add(x, y);
	println!("answer: {} + {} = {}", x, y, res);
}

calcモジュールの中にadd関数を置く

pub mod calc {
	pub fn add(left: usize, right: usize) -> usize {
	    left + right
	}
}

モジュールのテストはcargo testで実行する
use super::*; は親モジュール内の全てのモジュールをインポートする

### テスト用のマクロ
assert_eq!()
assert_ne!()
assert!()

pub mod calc {

	pub fn is_prime(num: unsize) -> bool {
		let mut f = true;
		for n in 2..(num/ 2) {
			if num % n == 0 {
				f = false;
			}
		}
		return f;
	}
}

テストコード

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

    #[test]
    fn it_is_prime() {
    	let data = [
    		2, 3, 5, 7, 11, 13, 17, 19, 23, 29
    	];

    	for n in data {
    		let res = calc::is_prime(n);
    		assert_eq!(res, true);
    	}
    }
    #[test]
    fn it_isnot_prime() {
    	let data = [
    		4, 6, 9, 10, 12, 14, 15, 16, 18, 20 
    	];

    	for n in data {
    		let res = calc::is_prime(n);
    		assert_eq!(res, true);
    	}
    }
}

Rustファイルアクセス

stdクレートのfsモジュールに必要な機能がまとめられている
use std::fs::File;

fileが得られたら、BufReader構造体のインスタンスを作成する
use std::io::{BufRead, BufReader}

data.txt

Hello!
This is sample text file
It takes text and display it.
file end.

data.txtを読み込み表示する

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

fn main(){
	let file = File::open("data.txt").unwrap();
	let reader = BufReader::new(file);

	let mut count = 0;
	for line in reader.lines() {
		count += 1;
		let txt = line.unwrap();
		println!("{}: {}", count, txt);
	}
}

### File取得とエラー処理

use std::fs::File;
use std::io::{BufRead, BufReader};
use std::io::ErrorKind;

fn main(){
	let file = match File::open("data.txt") {
		Ok(file) => file,
		Err(error) => match error.kind(){
			ErrorKind::NotFound => panic!("ファイルが見つかりませんでした"),
			ErrorKind::PermissionDenied => panic!("ファイルへのアクセス権限がありません"),
			_ => panic!("ファイルのオープンに失敗しました:{:?}", error),
		},
	};

	let reader = BufReader::new(file);

	for line in reader.lines() {
		println!("{}", line.unwrap());
	}
}

### ファイルにテキストを保存する

use std::fs::File;
use std::io::Write;

fn main(){
	let data = [
		"Hello world!",
		"これはサンプルデータです。",
		"テストテスト!"
	];
	let str_data = data.join("\n");
	let mut file = File::create("backup.txt").unwrap();
	file.write_all(str_data.as_bytes()).unwrap();
}

### ファイルにデータを追記する

use std::fs::OpenOptions;
use std::io::Write;

fn main() {
	let str_data = "This is sample!\n";
	let mut file = OpenOptions::new()
		.create(true)
		.append(true)
		.open("append.txt").unwrap();
	file.write_all(str_data.as_bytes()).unwrap();
}

### ファイル一覧の取得

use std::fs;

fn main() {
	let paths = fs::read_dir("./").unwrap();

	for path in paths {
		let entry = path.unwrap();
		println!("{:?}", entry.path().to_str());
	}
}

### エントリーの種類を調べる

use std::fs;

fn main() {
	let paths = fs::read_dir("./").unwrap();

	for path in paths {
		let entry = path.unwrap();
		let ftype = entry.file_type().unwrap();
		if ftype.is_file() {
			println!("{:?} file", entry.path())
		} else if ftype.is_dir(){
			println!("{:?} dir", entry.path())
		} else if ftype.is_symlink() {
			println!("{:?} link", entry.path())
		} else {
			println!("{:?}", entry.path())
		}
	}
}

### ファイル/フォルダの操作
fs::create_dir(), fs::copy(), fs::move(), fs::remove_file(), fs::remove_dir(), fs::remove_dir_all()

use std::fs;

fn main() {
	_ = fs::create_dir("./backup");
	let entries = fs::read_dir("./").unwrap();

	for path in entries {
		let entry = path.unwrap();
		if entry.file_type().unwrap().is_file(){
			let file_name = entry.file_name();
			let from_name = format!("./{}", file_name.to_string_lossy());
			let to_name = format!("./backup/_{}", file_name.to_string_lossy());

			_ = fs::copy(&from_name, &to_name);
			println!("backup: {:?} -> {}", from_name, to_name);
		} else {
			println!("not copied.({:?})", entry.file_name());
		}
	}
}

Rustマルチスレッドと非同期処理

処理を行うために実行されるのがスレッド
プロセスが開始されると「メインスレッド」と呼ばれるアプリの基本となる処理を実行するスレッドが実行され、この中で処理が行われる
同時に複数の処理を並行して実行する必要があるときはマルチスレッドになる

### threadによるスレッド作成
スレッドの作成は「std」というクレートに用意されている「thread」というモジュールを使う

use std::thread;

fn main() {
	thread::spawn(|| {
		println!("THread:Start!");
		println!("THread:End!");
	});

	println!("Main:Start!");
	println!("Main:End.");
}

別スレッドの実行前にメインスレッドが終了している

### スレッドの一時停止とDuration
スレッドの処理を一時的に定するには、threadの「sleep」という関数を使用する
thead::sleep(Duration:)

use std::thread;
use std::time::Duration;

fn main() {
	thread::spawn(|| {
		println!("THread:Start!");
		thread::sleep(Duration::from_millis(10));
		println!("THread:End!");
	});

	println!("Main:Start!");
	thread::sleep(Duration::from_millis(100));
	println!("Main:End.");
}

どのスレッドが処理を実行中で、どれが終了したかを常に頭に入れて処理を行う必要がある

両スレッドの実行順序を確認

use std::thread;
use std::time::Duration;

fn main() {
	thread::spawn(|| {
		for n in 1..10 {
			println!("Thread:No, {}", n);
			thread::sleep(Duration::from_millis(50));
		}
	});

	for n in 1..10 {
		println!("Main: No,{}.", n);
		thread::sleep(Duration::from_millis(100))
	}
}

sleepの持つ役割

use std::thread;
use std::time::Duration;

fn main() {
	thread::spawn(|| {
		for n in 1..100 {
			println!("Thread:No, {}", n);
		}
	});
	thread::sleep(Duration::from_millis(1));

	for n in 1..100 {
		println!("Main: No,{}.", n);
	}
}

スレッドが一時停止した時などに動く

joinHandleとjoinメソッド

use std::thread;
use std::time::Duration;

fn main() {
	println!("Main:Start!");

	let h = thread::spawn(||{
			thread::spawn(|| {
				for n in 1..6 {
					println!("Thread:No, {}", n);
					thread::sleep(Duration::from_millis(2));
				}
			});

			thread::spawn(|| {
				for n in 1..6 {
					println!("H2:No,{}", n);
					thread::sleep(Duration::from_millis(2));
				}
			});

			for n in 1..6 {
				println!("Thread:No,{}", n);
				thread::sleep(Duration::from_millis(1));
			}
	});

	let _res = h.join();
	println!("Main:End.");
}

スレッドによる値の共有

use std::thread;
use std::time::Duration;

fn main() {
	let mut num = 1;
	println!("Main:Start!");

	let h1 = thread::spawn(move || {
		println!("H1: start!");
		for n in 1..5 {
			num = 10 * n;
			println!("H1: num_h={}.", num);
			thread::sleep(Duration::from_millis(10));
		}
		println!("H1: End.");
	});

	let h2 = thread::spawn(move || {
		println!("H2:Start!");
		for n in 1..5 {
			num += n;
			println!("H2: num_h={}.", num);
			thread::sleep(Duration::from_millis(10));
		}
		println!("H2: End.");
	});

	let _res = h1.join();
	let _res = h2.join();
	println!("Main:End.");
}

### Arc/Mutexで値を共有
Mutexはスレッド間でデータを共有する際のデータ保護を行うために用いられる構造体

use std::sync::{Mutex, Arc};
use std::thread;
use std::time::Duration;

fn main(){
	let num = Arc::new(Mutex::new(1));
	println!("Main start!");

	let num_1 = Arc::clone(&num);

	let h1 = thread::spawn(move || {
		let mut num_h1 = num_1.lock().unwrap();
		println!("H1: start!");
		for n in 1..5 {
			*num_h1 += n;
			println!("H1: num_h={}.", *num_h1);
			thread::sleep(Duration::from_millis(1));
		}
		println!("H1: End.");
	});

	let num_2 = Arc::clone(&num);

	let h2 = thread::spawn(move || {
		let mut num_h2 = num_2.lock().unwrap();
		println!("H2: start!");
		for n in 1..5 {
			*num_h2 *= n;
			println!("H2: num_h={}", *num_h2);
			thread::sleep(Duration::from_millis(1));
		}
		println!("H2: End.");
	});

	let _res = h1.join();
	let _res = h2.join();

	println!("Main:: End.");
}

スレッドのデッドロック

use std::sync::{Mutex, Arc};
use std::thread;
use std::time::Duration;

fn main(){
	let num1 = Arc::new(Mutex::new(0));
	let num2 = Arc::new(Mutex::new(0));

	let value1a = Arc::clone(&num1);
	let value2a = Arc::clone(&num2);

	let value1b = Arc::clone(&num1);
	let value2b = Arc::clone(&num2);

	let h1 = thread::spawn(move || {
		let mut num1 = value1a.lock().unwrap();
		thread::sleep(Duration::from_millis(50));
		let mut num2 = value2a.lock().unwrap();
		*num1 += 10;
		*num2 += 20;
	});

	let h2 = thread::spawn(move || {
		let mut num2 = value2b.lock().unwrap();
		thread::sleep(Duration::from_millis(50));
		let mut num1 = value1b.lock().unwrap();
		*num1 += 100;
		*num2 += 200;
	});

	h1.join().unwrap();
	h2.join().unwrap();

	println!("end");
}

チャンネルの利用

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main(){
	let (tx, rx) = mpsc::channel();
	println!("Main: start!");
	let h1 = thread::spawn(move ||{
		let mut num = 1;
		println!("H1: start!");
		for n in 1..5 {
			num += n;
			tx.send(num).unwrap();
			println!("H1: num={}.", num);
			thread::sleep(Duration::from_millis(10));
		}
		println!("H1: End.");
	});

	let h2 = thread::spawn(move ||{
		println!("H2: start!");
		for n in 1..5 {
			let num_recv = rx.recv().unwrap();
			println!("H2: num_recv={}.", num_recv);
			thread::sleep(Duration::from_millis(20));
		}
		println!("H2: End.");
	});
	let _res = h1.join();
	let _res = h2.join();
	println!("Main: End.");
}

相互に送受信

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main(){
	let (tx1, rx1) = mpsc::channel();
	let (tx2, rx2) = mpsc::channel();
	tx2.send(0).unwrap();
	println!("Main: start!");

	let h1 = thread::spawn(move ||{
		let mut num = 1;
		println!("H1: start!");
		for n in 1..5 {
			let val = rx2.recv().unwrap();
			num += n;
			println!("H1: num={}.", num);
			tx1.send(num).unwrap();
			thread::sleep(Duration::from_millis(10));
		}
		println!("H1: End.");
	});
	thread::sleep(Duration::from_millis(5));
	let h2 = thread::spawn(move ||{
		println!("H2: start!");
		for n in 1..5 {
			let val = rx1.recv().unwrap();
			let num = val * 2;
			println!("H2: num={}.", num);
			tx2.send(num).unwrap();
			thread::sleep(Duration::from_millis(10));
		}
		println!("H2: End.");
	});
	let _res = h1.join();
	let _res = h2.join();
	println!("Main: End.");
}

### 同期チャンネル

use std::sync::mpsc;
use std::thread;
use std::time::Duration;

fn main(){
	let (tx1, rx1) = mpsc::sync_channel(1);
	let (tx2, rx2) = mpsc::sync_channel(1);
	tx2.sync_send(0).unwrap();
	println!("Main: start!");

	let h1 = thread::spawn(move ||{
		let mut num = 1;
		println!("H1: start!");
		for n in 1..5 {
			let val = rx2.recv().unwrap();
			num += n;
			println!("H1: num={}.", num);
			tx1.sync_send(num).unwrap();
			thread::sleep(Duration::from_millis(10));
		}
		println!("H1: End.");
	});
	thread::sleep(Duration::from_millis(5));
	let h2 = thread::spawn(move ||{
		println!("H2: start!");
		for n in 1..5 {
			let val = rx1.recv().unwrap();
			let num = val * 2;
			println!("H2: num={}.", num);
			tx2.sync_send(num).unwrap();
			thread::sleep(Duration::from_millis(10));
		}
		println!("H2: End.");
	});
	let _res = h1.join();
	let _res = h2.join();
	println!("Main: End.");
}