RustによるWebサーバー構築と所有権の基本的なまとめ

この学習段階でとても嬉しく感じています!

つまり、まもなく『The Rust Programming Language』を終えることになります。

毎日少しずつ学び、全体で半年ほどかかりました。速度はあまり良くありませんが、年齢が上がったせいかもしれません!

練習が必要なので、およそ119個の.rsファイルを作成しました(もちろん大部分は本書からのものです)。速度はまだ許容範囲内です。

しかし正直、心の準備ができていないです。Rustについて徐々に慣れ始めたように感じますが、本当に深く理解しているとは言い難いです!

今こそ最後の第21章の練習に取り組む時です。

この練習のために、これまでに週間の休暇を使っていました。これまでで最も多くの時間を費やしました!

私のコードは本書とほとんど同じですが、以下の点で異なります:

1.静的ファイルをキャッシュするモジュールcacheを追加

2.システムシグナルを処理するkeyboardモジュールを追加し、Ctrl+Cを受信してプログラムを終了させる

モジュールが多くなったため、ワークスペースを構築しました。以下の図をご覧ください:

httpmainがメインバイナリプロジェクトであり、threadpoolは本書から持ってきたスレッドプールです。

一、サンプルコード

この章では4つのプロジェクトのコードを示します:

  1. httpmain
  2. threadpool
  3. keyboard
  4. cache

およびワークスペースのコード。

1.1、ワークスペースファイル

Cargo.toml

[workspace]
resolver = "2"
members = [ "cache","httpmain", "keyboard", "threadpool"]

[workspace.package]
version = "0.1.0"
edition = "2021"
[workspace.dependencies]
rand = "0.9.0-beta.1" 

1.2、httpmain

Cargo.toml

[package]
name = "httpmain"
version.workspace = true
edition.workspace = true

[dependencies]
chrono = "0.4"
threadpool={path="../threadpool"}
cache={path="../cache"}
keyboard={path="../keyboard"}

main.rs

use cache::{init_cache, ResourceCache};
/**
 * 単一スレッドのHTTPサーバーを実装します
 * これは非常にシンプルで、GETリクエストのみを処理し、HTTP/1.1 GET / とfavicon.icoのリクエストのみを対応しています
 *
 * このようなプログラムを完成させるのは困難です。基本的な知識に加えて、
 * 1.Rustの一般的なAPIを熟知すること(https://doc.rust-lang.org/のAPI資料を参照)
 * 2.様々なunwrap()の記憶
 * 3.forループ、イテレータ、パターンマッチングによる混乱の理解
 */
use chrono::Local;
use keyboard::Keyboard;
use std::{
    fs,
    io::{prelude::*, BufReader},
    net::{TcpListener, TcpStream},
    path::Path,
    sync::{Arc, Mutex, OnceLock},
    thread,
    time::Duration,
};
use threadpool::ThreadPool;
// リクエスト回数をカウントします。練習のため、値がオーバーフローする心配はありません
static COUNTER: OnceLock<Arc<Mutex<u32>>> = OnceLock::new();

/**
 * クライアントのリクエストヘッダーを読み取り、特定の情報に基づいて異なる処理を行います
 */
fn get_agent(headers: &Vec<String>) -> String {
    let mut user_agent = String::new();
    for line in headers {
        if line.starts_with("User-Agent") {
            user_agent = line.to_string();
            // user_agentがFireFoxを含むか確認
            if user_agent.contains("Firefox") {
                user_agent = "Firefox".to_string();
            } else if user_agent.contains("Edg") {
                user_agent = "Edge".to_string();
            } else if user_agent.contains("OPR") {
                user_agent = "OPR".to_string();
            }
            else if user_agent.contains("Chrome") {
                user_agent = "Chrome".to_string();
            } 
            else{
                user_agent = "Other".to_string();
            }
            break;
        }
    }
    user_agent
}

/**
 * リクエストヘッダー全体を読み取り、文字列配列として返します
 */
fn read_header(stream: &mut TcpStream) -> Vec<String> {
    let buf_reader = BufReader::new(stream);
    let http_request: Vec<String> = buf_reader
        .lines()
        .map(|result| result.unwrap())
        .take_while(|line| !line.is_empty())
        .collect();
    return http_request;
}

fn main() {
    COUNTER.get_or_init(|| Arc::new(Mutex::new(0)));
    init_cache();
    // キーボードを初期化
    let kb = Keyboard::new();
    let pool = Arc::new(ThreadPool::new(10));
    let listener = TcpListener::bind("127.0.0.1:7878").unwrap();

    println!("サーバーは 127.0.0.1:7878 で起動しました ❤🌼");
    println!("Ctrl+C を押してサーバーを停止してください");
    while !kb.is_ctrl_c_pressed() {
        match listener.accept() {
            Ok((mut stream, _)) => {
                pool.execute(move || {
                    response_ifhttpreq(&mut stream);
                });
            }
            Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
                thread::sleep(Duration::from_millis(100));
                continue;
            }
            Err(e) => {
                eprintln!("接続受信エラー: {}", e);
                break;
            }
        }
    }
    println!("\n\rサーバーは正常にシャットダウンしています...");
}

fn inc_counter() -> u32 {
    let cc = COUNTER.get().unwrap().clone();
    let mut currnet_counter = cc.lock().unwrap();
    *currnet_counter += 1;
    *currnet_counter
}

/**
 * 簡単な応答
 * リクエストがHTTP/1.1 GET / の場合のみhello.htmlの内容を返します
 * リクエストがGET /favicon.ico HTTP/1.1 の場合favicon.icoの内容を返します
 */
fn response_ifhttpreq(stream: &mut TcpStream) {
    // ここで各処理ごとに1秒待機
    // 単一スレッドでのブロッキング問題を強調し、効果を観察するために
    println!("データ受信中...");
    // 現在時刻をミリ秒精度で表示
    let current_counter = inc_counter();
    let human_readable_time = Local::now().format("%Y-%m-%d %H:%M:%S%.3f").to_string();
    let headers: Vec<String> = read_header(stream);
    if headers.len() == 0 {
        println!("第{}回受信時間: {}", current_counter, human_readable_time);
        handle_404(stream);
        return;
    } else {
        let user_agent = get_agent(&headers);
        println!("第{}回受信時間: {} {}\n\r", current_counter, human_readable_time,user_agent);
        let param = headers[0].as_str();
        if param == "GET / HTTP/1.1" {
            // ホームページを表示
            handle_root(stream);
        } else if param == "GET /favicon.ico HTTP/1.1" {
            // favicon.icoアイコンを表示
            handle_favicon(stream);
        } else {
            // その他のリクエストは404エラーを返す
            handle_404(stream);
        }
    }
}

/**
 * ルートディレクトリリクエストを処理
 */
fn handle_root(stream: &mut TcpStream) {
    let status_line = "HTTP/1.1 200 OK";

    let cache_html = ResourceCache::get_cache_html("hello.html");
    match cache_html {
        Some(html) => {
            println!("キャッシュからhello.htmlを取得");
            let response = format!("{status_line}\r\n\r\n{html}");
            stream.write_all(response.as_bytes()).unwrap();
            return;
        }
        None => {}
    }
    println!("ファイルシステムからhello.htmlを読込み");
    let contents = fs::read_to_string("hello.html").unwrap();
    let response = format!("{status_line}\r\n\r\n{contents}");
    stream.write_all(response.as_bytes()).unwrap();
    println!("htmlをキャッシュに保存...");
    ResourceCache::set_cache_html("hello.html", contents);
}
/**
 * 404エラーを処理
 */
fn handle_404(stream: &mut TcpStream) {
    let response = "HTTP/1.1 404 NOT FOUND\r\n\
                    Content-Length: 0\r\n\
                    \r\n";
    stream.write_all(response.as_bytes()).unwrap();
}

/**
 * favicon.icoリクエストを処理
 */
fn handle_favicon(stream: &mut TcpStream) {
    let image = ResourceCache::get_cache_image("favicon.ico");
    match image {
        Some(bytes) => {
            println!("キャッシュからfavicon.icoを取得");
            let mut response = format!(
                "HTTP/1.1 200 OK\r\n\
                Content-Type: image/x-icon\r\n\
                Content-Length: {}\r\n\
                \r\n",
                bytes.len()
            )
            .into_bytes();
            response.extend(&bytes);
            stream.write_all(&response).unwrap();
            stream.flush().unwrap();
            return;
        }
        None => {}
    }
    // favicon.icoファイルを読込み
    println!("ファイルシステムからfavicon.icoを読込み");
    let favicon_path = Path::new("favicon.ico");
    match fs::read(favicon_path) {
        Ok(favicon_data) => {
            // 完全なHTTPレスポンスを構築して送信
            let mut response = format!(
                "HTTP/1.1 200 OK\r\n\
                Content-Type: image/x-icon\r\n\
                Content-Length: {}\r\n\
                \r\n",
                favicon_data.len()
            )
            .into_bytes();
            response.extend(&favicon_data);
            stream.write_all(&response).unwrap();
            stream.flush().unwrap();
            println!("faviconをキャッシュに保存...");
            ResourceCache::set_cache_image("favicon.ico", favicon_data);
        }
        Err(_) => {
            // アイコンファイルが存在しない場合は404を返す
            let response = "HTTP/1.1 404 NOT FOUND\r\n\
                    Content-Length: 0\r\n\
                    \r\n";
            stream.write_all(response.as_bytes()).unwrap();
            stream.flush().unwrap();
        }
    }
}

1.3、threadpool

Cargo.toml

[package]
name = "threadpool"
version.workspace = true
edition.workspace = true

[dependencies]

lib.rs

use std::{
    sync::{mpsc, Arc, Mutex},
    thread
};
type Job = Box<dyn FnOnce() + Send + 'static>;

struct Worker {
    id: usize,
    thread: Option<thread::JoinHandle<()>>    
}

impl Worker {
    // すべてのワーカーは同じreceiverを共有するので、Arc+Mutexが必要です
    // この設計は簡単なシナリオのみに対応できます。複雑なシナリオにはより複雑な設計が必要です
    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
        // 無限ループでメッセージを受け取りタスクを実行するスレッドを作成
        // これは無限ループで、どのように終了するのでしょうか?
        // spawnはJoinHandleを返し、スレッド終了を待つことができます
        let thread = thread::spawn(move || loop {
            let message = receiver.lock().unwrap().recv();
            match message {
                Ok(job) => {
                    println!("Worker {id} にタスクを受領; 実行中.");
                    job();
                }
                Err(_) => {
                    println!("Worker {id} が切断; シャットダウン中.");
                    break;
                }
            }
        });

        Worker {
            id,
            thread: Some(thread),
        }
    }
}


pub struct ThreadPool {
    workers: Vec<Worker>,
    sender: Option<Arc<mpsc::Sender<Job>>>,
}

impl ThreadPool {
    pub fn new(size: usize) -> ThreadPool {
        assert!(size > 0);
        let (sender, receiver) = mpsc::channel();
        let receiver = Arc::new(Mutex::new(receiver));
        let mut workers = Vec::with_capacity(size);
        for id in 0..size {
            workers.push(Worker::new(id, Arc::clone(&receiver)));
        }

        ThreadPool {
            workers,
            sender: Some(Arc::new(sender)),
        }
    }

    pub fn get_sender(&self) -> Option<Arc<mpsc::Sender<Job>>> {
        // as_ref() は Option<&T> を返します
        // Optionのmapは匿名関数/クロージャを受け取り、Tに対して適用し、新しいOptionを返します
        self.sender.as_ref().map(Arc::clone)
    }

    pub fn execute<F>(&self, f: F)
    where
        F: FnOnce() + Send + 'static,
    {
        let job = Box::new(f);
        self.sender.as_ref().unwrap().send(job).unwrap();
    }
}

impl Drop for ThreadPool {
    fn drop(&mut self) {
        // senderを削除するとreceiverがエラーを受け取り、すべてのワーカーがループを終了します
        // take() はOption中のTを返し、OptionをNoneにします
        drop(self.sender.take());
        for worker in &mut self.workers {
            println!("ワーカー {} をシャットダウン中", worker.id);
            if let Some(thread) = worker.thread.take() {
                thread.join().unwrap();
            }
        }
    }
}

1.4、keyboard

Cargo.toml

[package]
name = "keyboard"
version.workspace = true
edition.workspace = true

[dependencies]
signal-hook = "0.3"

lib.rs

use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use signal_hook::consts::signal::SIGINT;
use signal_hook::flag::register;

/**
 * システムシグナルハンドラ、Ctrl+C(SIGINT)シグナルを捕捉します
 */
pub struct Keyboard {
    ctrl_c_pressed: Arc<AtomicBool>,
}

impl Keyboard {
    pub fn new() -> Keyboard {
        let ctrl_c_pressed = Arc::new(AtomicBool::new(false));
        let pressed_ref = ctrl_c_pressed.clone();
        
        // SIGINT(Ctrl+C)シグナルハンドラを登録
        register(SIGINT, pressed_ref).expect("シグナルハンドラを登録できません");

        Keyboard { ctrl_c_pressed }
    }

    /// Ctrl+Cシグナルが受信されたか確認します
    pub fn is_ctrl_c_pressed(&self) -> bool {
        self.ctrl_c_pressed.load(Ordering::Relaxed)
    }

    /// Ctrl+C状態をリセットします
    pub fn reset_ctrl_c_state(&self) {
        self.ctrl_c_pressed.store(false, Ordering::Relaxed);
    }
}

1.5、cache

Cargo.toml

[package]
name = "cache"
version.workspace = true
edition.workspace = true

[dependencies]

lib.rs

use std::collections::HashMap;
use std::sync::{Arc, Mutex, OnceLock};

static CACHE: OnceLock<Arc<Mutex<ResourceCache>>> = OnceLock::new();

pub enum Container {
    Text(String),
    Image(Vec<u8>),
}

pub struct ResourceCache {
    data: HashMap<String, Container>,
}

impl ResourceCache {
    pub fn new() -> Self {
        ResourceCache {
            data: HashMap::new(),
        }
    }
    pub fn get(&self, key: &str) -> Option<&Container> {
        self.data.get(key)
    }
    pub fn set(&mut self, key: String, value: Container) {
        self.data.insert(key, value);
    }
    pub fn remove(&mut self, key: &str) -> Option<Container> {
        self.data.remove(key)
    }

    pub fn has(&self, key: &str) -> bool {
        self.data.contains_key(key)
    }

    pub fn set_cache_html(path: &str, text: String) {
        // OnceLock::get()メソッドはOption<&T>を返します
        // unwrap()メソッドはOption<&T>を&Tに変換し、Noneの場合はパニックになります
        // get().unwrap()でArc<Mutex<ResourceCache>>の参照を取得し、clone()で参照カウントを増やして複数箇所で共有します
        let arc_cache = CACHE.get().unwrap().clone();
        // lock()でResourceCacheのミューテックスロックを取得し、unwrap()で&mut ResourceCacheの参照に変換します

        let cache = &mut *arc_cache.lock().unwrap();
        if let None = cache.get(path) {
            cache.set(path.to_string(), Container::Text(text));
        }
    }

    pub fn get_cache_html(path: &str) -> Option<String> {
        let arc_cache = CACHE.get().unwrap().clone();
        let cache = &*arc_cache.lock().unwrap();
        if let Some(txt) = cache.get(path) {
            match txt {
                Container::Text(text) => {
                    return Some(text.clone());
                }
                _ => {
                    return None;
                }
            }
        } else {
            return None;
        }
    }

    pub fn set_cache_image(path: &str, bytes: Vec<u8>) {
        let arc_cache = CACHE.get().unwrap().clone();
        let cache = &mut *arc_cache.lock().unwrap();
        if let None = cache.get(path) {
            cache.set(path.to_string(), Container::Image(bytes));
        }
    }

    pub fn get_cache_image(path: &str) -> Option<Vec<u8>> {
        let arc_cache = CACHE.get().unwrap().clone();
        let cache = &*arc_cache.lock().unwrap();
        if let Some(image) = cache.get(path) {
            match image {
                Container::Image(bytes) => {
                    return Some(bytes.clone());
                }
                _ => {
                    return None;
                }
            }
        } else {
            return None;
        }
    }
}

pub fn init_cache() {
    CACHE.get_or_init(|| Arc::new(Mutex::new(ResourceCache::new())));
}

テスト結果

キャッシュが効果があることがわかります。また、すべてのリクエストが意味のあるコンテンツを送信するわけではなく、おそらくブラウザによるものと思われます!

ブラウザ出力(Opera)

ちなみに、テストにより異なるブラウザのリクエストには微細な違いがあることがわかりました。例えばOperaは一度ページを更新すると複数回リクエストが発生しますが、FirefoxやEdgeはそうではありません。

プログラムはまだ不完全です! Ctrl+Cでプログラムが停止せず、ブラウザを再読み込みしないと停止しません。改善が必要です...

二、考察

2.1、所有権とライフタイムについて

間違いなく、これこそが最も理解していない部分です。他の言語に慣れている人にとって、これまでの経験は枷になることが多いです!

現在、所有権の主な印象は「3原則」、ライフタイムの印象は奇妙な'aのような記号です。

2.2.1、所有権の問題

所有権の問題には所有権と借用に関する内容が含まれます。

主なポイントは:

  • 所有権と借用の定義
  • 所有権が移転される条件とされない条件
  • 移転せずに値を使用する方法(標準的な借用以外)

下図はこれらの内容を示しています(後文とは完全に一致しないことに注意)

注意:上図のFnの記述は誤りです。正しいのは:Fnは複数回実行可能です。他の余分な記述は適切ではありません。

具体的には:

a、3原則

  1. 値(変数ではない)は必ず所有者を持つ必要があります
  2. 任意の時点で、値は一つの所有者しか持ちません
  3. 値の所有者がスコープを離れるとき、保持されている値は即座に解放されます

注意:第二条について疑問があります。例えばRc,Arcとは?

b、関数呼び出しにおける引数の所有権への影響

  1. スタック変数を直接渡す - 所有権が移転
  2. &を用いた借用渡し
  3. 例:&mutによる可変借用渡し

例外:Copyトレイトを実装している場合、所有権は奪われません。

c、複合型メンバーに対する所有権の影響

  1. 直接複合型メンバーとして使用した場合、所有権が移転

例外:Copyトレイトを実装している場合、所有権は奪われません。

d、匿名関数/クロージャによる所有権の影響

FnOnce, FnMut, Fnの所有権移転への影響を覚えておきます(これらはRustプログラミング言語の中国語翻訳に基づく)

  • FnOnce は一度しか呼び出せないクロージャに適しています。すべてのクロージャは少なくともこのトレイトを実装しており、すべてのクロージャは呼び出すことができます。クロージャ本体からキャプチャされた値を移動するクロージャはFnOnceトレイトのみを実装し、他のFn関連トレイトは実装しません。なぜなら一度しか呼び出せないからです。
  • FnMut はキャプチャされた値を移動せず、かつキャプチャされた値を変更するクロージャに適しています。このクロージャは複数回呼び出すことができます。簡潔に言えば、FnMutはキャプチャされた変数を変更し、FnMutは複数回実行できます。
  • Fn はキャプチャされた値を移動せず、かつキャプチャされた値を変更しないクロージャに適しており、環境からキャプチャしないクロージャも含まれます。このクロージャは複数回呼び出すことができ、環境を変更しません。これはクロージャを複数回並行して呼び出す場面で重要です。

注:Fnの中国語翻訳には問題があります。正しい意味は:Fnはキャプチャされた変数を変更せず、キャプチャされた変数の所有権を得ず、Fnは複数回実行可能です。FnとFnOnceの唯一の違いは、Fnは複数回実行できることです。

詳細は:Rust学習13.1、Rust匿名関数(クロージャ)をご参照ください。

e、参照カウントポインタによる所有権の影響

これはArcポインタと呼ばれます。値に複数の所有者を許可し、Rc::cloneで参照カウントを増加させます。

Rustによれば、cloneは多くの時間を消費しません!

この特性により、Arcはマルチスレッドプログラミングで頻繁に使用されます。

ただし、これはJVMのような参照カウントとは何の違いがあるのでしょうか?

f、マルチスレッドによる所有権の影響

Sendトレイト -- Sendを実装した型の値の所有権はスレッド間で送信可能です。ほぼすべてのRust型はSendですが、例外としてRcがあります。

Syncトレイト -- Syncを実装した型は複数スレッドでその値の参照を安全に保つことができます

 * 1.Sendは、Sendを実装した型の値の所有権がスレッド間で送信可能であることを示します。ほぼすべてのRust型はSendですが、
 *   例外としてRc<T>があります。
 * 2.完全にSend型から構成される型も自動的にSendとマークされます。
 *   ほぼすべての基本型はSendであり、第20章で議論される裸ポインタ(raw pointer)を除きます。
 * 3.Syncは、Syncを実装した型が複数スレッドでその値の参照を安全に保つことを示します。
 * 4.Sendと同様に、基本型はSyncであり、完全にSync型から構成される型もSyncです。
 * 5.手動でSendおよびSyncトレイトを実装することは一般的ではありません。SendおよびSyncの型から構成される型は自動的にSendおよびSyncです。
 *   これらはマーカートレイトであり、実装する必要のあるメソッドはありません。これらは並行性に関連する不変性を強化するためだけにあります。
 * 6.これらのマーカートレイトを手動で実装するのは、安全でないRustコードを書くことになります。

g、Copyトレイトによる所有権の影響

型がCopyトレイトを実装している場合、パラメータとして渡すときや代入するときに所有権は失われません。なぜなら、Rustはデフォルトで値のコピーを行うからです。

そのため、コードが複雑になることがあります。

参考例: Rust学習5、Rust所有権と関数パラメータ

h、静的コンパイルにおける変数定義の要件

rustcは変数のサイズをコンパイル時に知る必要があります。そうでない場合、エラーになります。

この厳しい要件により、多くの変数は参照型として定義する必要があります。

2.1.2、ライフタイムの問題

所有権、借用は問題を複雑にし、さらにライフタイムが現れます。

なぜライフタイムが必要なのか?それはRustの変数(値)がスコープを持つからです。しかし、いくつかの場合、rustcは複数の変数のスコープが一致しているかわからないため、

実行時エラーを回避するために、rustcはパラメータにライフタイムを明示するように強制します。これはパラメータのスコープが互換的(通常は同じ)であることを示します。

rustcがこれを行う主な目的は、可能性のある例外を実行時ではなくコンパイル時に排除することです。

この副作用により、コードは見づらくなるが、仕方がないのです。

これは設計としては悪く、少なくとも工学的には、将来のどこかで解消されるでしょう。

私たちにとって重要なのは:

  1. 関数/メソッドの場合、戻り値がない場合は通常ライフタイムを明示する必要はありません

  2. パラメータが &self, &mut self の場合、通常は明示不要

  3. ライフタイムの明示は通常参照によるものです

参考:Rust学習11.3、ライフタイムの明示

2.2、匿名関数とクロージャについて

この機能が普及している今、それを習得することは非常に重要です。

これを習得するには、主にFnOnce, FnMut, Fnの3つを理解し、外部変数の所有権が移転するかどうか、変更されるかどうか、移出されるかどうかを把握します。

基本的なことの他に、匿名関数のネストについても考慮する必要があります。

匿名関数のネストは問題ありません(一般的には)。

その他の問題については、後で補足します...

2.3、Optionと列挙型について

OptionとResultはRustにおいて極めて普通のものです。

これらは以下の問題を解決できます:

  1. 例外

  2. 一つの型に複数の値を格納できる(他の言語では簡単だが、Rustでは主にこれしかない)

  3. Rustが好むmatchと連携できる

matchについて、さまざまなマッチング方法を知っておく必要があります。

例えばこのプロジェクトでは、さまざまなキャッシュデータを格納するための型が定義されています:

pub enum Container {
    Text(String),
    Image(Vec<u8>),
}

pub struct ResourceCache {
    data: HashMap<String, Container>,
}

2.4、普遍的なunwrapについて

これはOptionやResultの乱用と関係しています。

OptionとResultにはunwrapメソッドがあります。

例:Optionのunwrap()の説明:

含まれているSome値を返し、self値を消費します。
この関数はパニックを引き起こす可能性があるため、一般的には推奨されません。
パニックは回復不可能なエラーを意図しており、プログラム全体を終了する可能性があります。

代わりに、パターンマッチングを使用してNoneケースを明示的に処理するか、
unwrap_or、unwrap_or_else、unwrap_or_defaultを使用することを推奨します。

Optionを返す関数では、?(試行)演算子を使用できます。

簡単に言うと、unwrap()は含まれている値を取得しますが、パニックを引き起こす可能性があります。

Rustはこの使用を推奨しません。なぜなら、エラーを引き起こす可能性があるからです:

let some_number = Some(5); // some_numberの型はOption<i32>
let score=some_number.unwrap()*20;
println!("some_number is {}", score);

let message:Option<String>=None;
message.unwrap();

このコードは最終的にエラーになります。最後の行が問題です。

重要な注意:Optionの各メソッドを真剣に調べてください!!これはRustのヒントであり、私の経験でもあります。

参考:Rust列挙型の説明

2.5、マルチスレッドについて

複雑なマルチスレッドプログラミングは簡単に説明できません。しかし、主な内容は本書に記載されています:

  • スレッド
  • 並行性(チャネルと共有メモリ)
  • ArcポインタとMutex変数
  • SendとSyncトレイト -- ほとんど自動的に実装され、これが主な利点です

この例では:

チャネルを使用してマルチスレッド間通信を実装

OnceLockを使用してキャッシュデータを保存

OnceLockを使用してリクエストカウンターを保存

チャネルと共有メモリのどちらを使うかは個人の好みですが、Rustは両方をサポートしています!

2.6、スマートポインタについて

スマートポインタの重要性は疑いありません。

なぜなら、多くのアプリケーションはマルチスレッドを使用する必要があり、マルチスレッドでは複雑なデータ構造を考慮する必要があるためです。これらの複雑なデータ構造は基本的にスマートポインタです。

スマートポインタの主な焦点は:

1.DerefとDropトレイト

2.Derefの暗黙的変換

特に&Boxのようなものは&(*Box)に暗黙的に変換され、コードの記述を便利にします。この暗黙的変換により問題が生じることもあります。

一部の人々は暗黙的変換の書き方を好む。コードの中に暗黙的と非暗黙的な記述が混在すると、一部の人にとっては不快かもしれません。

参考:Rust学習15.3、スマートポインタ関連のDerefとDropトレイト

2.7、パラメータの定義と渡し方

多くの場合、関数/メソッドにパラメータを渡す際は比較的簡単です。

注意すべき点は:

  1. パラメータの定義方法によって渡し方が決まります。これはほとんどの場合に当てはまります

  2. CopyとDerefを実装した場合、いくつかの特別な方法があります

a. Copyを実装している場合、パラメータはTで、実際にはTのコピー(浅いコピー)が渡されます

b. Derefを実装している場合、パラメータが&Tで、直接&Tを自動的に逆参照できます。ただし、&(*T)のように書くことも可能で、これは個人の習慣です

  1. 可能であれば、中間変数を定義することで、関数呼び出し中に&、&mut、mutなどの記号を省略できます

例:

#[derive(Debug, Clone,Copy)]
struct Bag {
    price: f32,
    no: u32,
}

/**
 * BagがCopyトレイトを実装しているため、sell_bag関数はパラメータをコピーして渡すことができます
 */
fn sell_bag(bag:Bag)->f32{
    println!("{:?} が売却されました..",bag);    
    bag.price
}

fn show_bag(bag:&Bag){
    println!("{:?}",bag);
}

fn main() {
    let bag=Bag{
        price:100.0,
        no:1000
    };
    println!("{:?}",bag);

    //1.0 Copyトレイトがパラメータ渡しに与える影響を示します
    // bagオブジェクトをコピーしてsell_bag関数に渡します
    let price=sell_bag(bag);
    println!("{}",price);
   
    // bagオブジェクトを再度表示し、bagが変更されていないことを確認します
    println!("{:?}",bag);

    //2.0 Derefトレイトがパラメータ渡しに与える影響を示します
    let mybag=Box::new(Bag{ 
        price:100.0,
        no:1000
    });
    show_bag(&mybag);    
}

2.8、APIドキュメントの習熟

これはどの言語を学ぶにも必要な基本です。

jQueryを学ぶ際にはjQueryドキュメントを見て、jqがどのようなメソッドをサポートしているかを知らなければなりません

Javaを学ぶ際にはjavaDocを参照して、ListやStringがどのようなメソッドまたはインターフェースをサポートしているかを理解しなければなりません

VC++を学ぶ際にはWinAPIを把握する必要があります

自然、Rustを学ぶ際には以下の重要な型の機能を把握する必要があります:

  • Vec
  • String
  • enum(Option,Result)
  • struct
  • Box, Rc, Arc, RefCell, OnceLock
  • Receiver, Sender

基本的な機能だけでなく、所有権に関するメソッドにも重点を置く必要があります。

参考:Rust標準ライブラリAPI lib.rs公式サイト、docs.rs公式サイト

三、まとめ

ここまでの学習で、Rustの知識をある程度習得したと言えるでしょう(少ししか知らない程度)。完全に理解しているとは言えず、精通するにはまだ遠いです!

今後の計画は以下の通りです:

  1. APIドキュメントを深く理解してRustの基本知識を深め、関連するノート/ブログを完璧にする

  2. lib.rsとcrates.ioを見て優れた流行りのものを確認し、他人のコードを読んで内容をまとめ、ブログに記録する

資料が多すぎるため、いくつかだけ選んで見てみます。例えばlib.rsには約18万のパッケージがあります。一生も見きれません。

  1. 小さな個人ツールをいくつか作ってみる

  2. 有机会にRustの組織に参加したり、プロジェクトに参加する

Rustは私が学んだ数え切れないほどの言語の中で最も理解しにくいものであり、所有権と奇妙な記号が原因です!

タグ: rust Webサーバー 所有権 ライフタイム クロージャ

5月29日 21:55 投稿