【Python】editor

import curses

def main(stdscr):
    curses.curs_set(1)
    stdscr.clear()
    stdscr.refresh()

    text = []
    row = 0

    while True:
        stdscr.move(row, 0)
        stdscr.clrtoeol()
        stdscr.addstr(row, 0, ''.join(text))
        stdscr.refresh()
        ch = stdscr.getch()

        if ch == 27:  # ESCキーで終了
            break
        elif ch == 10:  # Enter
            text.append('\n')
            row += 1
        elif ch == 127 or ch == curses.KEY_BACKSPACE:
            if text:
                text.pop()
                if text[-1] == '\n':
                    row -= 1
        else:
            text.append(chr(ch))

    with open("output.txt", "w") as f:
        f.write(''.join(text))
    

curses.wrapper(main)

Rustで書くと

use crossterm::{
    cursor,
    event::{read, Event, KeyCode},
    terminal::{enable_raw_mode, disable_raw_mode, ClearType},
    ExecutableCommand,
};
use std::io::{stdout, Write};
use std::fs::File;

fn main() -> std::io::Result<()> {
    let mut stdout = stdout();
    let mut buffer = String::new();

    enable_raw_mode()?;
    stdout.execute(crossterm::terminal::Clear(ClearType::All))?;
    stdout.execute(cursor::MoveTo(0, 0))?;

    loop {
        match read()? {
            Event::Key(event) => match event.code {
                KeyCode::Char(c) => {
                    buffer.push(c);
                    print!("{}", c);
                    stdout.flush()?;
                }
                KeyCode::Enter => {
                    buffer.push('\n');
                    print!("\r\n");
                    stdout.flush()?;
                }
                KeyCode::Backspace => {
                    buffer.pop();
                    stdout.execute(cursor::MoveLeft(1))?;
                    print!(" ");
                    stdout.execute(cursor::MoveLeft(1))?;
                    stdout.flush()?;
                }
                KeyCode::Esc => {
                    break;
                }
                _ => {}
            },
            _ => {}
        }
    }

    disable_raw_mode()?;

    let mut file = File::create("output.txt")?;
    write!(file, "{}", buffer)?;

    Ok(())
}

inputファイルを読み込んで、出力

use crossterm::{
    cursor,
    event::{read, Event, KeyCode},
    terminal::{enable_raw_mode, disable_raw_mode, ClearType},
    ExecutableCommand,
};
use std::io::{stdout, Write};
use std::fs::{File, read_to_string};

fn main() -> std::io::Result<()> {
    let mut stdout = stdout();
    let mut buffer = String::new();

    let initial_text = match read_to_string("input.txt") {
        Ok(content) => content,
        Err(_) => String::new(),
    };
    buffer.push_str(&initial_text);

    enable_raw_mode().expect("Failed to enable raw mode");
    stdout.execute(crossterm::terminal::Clear(ClearType::All))?;
    stdout.execute(cursor::MoveTo(0, 0))?;

    let mut row: u16 = 0;
    let mut col: u16 = 0;
    for ch in buffer.chars() {
        match ch {
            '\n' => {
                print!("\r\n");
                row += 1;
                col = 0;
            }
            _ => {
                print!("{}", ch);
                col += 1;
            }
        }
    }
    stdout.flush()?;

    stdout.execute(cursor::MoveTo(col, row))?;

    loop {
        match read()? {
            Event::Key(event) => match event.code {
                KeyCode::Char(c) => {
                    buffer.push(c);
                    print!("{}", c);
                    stdout.flush()?;
                }
                KeyCode::Enter => {
                    buffer.push('\n');
                    row += 1;
                    print!("\r\n");
                    stdout.flush()?;
                }
                KeyCode::Backspace => {
                    buffer.pop();
                    stdout.execute(cursor::MoveLeft(1))?;
                    print!(" ");
                    stdout.execute(cursor::MoveLeft(1))?;
                    stdout.flush()?;
                }
                KeyCode::Esc => {
                    break;
                }
                _ => {}
            },
            _ => {}
        }
    }

    disable_raw_mode()?;

    let mut file = File::create("output.txt")?;
    write!(file, "{}", buffer)?;

    Ok(())
}

input.txtを行単位で読み込んで出力するなどの工夫が必要

【Rust】RwLockの使い所

複数スレッドで読み取りは同時、書き込みは排他


use std::sync::{Arc, RwLock};
use std::thread;

fn main() {
    let data = Arc::new(RwLock::new(5));

    let readers: Vec<_> = (0..3).map(|i| {
        let data = Arc::clone(&data);
        thread::spawn(move || {
            let read = data.read().unwrap();
            println!("Thread {} read: {}", i, *read);
        })
    }).collect();

    {
        let mut write = data.write().unwrap();
        *write += 1;
        println!("Main thread wrote: {}", *write);
    }

}

書き込みが多い場合はmutex向き

【Rust】Arcとはどういう時に使用するか?

Arc(std::sync::Arc)は、複数スレッドで安全に共有できる参照カウント付きスマートポインタ

### Arcはいつ使うか?
– 複数スレッドで同じデータを共有したい時
– かつ、読み取り専用 or 内部で排他制御する設計の時

use std::sync::Arc;
use std::thread;

fn main() {
    let data = Arc::new(vec![1, 2, 3]);

    let mut handles = vec![];

    for i in 0..3  {
        let data_clone = Arc::clone(&data);
        let handle = thread::spawn(move || {
            println!("Thread {} sees: {:?}", i, data_clone);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
}

$ ./playground
Thread 0 sees: [1, 2, 3]
Thread 1 sees: [1, 2, 3]
Thread 2 sees: [1, 2, 3]

同じようなコード

use std::sync::{Arc};
use std::thread;

fn main() {
    let data = Arc::new(1);

    let mut handles = vec![];

    for i in 0..3  {
        let data_clone = Arc::clone(&data);
        let handle = thread::spawn(move || {
            println!("{}", i + *data_clone);
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }
}

### 書き換えたい時

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

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

    let mut handles = vec![];

    for _ in 0..10  {
        let counter = Arc::clone(&counter);
        let handle = thread::spawn(move || {
            let mut num = counter.lock().unwrap();
            *num += 1;
        });
        handles.push(handle);
    }

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Final counter: {}", *counter.lock().unwrap());
}

単なるデータのカウントアップだけなら、以下のようなシングルスレッドの方が効率的だが、

fn main() {
    let mut counter = 0;

    for _ in 0..10 {
        counter += 1;
    } 

    println!("{}", counter);
}

並列ダウンロード、Webサーバなどでは、圧倒的に複数スレッドの方が効率が良い。
それでArcやmutexが使用される。
なるほど〜

【Rust】box と dyn の使い所

Box は値をヒープに確保するためのスマートポインタ
L サイズがコンパイル時に決まらない型を使う時、dyn Traitを使う時、所有権を別の場所に移したいとき などに利用する

スタックではなく、ヒープに保存する

fn main() {
    let x = Box::new(77);
    println!("x = {}", x);
}

### 再起的なデータ構造で、コンパイル時にサイズが決まらないケース

#[derive(Debug)]
enum List {
    Cons(i32, Box<List>),
    Nil
}

use List::{Cons, Nil};

fn main() {
    let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));
    println!("{:?}", list);
}

$ ./playground
Cons(1, Cons(2, Cons(3, Nil)))

### dyn Traitと一緒に使う

*/
trait Animal {
    fn speak(&self);
}

struct Dog;
impl Animal for Dog {
    fn speak(&self) {
        println!("Woof!");
    }
}

fn main() {
    let dog: Box<dyn Animal> = Box::new(Dog);
    dog.speak();
}

ヒープに確保せず、let dog = Dog; とした場合でも問題なく動くが、let dog の型を明示的に示す場合は、Boxと書くのね。。

つまり、
dyn Fn(Request) -> Box> + Send> + Send + Sync;
は、 dyn Fn(Request) は型が決まっていない Request という意味ですね。
Box<> は型が決まっていないのでヒープに保存。

【Rust】dyn って一体何か?

例えば、こんな一文があります。

type BoxedHandler = dyn Fn(Request<Body>) -> Box<dyn futures::Future<Output = Response<Body>> + Send> + Send + Sync;

typeがエイリアスなのはわかりますが、dynって一体なんでしょうか?
=>
dyn はRustにおける動的ディスパッチ(dynamic dispatch)を表すキーワードで、トレイトオブジェクトを使うときに必要
L どの型が来るかは実行時に決まる
L 複数の異なる型を一つのコレクションで扱いたい

trait Animal {
    fn speak(&self);
}

struct Dog;
struct Cat;

impl Animal for Dog {
    fn speak(&self) {
        println!("Woof!");
    }
}

impl Animal for Cat {
    fn speak(&self) {
        println!("Meow!");
    }
}

fn make_animal_noise(animal: &dyn Animal) {
    animal.speak();
}

fn main() {
    let dog = Dog;
    let cat = Cat;

    make_animal_noise(&dog);
    make_animal_noise(&cat);
}

うーん、わかったようなわかってないような…
別の例に置き換える

trait BulletTrain {
    fn run(&self);
}

struct TohokuShinkansen;
struct YamagataShinkansen;

impl BulletTrain for TohokuShinkansen {
    fn run(&self) {
        println!("東京-山形");
    }
}

impl BulletTrain for YamagataShinkansen {
    fn run(&self) {
        println!("東京-青森");
    }
}

fn travel_to_tohoku(bullettrain: &dyn BulletTrain) {
    bullettrain.run();
}

fn main() {
    let hayabusa1 = TohokuShinkansen;
    let tsubasa1 = YamagataShinkansen;

    travel_to_tohoku(&hayabusa1);
    travel_to_tohoku(&tsubasa1);
}

$ ./playground
東京-山形
東京-青森

【Rust】typeの使い所

型に別名/エイリアスを付けたい時に使用する

type Meters = u32;

fn print_distance(d: Meters) {
    println!("Distance: {} meters", d);
}

fn main() {
    let distance: Meters = 100;
    print_distance(distance);
}

$ ./playground
Distance: 100 meters

### よくある用途
複雑な型にエイリアスをつける

type Result<T> = std::result::Result<T, std::io::Error>;

fn read_file() -> Result<String> {
    std::fs::read_to_string("example.txt")
}

なるほど、複雑な型にエイリアスというのは納得!

【Rust】フレームワーク自作: セッションのレコードを自動削除

$ psql –version
psql (PostgreSQL) 14.15 (Ubuntu 14.15-0ubuntu0.22.04.1)
$ sudo apt install postgresql-14-cron
$ sudo vi /etc/postgresql/14/main/postgresql.conf

以下を追加
shared_preload_libraries = ‘pg_cron’
cron.database_name = ‘postgres’

$ sudo systemctl restart postgresql

$ CREATE EXTENSION IF NOT EXISTS pg_cron;

一時間おきに1日前に作成されたセッションを削除する設定にする
$ SELECT cron.schedule(‘delete_expired_sessions’, ‘0 * * * *’, $$
DELETE FROM ares_sessions
WHERE created_at < NOW() - INTERVAL '1 day' $$); cronではなく、psql自体にバッチ処理の仕組みが備わっているのは素晴らしいね。

【Rust】フレームワーク自作: jsonも扱えるようにする

let content_type = request
        .lines()
        .find(|line| line.to_ascii_lowercase().starts_with("content-type:"))
        .unwrap_or("");
    let is_json = content_type.contains("application/json");
//

pub fn parse_json(body: &str) -> Option<HashMap<String, String>> {
    let parsed: Result<Value, _> = serde_json::from_str(body);
    match parsed {
        Ok(Value::Object(map)) => {
            let mut result = HashMap::new();
            for (k, v) in map {
                if let Some(s) = v.as_str() {
                    result.insert(k, s.to_string());
                } else {
                    result.insert(k, v.to_string()); // fallback
                }
            }
            Some(result)
        }
        _ => None,
    }
}

jsonデータの場合は、parse_jsonとしてdataを利用する。

fn handle_api_post(_req: &str, body: String) -> HttpResponse {
    let data = parse_json(&body).unwrap_or_default();

    let binding = "<unknown>".to_string();
    let name = data.get("name").unwrap_or(&binding);
    let message = format!("{{\"greeting\": \"Hello, {}!\"}}", name);

    let mut response = HttpResponse::new(200, &message);
    response.headers.insert("Content-Type".to_string(), "application/json".to_string());
    response
}

なるほどー

【Rust】フレームワーク自作: ログイン・csrf認証をpostメソッドで行う

サインイン、ログイン時に、is_authenticatedをtrueに変更する設計にする。そうすることにより、ログイン前のログイン画面やサインイン画面で匿名セッションを発行して、csrf認証を行うことができる。

CREATE TABLE ares_sessions (
    id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
    session_token TEXT,
    csrf_token TEXT,
    is_authenticated BOOLEAN DEFAULT FALSE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
            let parsed_form = parse(&body);
            let csrf_token_from_form = parsed_form.get("csrf_token").map(|s| s.as_str());

            let csrf_valid = match (&session_token, csrf_token_from_form) {
                (Some(sess_token), Some(csrf_token)) => validate_csrf_token(sess_token, csrf_token),
                _ => false,
            };
//

pub fn validate_session_token(token: &str) -> bool {
    let mut client = match psql_connect() {
        Ok(c) => c,
        Err(_) => return false,
    };

    let rows = match client.query(
        "SELECT is_authenticated FROM ares_sessions WHERE session_token = $1",
        &[&token],
    ) {
        Ok(r) => r,
        Err(_) => return false,
    };

    if rows.is_empty() {
        return false;
    }

    let is_authenticated: bool = rows[0].get(0);
    is_authenticated
}

【Rust】フレームワーク自作: 静的ファイルハンドリング

静的ファイルはstaticに置くルールとする

    if path.starts_with("/static/") {
        let file_path = format!(".{}", path);

        match fs::read(&file_path) {
            Ok(contents) => {
                let content_type = get_content_type(path);
                let response = format!(
                    "HTTP/1.1 200 OK\r\nContent-Type: {}\r\nContent-Length: {}\r\n\r\n",
                    content_type,
                    contents.len()
                );
                let _ = stream.write_all(response.as_bytes());
                let _ = stream.write_all(&contents);
            }
            Err(_) => {
                let response = http_response(404, "text/plain", "Static file not found");
                let _ = stream.write_all(response.as_bytes());
            }
        }
        stream.flush().unwrap();
        return;
    }
// 

fn get_content_type(path: &str) -> &'static str {
    if path.ends_with(".css") {
        "text/css"
    } else if path.ends_with(".js") {
        "application/javascript"
    } else if path.ends_with(".png") {
        "image/png"
    } else if path.ends_with(".jpg") || path.ends_with(".jpeg") {
        "image/jpeg"
    } else if path.ends_with(".svg") {
        "image/svg+xml"
    } else if path.ends_with(".woff2") {
        "font/woff2"
    } else {
        "application/octet-stream"
    }
}

templatesフォルダのファイルが、以下対応できるようになります。

<link rel="stylesheet" href="/static/styles.css">
<script src="/static/app.js"></script>
<h1>Welcome to the Rust server!</h1>
<p>hello, {{ name }}</p>