Memo

メモ > 技術 > プログラミング言語: Rust

入門
Rustプログラミング言語 https://www.rust-lang.org/ja とほほのRust入門 - とほほのWWW入門 https://www.tohoho-web.com/ex/rust.html プログラミング言語「Rust」とは? "Hello, World!"で基本を押さえる:基本からしっかり学ぶRust入門(1)(1/3 ページ) - @IT https://atmarkit.itmedia.co.jp/ait/articles/2107/28/news010.html プログラミング言語Rustのススメ - Qiita https://qiita.com/elipmoc101/items/3c8b6d8332a9019e578c Rust入門 https://zenn.dev/mebiusbox/books/22d4c1ed9b0003 組込みエンジニアのためのRustのよくわからない記号まとめ - Qiita https://qiita.com/yagisawa/items/281478ca05514d4ed49e Rustで作るテトリス入門 https://zenn.dev/kumavale/books/30efec2e1d3428 Rustソースコードのざっくりとした歩き方 - Speaker Deck https://speakerdeck.com/tako8ki/rustsosukotonosatukuritositabu-kifang-3f597e58-222e-4c70-9b1b-e644ea... これからRustを勉強したいエンジニアにおすすめしたい参考書や教材を紹介します https://zenn.dev/pharmax/articles/44d730172f6b61 そもそもプログラミング経験自体無い人がRustを学ぶとき,どんな順序が良いのか https://zenn.dev/toga/articles/rust-for-beginner これまでと違う学び方をしたら挫折せずにRustを学べた話 / Programming Rust techramen24conf LT - Speaker Deck https://speakerdeck.com/twada/programming-rust-techramen24conf-lt Rustで勘違いしていたこと3選 【100 Exercises To Learn Rust 完走記事】 #Rust - Qiita https://qiita.com/namn1125/items/2a59f486cce30fd2b889 GopherがRust入門したので違いをまとめてみた https://zenn.dev/skanehira/articles/2024-08-12-go-rust-pros-cons もしもいま、Rustをイチから学び直すとしたら? Rust入門書著者・matsu7874さんに聞く学習ロードマップ - Findy Engineer Lab https://findy-code.io/engineer-lab/techtensei-matsu7874 フロントエンジニアのためのTauri 2.0ではじめるRustプログラミング一覧|CodeZine(コードジン) https://codezine.jp/article/corner/1033 デスクトップアプリを開発しよう! 「Rust」と「Tauri 2.0」の基本情報と環境整備の仕方を解説 (1/3)|CodeZine(コードジン) https://codezine.jp/article/detail/20539 RealWorld 業務 Rust #Rust - Qiita https://qiita.com/legokichi/items/4e85ec1e74f4e754fb94 Rustプロジェクトのビルド高速化に関するベストプラクティス(ローカル環境編) https://zenn.dev/fairydevices/articles/59cd718341da58 ■紹介や事例 「Rust」はなぜクラウドネイティブ開発者の間で大人気なのか?:「Rust」のメリットとデメリット【前編】 - TechTargetジャパン システム開発 https://techtarget.itmedia.co.jp/tt/news/2109/06/news04.html 難しくても「Rust」を学びたくなってしまう理由:「Rust」のメリットとデメリット【後編】 - TechTargetジャパン システム開発 https://techtarget.itmedia.co.jp/tt/news/2109/13/news04.html なぜわざわざ学習コストを払ってまでRustを採用するのか? Webエンジニア目線でRustを考察:WebエンジニアからみたRust(1) - @IT https://atmarkit.itmedia.co.jp/ait/articles/2109/30/news009.html Rust開発時の便利ツールたち https://zenn.dev/toru3/articles/14312f4dbf18b6 Rust初心者殺しの文法10選 - Qiita https://qiita.com/muumu/items/8cdcc79fa881912adf51 他言語ユーザがRust言語をガチめに使っての雑感 - 分散KVSを書いてみて - - Qiita https://qiita.com/ryo_grid/items/9a82d7230fbc4a0875c1 プログラミング言語「Rust」が「C」「C++」の後継になるこれだけの根拠:AWSやMicrosoftも支援する「最も愛される言語」 - TechTargetジャパン システム開発 https://techtarget.itmedia.co.jp/tt/news/1912/05/news04.html 「Rustでやると知らないうちに詰む設計」を避けるためのTipsを集めてみる https://zenn.dev/qnighy/scraps/93071f7c813cb9 なぜRustなの?と言われた時のために https://zenn.dev/khale/articles/rust-beginners-catchup Rust のここが好き、10選 https://zenn.dev/sawatani/articles/4d6349e6b795cd Rustの良質な学習リソースをまとめる - BioErrorLog Tech Blog https://www.bioerrorlog.work/entry/rust-good-learning-resources なぜRustはメモリ安全なのかをC言語のコードと考える - Qiita https://qiita.com/ohakutsu/items/5d29001f79d42d63e886 満を持して始めるRust - Speaker Deck https://speakerdeck.com/estie/man-wochi-siteshi-merurust sudoとsuがRustで書き直される。メモリ安全性向上へ - PC Watch https://pc.watch.impress.co.jp/docs/news/1498034.html Rustプログラムのデバッグ辛すぎ問題 #Rust - Qiita https://qiita.com/k0kubun/items/766dc15773ec73925163 プログラミング言語Rustになぜ注目するのか #Rust - Qiita https://qiita.com/comware_takatsuru/items/9607df56a5803f68588e Rust始めてみた。JSONフォーマッターを作ってみた | stin's Blog https://blog.stin.ink/articles/rust-json-formatter-cli 開発用適当ツールは Rust で作るのもオススメ https://zenn.dev/codemountains/articles/0d3831c10c46b8 C++しか使ってこなかった男がRustを使ってみた #C++ - Qiita https://qiita.com/Enuwbt/items/5b822020e575c5250606
インストール
■Windowsにインストール Rust をインストール - Rustプログラミング言語 https://www.rust-lang.org/ja/tools/install rustup.rs - The Rust toolchain installer https://rustup.rs/ 上のページから rustup-init.exe をダウンロードして実行する。 コマンドプロンプトが立ち上がり、以下のメッセージが表示される。
The Cargo home directory is located at: C:\Users\refirio\.cargo This can be modified with the CARGO_HOME environment variable. The cargo, rustc, rustup and other commands will be added to Cargo's bin directory, located at: C:\Users\refirio\.cargo\bin This path will then be added to your PATH environment variable by modifying the HKEY_CURRENT_USER/Environment/PATH registry key. You can uninstall at any time with rustup self uninstall and these changes will be reverted. Current installation options: default host triple: x86_64-pc-windows-msvc default toolchain: stable (default) profile: default modify PATH variable: yes 1) Proceed with standard installation (default - just press enter) 2) Customize installation 3) Cancel installation >1 … 「1」と入力してEnterを入力。 info: profile set to 'default' info: default host triple is x86_64-pc-windows-msvc info: syncing channel updates for 'stable-x86_64-pc-windows-msvc' info: latest update on 2024-06-13, rust version 1.79.0 (129f3b996 2024-06-10) info: downloading component 'cargo' info: downloading component 'clippy' info: downloading component 'rust-docs' info: downloading component 'rust-std' info: downloading component 'rustc' 57.7 MiB / 57.7 MiB (100 %) 25.1 MiB/s in 2s ETA: 0s info: downloading component 'rustfmt' info: installing component 'cargo' 6.4 MiB / 6.4 MiB (100 %) 5.5 MiB/s in 1s ETA: 0s info: installing component 'clippy' info: installing component 'rust-docs' 15.4 MiB / 15.4 MiB (100 %) 1.5 MiB/s in 19s ETA: 0s info: installing component 'rust-std' 18.3 MiB / 18.3 MiB (100 %) 4.8 MiB/s in 3s ETA: 0s info: installing component 'rustc' 57.7 MiB / 57.7 MiB (100 %) 4.9 MiB/s in 11s ETA: 0s info: installing component 'rustfmt' info: default toolchain set to 'stable-x86_64-pc-windows-msvc' stable-x86_64-pc-windows-msvc installed - rustc 1.79.0 (129f3b996 2024-06-10) Rust is installed now. Great! To get started you may need to restart your current shell. This would reload its PATH environment variable to include Cargo's bin directory (%USERPROFILE%\.cargo\bin). Press the Enter key to continue. … Enterを入力。
1分程度でインストールが完了する。 ■コンパイル C:\Users\refirio\Rust 内に hello フォルダを作成し、そこで作業するものとする。 main.rs を作成して以下を記述する
fn main() { println!("Hello, world!"); }
コマンドプロンプトから、以下のとおりコンパイルして実行する。(「.\main」としても実行できる。PowerShellの場合、「./main」として実行する。) なおコンパイルすると、main.exe と main.pdb が作成される。 >cd C:\Users\refirio\Rust\hello >rustc main.rs >main Hello, world! ■プロジェクト ※上の手順で作成した「hello」フォルダは削除しておくものとする。 以下のコマンドでプロジェクトを作成できる。 ソースコードは src フォルダ内に main.rs として格納される。(内容は上の「コンパイル」のものと同じ。) >cd C:\Users\refirio\Rust >cargo new hello --bin … プロジェクトを作成。 >cd hello 以下のコマンドでビルドできる。 プログラムは target\debug 内に hello.exe として作成される。 >cargo build … コンパイルだけ行う場合。 >cargo run … コンパイルして実行する場合。 >cargo build --release … リリース用にコンパイルする場合。 以下のようにして実行できる。 >target\debug\hello もしくは、以下のようにして実行できる。 >cd target\debug >hello ■メモ VSCodeだと、特に拡張機能をインストールしなくても色分け表示された。
基本の文法
以下を参考に、基本の文法を確認する。 プログラミング言語「Rust」とは? "Hello, World!"で基本を押さえる:基本からしっかり学ぶRust入門(1)(1/3 ページ) - @IT https://atmarkit.itmedia.co.jp/ait/articles/2107/28/news010.html とほほのRust入門 - とほほのWWW入門 https://www.tohoho-web.com/ex/rust.html プログラミング言語Rustのススメ - Qiita https://qiita.com/elipmoc101/items/3c8b6d8332a9019e578c Rust入門 https://zenn.dev/mebiusbox/books/22d4c1ed9b0003 ■基本のコード ・C言語などと同様、main関数がプログラムのエントリーポイントとなる。 ・「println!」というのはマクロで、マクロは名称の後に「!」を付けて使用する。 Rustでは引数の数が可変である関数を定義できないため、マクロを使って定義されている。 ・「print!」というマクロもあり、この場合は末尾に改行が付かない。
fn main() { println!("Hello, world!"); }
■コメント
fn main() { println!("Hello, world!"); /* println!("Hello, world!"); */ //println!("Hello, world!"); }
■変数 Rustの変数は、デフォルトでイミュータブル(変更できない)となる。 変数名には、小文字のスネークケースを使うといい。 「{}」を使用して変数の内容を表示できる。
fn main() { let x = 100; //x = 200; println!("値 {} は変数xに拘束されています。", x); }
「{:?}」を使用して配列の内容を表示できる。 (配列定義の「vec!」については、後述の「ベクター」を参照。)
fn main() { let values = vec![1, 2, 3, 4]; println!("values: {:?}", values); }
「{0}」のように、0からの番号を指定することもできる。
fn main() { let x = 100; let y = 200; let values = vec![1, 2, 3, 4]; println!("変数x = {0} / 変数y = {1} / 配列 = {2:?}", x, y, values); }
Rust 1.58からフォーマット文字列で変数の直接参照が可能に! - dotTrail https://dottrail.codemountains.org/rust-format-strings/ Rust 1.58 から、以下のようにフォーマット文字列内で変数名を直接参照できる。(ただし「{x * 2}」のように式の展開は現状対応していない。)
fn main() { let x = 100; println!("値 {x} は変数xに拘束されています。"); let values = vec![1, 2, 3, 4]; println!("values: {values:?}"); }
変数の変更。(「mut」の指定が必要。)
fn main() { let mut x = 100; println!("値 {x} は変数xに拘束されています。"); x = 200; println!("値 {x} は変数xに拘束されています。"); }
変数の宣言と初期化を分ける。(初回の代入は許されている。)
fn main() { let x; x = 100; println!("値 {x} は変数xに拘束されています。"); }
変数は型を明示することができる。 異なる型同士で計算する場合、as を使って型を合わせる。
fn main() { let x: i32 = 100; let y: f32 = 0.5; let result = (x as f32) * y; println!("計算結果は {result} です。"); }
変数の型は、「u16」「i32」「f64」などがある。(符号なし整数、符号あり整数、浮動小数点。) 例えば「u8」で宣言した変数には0〜255を格納できるが、ここに300を格納しようとすると「error: literal out of range for `u8`」のようにエラーになる。 型の一覧は、以下などの記事が参考になる。 Rustの数値型をマスターしたい(Part1) #Rust - Qiita https://qiita.com/LynxLevin/items/6cf47f5f68abe573f135 型が指定されていなければ、一例だがデフォルトタイプにより以下のように解釈される。
let x = 10; // i32 let y = 3.14; // f64
基本的には「i32」を使うことが勧められている。 「整数を扱うならi32を使う、小数を扱うならf64を使う」としておけば良さそう。(そもそも、型推論に任せれば良さそう。) 32bitが安定して早いらしい。 Data Types - The Rust Programming Language https://doc.rust-lang.org/book/ch03-02-data-types.html > So how do you know which type of integer to use? If you’re unsure, Rust’s defaults are generally good places to start: integer types default to i32. ■定数 変数には任意の式を代入できるが、定数には定数式しか代入できない。 定数名には、大文字のスネークケースを使うといい。
fn main() { const TAX_RATE: f32 = 1.1; let price: i32 = 100; let total_price = (price as f32 * TAX_RATE) as i32; println!("100円の税込み価格は {total_price} 円です。"); }
■文字列 後述の「文字列型」を参照。 ■タプル 複数の値をまとめて扱うことができる。 それぞれの値に名前は無い。 要素数は固定で、インデックスは0から始まる数値となる。 「t[i]」のように、インデックスに変数を使用できない。 タプル内の一部の型を、後から変更することはできない。
fn main() { let t = (2, 3.14, 0); let (a, b, c) = t; println!("a={a}, b={b}, c={c}"); let x = t.0; let y = t.1; let z = t.2; println!("x={x}, y={y}, z={z}"); }
■配列 配列のサイズは固定で、コンパイル時に決まっている必要がある。 要素数を可変させたい場合、ベクターを使う必要がある。 型の異なる要素を含むことはできない。
fn main() { let a = [1, 2, 3, 4, 5]; let x = a[0]; let y = a[1]; let z = a[2]; println!("x={x}, y={y}, z={z}"); }
■条件分岐 条件は、必ず「論理値を返すbool型」の結果にする必要がある。
fn main() { let age = 20; if age >= 25 { println!("選挙権と被選挙権があります。"); } else if age >= 18 { println!("選挙権があります。"); } else { println!("選挙権がありません。"); } }
値を返す条件式。 セミコロンの有無に注意。
fn main() { let age = 20; let result = if age >= 25 { "選挙権と被選挙権があります。" } else if age >= 18 { "選挙権があります。" } else { "選挙権がありません。" }; println!("{}", result); }
switch文に似た分岐として、match文がある。
fn main() { let size = 'S'; match size { 'Z' => println!("{size}はアルファベット最後の文字です。"), // 単一値のマッチング 'S' | 'M' | 'L' => println!("{size}はサイズを表すアルファベットです。"), // 複数値のマッチング '0'..='9' | 'A'..='F' => println!("{size}は16進数で使う文字です。"), // 範囲を指定したマッチング _ => println!("{size}はいずれでもない文字です。"), // いずれにもマッチしなかった場合 }; }
以下のようにすると、値を返すことができる。
fn main() { let size = 'S'; let explain = match size { 'Z' => "アルファベット最後の文字", // 単一値のマッチング 'S' | 'M' | 'L' => "サイズを表すアルファベット", // 複数値のマッチング '0'..='9' | 'A'..='F' => "16進数で使う文字", // 範囲を指定したマッチング _ => "いずれでもない文字", // いずれにもマッチしなかった場合 }; println!("{size}は{explain}です。"); }
■繰り返し whileによる繰り返し。
fn main() { let max = 10; let mut count = 1; let mut sum = 0; while count <= max { sum += count; count += 1; } println!("{max}までの合計は{sum}です。"); }
loopによる繰り返し。 最もシンプルに繰り返しができる。
fn main() { loop { // 永遠に続くのでCtrl+Cを入力して止める println!("永遠に続くループ。"); } }
forによる繰り返し。 なお、C/C++などで伝統的に使われている「初期化、条件、繰り返し前処理」のような構文は無い。(そのような構文はwhileを使って記述する。)
fn main() { let scores = [90, 80, 85, 70, 95]; for score in scores.iter() { println!("点数は{score}点です。"); } }
iterでイテレータを扱うことはできる。 イテレータ|Rust のイテレータとクロージャを理解して rayon で並列処理する https://zenn.dev/suzuki_hoge/books/2024-04-rust-for-rayon-a450094c0faf3c/viewer/2-iterator iter()とinto_iter()の違いとちょっとした落とし穴 #Rust - Qiita https://qiita.com/harvath/items/b79eaf61e73e79e3fc0f ■関数
fn sample() { println!("シンプルな関数です。"); } fn main() { println!("関数を呼び出します。"); sample(); println!("関数を呼び出しました。"); }
引数のある関数。
fn sum(x: i32, y: i32) { println!("合計は{}です。", x + y); } fn main() { println!("関数を呼び出します。"); sum(3, 2); println!("関数を呼び出しました。"); }
戻り値のある関数。 Rustでは「最後に評価された式」が返されるが、他言語のようにreturnで返すこともできる。 (returnを使わない方が「Rustらしいプログラム」とされる。)
fn sum(x: i32, y: i32) -> i32 { //return x + y; // returnで返す x + y // 最後に評価された式で返す } fn main() { println!("関数を呼び出します。"); let a = sum(3, 2); println!("合計は{a}です。"); println!("関数を呼び出しました。"); }
■関数合成 関数を動的に作成し、そこに引数を渡すなどして処理できる。
fn add(a: i32) -> impl Fn(i32) -> i32 { move |b| a + b } fn main() { let add_five = add(5); // add(5)が「5を足す関数」を返す let result1 = add_five(3); // add_five内で「5 + 3 = 8」が計算される println!("{result1}"); // 出力: 8 let add_seven = add(7); // add(7)が「7を足す関数」を返す let result2 = add_seven(2); // add_five内で「7 + 2 = 9」が計算される println!("{result2}"); // 出力: 9 let result3 = add(1)(5); // 直接関数を指定できる。この場合「1 + 5 = 6」が計算される println!("{result3}"); // 出力: 6 }
■クロージャ 他の言語で「無名関数」や「ラムダ式」と呼ばれるもの。
fn main() { let square = | x: i32 | { x * x }; println!("{}", square(9)); }
■マクロ マクロは「マクロ名!」で呼び出すことができる。
macro_rules! log { ($x:expr) => { println!("{}", $x); } } fn main() { log!("TEST"); }
Rustのマクロを覚える #Rust - Qiita https://qiita.com/k5n/items/758111b12740600cc58f Rust の macros はどういう時に使われているのか? https://zenn.dev/linnefromice/articles/when-to-use-macros-in-rust ■所有権 スカラー型における所有権。 以下のコードは実行できる。
fn main() { let x = 1; let y = x; println!("xは{x}です。"); // 「xは1です。」となる println!("yは{y}です。"); // 「yは1です。」となる }
文字列型における所有権。 以下のコードは実行できない。(文字列の場合は値ではなく参照を保持しているが、参照の場合はメモリ安全のために所有権の概念が出てくる。) ただし「let s2 = s1;」の部分を「let s2 = s1.clone();」と書き換えると実行できる。(この場合は所有権の移動ではなく、所有権の複製となる。)
fn main() { let s1 = String::from("Hello, Rust!"); //let s1 = "Hello, Rust!".to_string(); // 文字列の定義は、このように書くこともできる //let s1 = "Hello, Rust!"; // 文字列の定義は、このように書くこともできる(ただしこの場合、文字列リテラルを参照するデータ型になり、String型とは別物になる) let s2 = s1; //let s2 = s1.clone(); // このように書けばエラーにならずに実行できる println!("s1は{s1}です。"); // コンパイルエラーになるので実行されない println!("s2は{s2}です。"); }
まとめると、以下2つのプログラムは「値のコピー」になる。 整数 (i32) は、Copyトレイトを実装しているため。 文字列リテラル(&str)はCopyではないが、xは参照(ポインタ)を持っていて、その参照がコピーされるため。
let x = 1; let y = x;
let x = "Hello, Rust!"; let y = x;
以下2つのプログラムは「所有権の移動」になる。 これらはヒープ領域に文字列データが確保されるため、元々の値は無効になる。
let x = "Hello, Rust!".to_string(); let y = x;
let s1 = String::from("Hello, Rust!"); let y = x;
■借用と参照 検索対象の文字列を後から使用するために、「s2」に代入しなおしている。 ただしこの場合、プログラムが冗長になってしまっている。
fn main() { let s = String::from("Hello, Rust!"); let c = 'R'; let (s2, pos) = search_position(s, c); println!("文字'{c}'は「{s2}」内の{pos}文字目にあります。"); // ここではsを参照できない } fn search_position(s: String, c: char) -> (String, usize) { let pos = s.find(c).unwrap(); (s, pos) }
参照で値を渡すと、読み取るだけの値として渡すことができる。 この場合、所有権が移動されないので、プログラムを簡潔に書くことができている。
fn main() { let s = String::from("Hello, Rust!"); let c = 'R'; let pos = search_position(&s, c); // 参照(&)で値を渡し、位置だけを受け取る println!("文字'{c}'は「{s}」内の{pos}文字目にあります。"); } fn search_position(s: &String, c: char) -> usize { // 参照(&)で値を受け取る let pos = s.find(c).unwrap(); pos // 位置だけを返す }
参照が有効なとき、値の移動はできなくなる。
fn main() { let s = String::from("Hello, Rust!"); let r = &s; println!("sは「{s}」です。"); println!("rは「{r}」です。"); // rを参照できる let s2 = s; println!("s2は「{s2}」です。"); println!("rは「{r}」です。"); // sの所有権が移動したので、rを参照できない }
参照で値を渡す際に「mut」を付けると、変更可能な参照となる。 (変数の宣言時も、同様に「mut」で宣言しておく必要がある。)
fn main() { let mut s = String::from("Hello"); println!("変更前の文字列は「{s}」です。"); change_string(&mut s); println!("変更後の文字列は「{s}」です。"); } fn change_string(s: &mut String) { s.push_str(", Rust!"); }
所有権、借用、参照については、後述の「ライフタイム」「参照型」も参照。 ■unwrap 「unwrap()」は、Option型やResult型の値がSomeやOkであることを前提として、値を取り出すもの。 値がNoneだった場合、panicが発生する。
fn main() { let opt: Option<i32> = Some(42); println!("{}", opt.unwrap()); // 「42」と表示される let opt: Option<i32> = None; println!("{}", opt.unwrap()); // panicが発生する }
ただしプログラムがパニック(クラッシュ)を起こすようなような処理は、特にプロダクション環境では避けるべき。 このような場合、「unwrap()」ではなく「unwrap_or()」を使用すればデフォルト値を返すことができ、安全に処理を行える。
fn main() { let opt: Option<i32> = Some(42); println!("{}", opt.unwrap_or(0)); // 「42」と表示される let opt: Option<i32> = None; println!("{}", opt.unwrap_or(0)); // 「0」と表示される }
Rust Optionとunwrapとは 使い方と注意点について|webdrawer https://note.com/webdrawer/n/n069975792f6c rust - Rustの"unwrap()"は何をするものですか? - スタック・オーバーフロー https://ja.stackoverflow.com/questions/1730/rust%E3%81%AEunwrap%E3%81%AF%E4%BD%95%E3%82%92%E3%81%99%... Rust のエラーハンドリングはシンタックスシュガーが豊富で完全に初見殺しなので自信を持って使えるように整理してみたら完全に理解した #Rust - Qiita https://qiita.com/nirasan/items/321e7cc42e0e0f238254 ■構造体 構造体はstructを使って定義できる。
struct Person { name: String, height: f64, weight: f64, } fn main() { let taro = Person { name: String::from("山田太郎"), height: 170.0, weight: 60.0, }; println!("{}さんの身長は{}、体重は{}です。", taro.name, taro.height, taro.weight); }
構造体のメソッド。 メソッドは「impl」内に「fn」を使って実装する。 fnの引数には「&self」が必要で、値を参照するときは「selft」を使う。
struct Person { name: String, height: f64, weight: f64, } impl Person { fn bmi(&self) -> f64 { self.weight / ((self.height / 100.0) * (self.height / 100.0)) } } fn main() { let taro = Person { name: String::from("山田太郎"), height: 170.0, weight: 60.0, }; println!("{}さんの身長は{}、体重は{}です。", taro.name, taro.height, taro.weight); println!("{}さんのBMIは{}です。", taro.name, taro.bmi()); }
メソッドの戻り値に自身の型を指定することで、メソッドチェーンを使うことができるようになる。
struct Person { name: String, } impl Person { fn say_hello(&self) -> &Self { println!("{}さんこんにちは。", self.name); self } fn say_bye(&self) -> &Self { println!("{}さんさようなら。", self.name); self } } fn main() { let taro = Person { name: String::from("山田太郎"), }; taro.say_hello().say_bye(); }
■列挙型 列挙型はenumを使って定義できる。
enum Card { HEART, SPADE, DIAMOND, CLOVER, } fn main() { let card = Card::SPADE; match card { Card::HEART => println!("カードはハートです。"), Card::SPADE => println!("カードはスペードです。"), Card::DIAMOND => println!("カードはダイヤです。"), Card::CLOVER => println!("カードはクローバーです。"), _ => println!("カードはどれでもありません。"), } }
構造体と同様、列挙型にもメソッドを定義できる。
enum Card { HEART, SPADE, DIAMOND, CLOVER, } impl Card { // カードの名前を返すメソッド fn get_name(&self) -> &str { match self { Card::HEART => "ハート", Card::SPADE => "スペード", Card::DIAMOND => "ダイヤ", Card::CLOVER => "クローバー", } } // カードの色を返すメソッド fn get_color(&self) -> &str { match self { Card::HEART | Card::DIAMOND => "赤", Card::SPADE | Card::CLOVER => "黒", } } } fn main() { let card = Card::SPADE; println!("カードは{}です。色は{}です。", card.get_name(), card.get_color()); let another_card = Card::HEART; println!("カードは{}です。色は{}です。", another_card.get_name(), another_card.get_color()); }
■Trait 様々な型に、共通のメソッドを実装するように促すことができる。
trait Tweet { fn tweet(&self); fn tweet_twice(&self) { self.tweet(); self.tweet(); } fn shout(&self) { println!("Uoooohh!!!!"); } } struct Dove(); struct Duck(); impl Tweet for Dove { fn tweet(&self) { println!("Coo!"); } } impl Tweet for Duck { fn tweet(&self) { println!("Quack!"); } } fn main() { let dove = Dove {}; dove.tweet(); dove.tweet_twice(); dove.shout(); let duck = Duck {}; duck.tweet(); duck.tweet_twice(); duck.shout(); }
■Derive 構造体や列挙型に振る舞いを追加できる。 例えば以下のプログラムは「`Data` cannot be formatted using `{:?}`」というエラーになるが、
struct Data{ value: i32, } fn main() { let d = Data {value: 2}; println!("{:?}", d); }
一行目に「#[derive(Debug)]」を指定すると、「{:?}」によって「Data { value: 2 }」という結果を得られるようになる。 Rustの構造体などに追加できる振る舞いを確認する #Rust - Qiita https://qiita.com/apollo_program/items/2495dda519ae160971ed RustのトレイトとDeriveのキホンがよくわからんかったので自分なりに落とし込んだ #Rust - Qiita https://qiita.com/Papillon6814/items/97c175fd94f0107d3821 100日後にRustをちょっと知ってる人になる: [Day 42]属性 (アトリビュート) https://zenn.dev/shinyay/articles/hello-rust-day042 ■ResultとOption Resultは、成功と失敗を扱うことができる。
enum Result<T, E> { Ok(T), // 成功 Err(E), // 失敗 }
Optionは、値の有無を扱うことができる。
enum Option<T> { None, // 値がない Some(T), // 値がある }
Rust のエラーハンドリングはシンタックスシュガーが豊富で完全に初見殺しなので自信を持って使えるように整理してみたら完全に理解した #Rust - Qiita https://qiita.com/nirasan/items/321e7cc42e0e0f238254 RustのOptionとResult #Rust - Qiita https://qiita.com/take4s5i/items/c890fa66db3f71f41ce7 ■パターンマッチ
fn main() { let input1 = "+2"; let input2 = "-3"; let input3 = "42"; print_sign(input1); // "+" print_sign(input2); // "-" print_sign(input3); // "No sign found." } fn print_sign(input: &str) { if let Some(sign @ ('+' | '-')) = input.chars().next() { println!("{sign}"); } else { println!("No sign found."); } }
実行すると以下のように表示される。 + - No sign found. なお sign のように代入して後から使う必要が無い場合、変数名を「_sign」とすることで「warning: unused variable: `sign`」の警告を回避できる。 (つまり、使わない変数の名前の先頭に「_」を付けることで、「使わないが無視したくない」を示すことができる。) ■if-let / let-else 一例だが以下のようにすると、「値が数値か否かで分岐」ができる。
fn main() { let value = "10"; //let value = "ABC"; let val = if let Ok(n) = value.parse::<i32>() { println!("{value} is a number."); // 10 is a number. println!("n = {n}"); // n = 10 } else { println!("{value} is not a number."); }; println!("val = {val:?}"); // val = () }
Rust if letとは 使い方と注意点について|webdrawer https://note.com/webdrawer/n/ne14eda25abeb Rustのlet-else文気持ち良すぎだろ #Rust - Qiita https://qiita.com/namn1125/items/ccedf9cc06b8cef71557 Rustならではな書き方で、少し戸惑いがあるので挙動の詳細を確認する。 まず以下のようにして、同じ値かどうかを比較できる。
#[derive(PartialEq, Debug)] enum Sample { Value1(String), Value2(String), } fn main() { let sample = Sample::Value1("Hello".to_string()); if Sample::Value1("Hello".to_string()) == sample { println!("Your state is Value1({sample:?})"); } else { println!("Your state is Value2."); }; }
それとは別に、Rustでは以下のような書き方で「同じ型かどうか」を比較できる。 これは「sample の型が Sample::Value1 なら、値を取り出して state_inner に格納する」という意味で、型を保証して値を取り出すための構文。 Rustでは先のような「if let Ok」という分岐がよく出てくるが、この延長上にある書き方。
enum Sample { Value1(String), Value2(String), } fn main() { let sample = Sample::Value1("Hello".to_string()); if let Sample::Value1(state_inner) = sample { println!("Your state is Value1({state_inner})"); } else { println!("Your state is Value2."); }; }
なお、以下のように値を受け取ることもできる。
enum Sample { Value1(String), Value2(String), } fn main() { let sample = Sample::Value1("Hello".to_string()); let state_inner = if let Sample::Value1(state_inner) = sample { state_inner } else { println!("Your state is Value2."); return; }; println!("Your state is Value1({state_inner})"); }
■collect イテレータの全要素をコレクションに変換する。 一例だが以下のようにすると、「スペース区切りの文字を逆順に並べて表示」ができる。 「::<Vec<_>>」部分では、収集するコレクションの型を指定している。
fn main() { let value = "A B C"; let tokens = value.split_whitespace().rev().collect::<Vec<_>>(); //let tokens: Vec<_> = value.split_whitespace().rev().collect(); println!("{value}"); println!("{tokens:?}"); }
イテレータ|Rust入門 https://zenn.dev/mebiusbox/books/22d4c1ed9b0003/viewer/a86937 ■ファイル入出力とエラー処理 プロジェクト直下(src内では無い)に message.txt を作成し、適当なメッセージを記述しておく。 文字コードは UTF-8 にしておく。 message.txt
これはサンプルメッセージです。 Rustから読み書きします。
以下のとおりプログラムを記述する。
use std::env; use std::fs::File; use std::io::prelude::*; fn main() { let args: Vec<String> = env::args().collect(); let filename = &args[1]; let mut file = File::open(filename).unwrap(); let mut contents = String::new(); file.read_to_string(&mut contents).unwrap(); println!("{contents}"); }
以下のとおり、ファイル名も指定して実行する。 ファイルが存在しなかったり、文字コードが UTF-8 で無かった場合はエラーになる。 exeファイルを直接実行する場合も同じ。 >cargo run message.txt Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s Running `target\debug\hello.exe message.txt` これはサンプルファイルです。 Rustから読み書きします。 >cargo run message.txt Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s Running `target\debug\hello.exe message.txt` thread 'main' panicked at src/main.rs:8:41: called `Result::unwrap()` on an `Err` value: Os { code: 2, kind: NotFound, message: "指定されたファイルが見つかりません 。" } note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: process didn't exit successfully: `target\debug\hello.exe message.txt` (exit code: 101) >cargo run message.txt Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s Running `target\debug\hello.exe message.txt` thread 'main' panicked at src/main.rs:10:40: called `Result::unwrap()` on an `Err` value: Error { kind: InvalidData, message: "stream did not contain valid UTF-8" } note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: process didn't exit successfully: `target\debug\hello.exe message.txt` (exit code: 101) >hello message.txt これはサンプルファイルです。 Rustから読み書きします。 コマンドライン引数の取得で「args[1]」ではなく「&args[1]」としているのは、所有権移動によるエラーを回避するため、移動ではなく参照にしている。 代わりに「args[1].clone()」としてもエラーを回避できるが、余計なメモリ割り当てが発生するので推奨されない。 参照については、後述の「参照型」も参照。 上記のプログラムは強制的にunwrapしているので、エラー処理が十分ではない。 一例だが以下のようにすれば、ファイルが見つからないときにエラーメッセージを出力できる。
use std::env; use std::fs::File; use std::io::prelude::*; fn main() { let args: Vec<String> = env::args().collect(); let filename = &args[1]; let mut file = File::open(filename).unwrap_or_else(|e| panic!("ファイルを開けませんでした: {e}")); let mut contents = String::new(); file.read_to_string(&mut contents).unwrap_or_else(|e| panic!("ファイルを読み込めませんでした: {e}")); println!("{contents}"); }
以降は、それ以外のエラー処理について。
use std::env; use std::fs::File; use std::io::prelude::*; fn main() { let args: Vec<String> = env::args().collect(); let filename = &args[1]; match File::open(filename) { Ok(mut file) => { let mut contents = String::new(); file.read_to_string(&mut contents).unwrap(); println!("{}", contents); }, Err(error) => { println!("ファイル{filename}が見つかりません。"); println!("{error}") } }; }
>cargo run message.txt Compiling hello v0.1.0 (C:\Users\refirio\Rust\hello) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.67s Running `target\debug\hello.exe message.txt` これはサンプルファイルです。 Rustから読み書きします。 >cargo run message2.txt Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.02s Running `target\debug\hello.exe message2.txt` ファイルmessage2.txtが見つかりません。 指定されたファイルが見つかりません。 (os error 2)
use std::env; use std::fs::File; use std::io; use std::io::prelude::*; fn main() { let args: Vec<String> = env::args().collect(); let filename = &args[1]; match read_file(filename) { Ok(s) => println!("{s}"), Err(e) => println!("エラー:{e}"), }; } fn read_file(filename: &String) -> Result<String, io::Error> { let mut file = File::open(filename)?; let mut contents = String::new(); file.read_to_string(&mut contents)?; Ok(contents) }
>cargo run message.txt Compiling hello v0.1.0 (C:\Users\refirio\Rust\hello) Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.24s Running `target\debug\hello.exe message.txt` これはサンプルファイルです。 Rustから読み書きします。 >cargo run message2.txt Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.03s Running `target\debug\hello.exe message2.txt` エラー:指定されたファイルが見つかりません。 (os error 2) ResultやOption型を使う場合、エラーハンドリングを簡潔に記述できる「?」が利用できる。 例えば以下のプログラムがあったとして、
use std::num::ParseIntError; // 文字列をi32に変換して返す fn check_number(x: &str) -> Result<i32, ParseIntError> { //let num: Result<i32, ParseIntError> = x.parse(); let num = x.parse(); match num { Ok(n) => Ok(n), Err(e) => Err(e), } } fn main() { // 正常なケース match check_number("10") { Ok(num) => println!("Ok: {num}"), Err(e) => println!("Err: {e}"), } // エラーが発生するケース match check_number("abc") { Ok(num) => println!("Ok: {num}"), Err(e) => println!("Err: {e}"), } }
「?」を使えば、check_number関数を以下のように書くことができる。 (エラーが発生した場合、処理を即座に停止して呼び出し元に結果を返す。)
fn check_number(x: &str) -> Result<i32, ParseIntError> { let num: i32 = x.parse()?; Ok(num) }
なお今回の場合、以下のように書くこともできる。 x.parse() は「Result<i32, ParseIntError>」の型を返すため、関数の戻り値の型に適合する。 (最後のセミコロンを省略することで値を返す書き方は、このファイル内の「戻り値のある関数」を参照。)
fn check_number(x: &str) -> Result<i32, ParseIntError> { x.parse() }
■ベクター 配列とは違い、内部に収められる要素の数を増減させることができる。 型の異なる要素を含むことはできない。
fn main() { // ベクター(同じデータ型の値を複数持つことができる) let mut months = Vec::new(); months.push("January"); months.push("February"); months.push("March"); months.push("April"); // 初期値のあるベクターはマクロでも定義できる //let months = vec!["January", "February", "March", "April"]; // 値の参照 println!("Count: {}, {:?}", months.len(), months); println!("{}", months[1]); // 値の走査 for month in &months { println!("{}", month); } }
■ハッシュマップ 他の言語で「連想配列」と呼ばれるもの。
use std::collections::HashMap; fn main() { // ハッシュマップ(キーと値のリスト。ほかの言語では「マップ」や「連想配列」などと呼ばれるもの) let mut months = HashMap::new(); months.insert("January", 1); months.insert("February", 2); months.insert("March", 3); months.insert("April", 4); // ベクターからハッシュマップを生成できる //let keys = vec!["January", "February", "March", "April"]; //let values = vec![1, 2, 3, 4]; //let months: HashMap<_, _> = keys.iter().zip(values.iter()).collect(); // 値の参照 println!("Count: {}, {:?}", months.len(), months); println!("{}", months["February"]); }
■参照型 「&」でアドレスを取得でき、「*」でアドレスの指す値を取得できる。 ただし以下のように、「*」が無くても同様に値が表示される。 これは、標準ライブラリの「fmt::Display」トレイトの実装によって、デフォルトで自動的に参照を解決して値を表示するため。
fn main() { let a = 123; let p = &a; println!("{}", *p); // 「123」と表示される println!("{}", p); // 「123」と表示される }
もし変数のアドレスを表示させたければ、以下のように「:p」を指定するといい。
fn main() { let a = 123; println!("{}", a); // 「123」と表示される println!("{}", &a); // 「123」と表示される println!("{:p}", &a); // 「0x3340aff9a4」などのアドレスが表示される }
ミュータブルな参照を用いることで、参照先の値を変更することができる。
fn main() { let mut a = 123; // ミュータブルな変数を定義 let p = &mut a; // ミュータブルな参照を定義 *p = 456; // 参照先の値を変更 println!("{}", p); // 「456」が表示される }
■文字列型 文字列は「str」で表すが、Rustの基本的な文字列型としては「&str」を使う。 文字列のメモリへのポインタを指すようなもの。
fn main() { let s: &str = "Hello, Rust!"; //let s = "Hello, Rust!"; // このように書くこともできる println!("sは{s}です。"); }
また、ライブラリとして提供される文字列型に「String」がある。
fn main() { // 文字列(文字のコレクション) let jan = String::from("January"); let feb_utf8 = vec![0x46, 0x65, 0x62, 0x72, 0x75, 0x61, 0x72, 0x79]; let feb = String::from_utf8(feb_utf8).unwrap(); println!("{jan}, {feb}"); }
■ボックス コンパイル時にサイズが判らない型を格納できる。(実行時にサイズが判ればいい。) 以下を実行すると「error[E0277]: the size for values of type `[u8]` cannot be known at compilation time」のエラーになる。
fn main() { let byte_array = [b'h', b'e', b'l', b'l', b'o']; print(byte_array); } fn print(s: [u8]) { println!("{s:?}"); }
以下のようにボックスで渡すと、エラーにならなくなる。
fn main() { let byte_array = [b'h', b'e', b'l', b'l', b'o']; print(Box::new(byte_array)); } fn print(s: Box<[u8]>) { println!("{s:?}"); }
■ジェネリクス型 型を指定せずに処理を作ることができる。 以下は構造体の定義に「i32」や「f64」を明示していないので、構造体を呼び出す時点で型を決定できる例。
struct Range<T> { min: T, max: T, } fn main() { let int_range = Range { min: 1, max: 10 }; let float_range = Range { min: 2.0, max: 5.5 }; // 型を明示することもできる //let int_range: Range<i32> = Range { min: 1, max: 10 }; //let float_range: Range<f32> = Range { min: 2.0, max: 5.5 }; println!("int_range.min = {}, int_range.max = {}", int_range.min, int_range.max); println!("float_range.min = {}, float_range.max = {}", float_range.min, float_range.max); }
ジェネリクス型の構造体にもメソッドを定義できる。 ただしminとmaxの値は数字とは限らないため、単純に四則計算を行うことはできない。 この場合、「use std::ops::Sub」を指定することで、引き算を行なうことを認められるようになる。 ただしそれだけだと「error[E0507]: cannot move out of `self.min` which is behind a shared reference」というエラーになる。 詳細として「move occurs because `self.min` has type `T`, which does not implement the `Copy` trait」が表示される。 意味は「`Copy` トレイトが実装されていないため move が発生します」となり、指定のとおり「+ Copy」を追加することでプログラムを実行できるようになる。 (Copyトレイトにより、代入時に複製が行なわれるようにする。例えばString型はCopyトレイトを実装していないので、値は複製されず移動される。)
use std::ops::Sub; struct Range<T> { min: T, max: T, } impl<T> Range<T> where T: Sub<Output = T> + Copy { fn difference(&self) -> T { self.max - self.min } } fn main() { let int_range = Range { min: 1, max: 10 }; let float_range = Range { min: 2.0, max: 5.5 }; println!("int_range.min = {}, int_range.max = {}, int_range.difference = {}", int_range.min, int_range.max, int_range.difference()); println!("float_range.min = {}, float_range.max = {}, float_range.difference = {}", float_range.min, float_range.max, float_range.difference()); }
「impl<T> Range<T> where T: Sub<Output = T> + Copy」にある「Sub」は、1行目にある「use std::ops::Sub;」によって使えるようになったもの。 ただし「Copy」はuseを使用しなくても使用することはできる。 これは、「Copy」が Rustのプリミティブなトレイトであり、標準のプレリュード(prelude)に含まれているため。 プレリュードは、よく使われる型やトレイトが自動的に導入される仕組み。 例えば「Option<T>」「Result<T, E>」などの型や、「Copy」「Clone」などのトレイトが、プレリュードに含まれている。 ■クラス Rustにはクラスが無い。(代わりに構造体を使う。) オブジェクト指向経験者のためのRust入門 #Rust - Qiita https://qiita.com/nacika_ins/items/cf3782bd371da79def74
ライフタイム
&を用いた参照を利用した際に、解放されたメモリを参照してしまうことを避けるための仕組み。 例えば以下のプログラムは、実行すると「error[E0597]: `x` does not live long enough」のエラーになる。 printlnでrを参照する時点で、参照元のxは破棄されて内容を参照できないため。 (なお「r = x;」なら、参照による所有権の移動ではなく複製が行なわれるため、エラーにはならない。)
fn main() { { let r; { let x = 5; r = &x; } println!("r: {r}"); } }
上記はプログラムが単純だが、「&str」で文字列を参照する際に同じ問題が発生する。 RustのLifetimeってなんなん https://zenn.dev/ucwork/articles/6de5c9c2257f2d Rustのライフタイムについて知りたい #Rust - Qiita https://qiita.com/toreis/items/970bcfed6a930e3c84dc Rust の 所有権、借用、ライフタイムについて初心者目線で説明と整理を試みる | blog.ojisan.io https://blog.ojisan.io/rust-ownership-wakaran/ ライフタイム|Rust入門 https://zenn.dev/mebiusbox/books/22d4c1ed9b0003/viewer/fbd089 > ライフタイムは&演算子の後ろに指定します.慣例的に a,b,c,… と指定します. ■関数でのライフタイム 以下は文字列の長さを比較するプログラムだが、実行すると「error[E0106]: missing lifetime specifier」のエラーになる。 関数longestにxとyが渡されているが、関数からどちらの値が返ってくるかコンパイル時に確定できないため。
fn main() { let string1 = "ABC"; let string2 = "ABCD"; let result = longest(string1, string2); println!("The longest string is: {result}"); } fn longest(x: &str, y: &str) -> &str { if x.len() > y.len() { x } else { y } }
以下のとおりライフタイム「'a」を設定すると、xとyの両方が同じ期間だけ生きることを宣言でき、エラーにならなくなる。 (実行結果は「The longest string is: ABCD」となる。)
fn main() { let string1 = "ABC"; let string2 = "ABCD"; let result = longest(string1, string2); println!("The longest string is: {result}"); } fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { if x.len() > y.len() { x } else { y } }
また以下のように、「参照を含まずにデータを共有する」ように変更することでもエラーを回避できる。 (前述の「借用と参照」にあるコードと同じような内容になる。) ライフタイムの管理が不要になるメリットはあるが、文字列のコピーが発生するのでメモリ使用量が増え、パフォーマンスも落ちる。
fn main() { let string1 = String::from("ABC"); let string2 = String::from("ABCD"); let result = longest(string1, string2); println!("The longest string is: {result}"); } fn longest(x: String, y: String) -> String { if x.len() > y.len() { x } else { y } }
■構造体でのライフタイム 以下は構造体を使用するプログラムだが、実行すると「error[E0106]: missing lifetime specifier」のエラーになる。
struct Person { name: &str, } fn main() { let taro = Person { name: "山田太郎", }; println!("{}さんこんにちは。", taro.name); }
参照を構造体のフィールドとして使う場合、参照の寿命(ライフタイム)を明示的に指定する必要がある。 Rustには「ライフタイム省略規則」があり、関数の引数や戻り値など、明らかな場合だけは自動で補完してくれるが、構造体は対象外となっている。 詳細は後述の「構造体でのライフタイム(ChatGPTによる詳細解説)」も参照。 以下のとおりライフタイム「'a」を設定するとエラーにならなくなる。 (実行結果は「山田太郎さんこんにちは。」となる。)
struct Person<'a> { name: &'a str, } fn main() { let taro = Person { name: "山田太郎", }; println!("{}さんこんにちは。", taro.name); }
また以下のように、「参照を含まずにデータを共有する」ように変更することでもエラーを回避できる。 (前述の「構造体」にあるコードと同じような内容になる。) ライフタイムの管理が不要になるメリットはあるが、文字列のコピーが発生するのでメモリ使用量が増え、パフォーマンスも落ちる。
struct Person { name: String, } fn main() { let taro = Person { name: String::from("山田太郎"), }; println!("{}さんこんにちは。", taro.name); }
■構造体でのライフタイム(ChatGPTによる詳細解説) Rustには「ライフタイム省略規則」があり、関数の引数や戻り値など、明らかな場合だけは自動で補完してくれるが、構造体は対象外となっている。 …について、どうして構造体は対象外なのかをChatGPTに聞いた際の回答。 ↓ 関数ではライフタイムが省略できるのに、構造体ではダメなのはなぜか? ──これはRustの安全性と明確性を両立するための設計方針に深く関わっています。 結論から言うと: 構造体は"データの設計図"であり、その参照の寿命をはっきり明示する責任があるため、ライフタイム省略の対象外にされているのです。 理由?: 構造体は関数と違って「文脈」がない 関数のライフタイム省略は、「関数の引数と戻り値の関係」が分かっているから可能です。 たとえば
fn get_name(name: &str) -> &str { name }
このようなケースでは ・引数のライフタイムが1つしかない → 戻り値のライフタイムはそれと同じでよい ・明確な「借用の流れ」がある → 安全に推論可能 関数には文脈(引数 → 戻り値)があります。 しかし、構造体では
struct Person { name: &str, // ← これ、どのライフタイム? }
この時点では どこから借用してるかも、どう使われるかも全く分からない ため、推論が危険なんです。 たとえばこのPersonが ・一時変数の参照を持っているのか ・グローバルな静的変数を指しているのか ・関数の引数の一部なのか 何の情報もないため、Rustコンパイラは安全に判断できません。 理由?: 明示することで安全性と可読性を保証 構造体は「データ構造を定義する型」なので、長期的に使われる可能性が高いです。 もし構造体がライフタイムを省略できてしまったら ・どの参照がどの寿命に依存しているか分からない ・API設計として不明確 ・ライブラリ使用者にとって危険 となってしまいます。 Rustでは、安全性と同じくらい「明示性」を重視しています。構造体にライフタイムを明示させることで ・使う側もライフタイムを意識する ・借用関係を設計レベルで可視化できる ・コンパイルエラーで「何がどの寿命に依存しているか」が明確になる といった利点があるのです。 Rustは「曖昧さより明示性」を選ぶ言語です。 短期的な書きやすさよりも、長期的なバグのなさと可読性を優先しています。 ■その他 初期はライフタイムの明記が必須だったらしいが、今は以下の場合にライフタイムを明記しなくてもいいようになっている…らしい。 構造体のメソッドはselfを持つことが多いので、ライフタイムの指定は不要になることが多い…らしい。 ・参照の各引数は、独自のライフタイム引数を得る。 ・1つだけ入力ライフタイム引数があるなら、そのライフタイムが全ての出力ライフタイム引数に代入される。 ・selfのあるメソッドの場合、selfのライフタイムが全出力ライフタイム引数に代入される。 以下は構造体のメソッドfirst_charを定義しているが、このように「self」が渡されることが多い。
struct Person<'a> { name: &'a str, } impl<'a> Person<'a> { fn first_char(&self) -> Option<char> { self.name.chars().next() } } fn main() { let taro = Person { name: "山田太郎", }; println!("{}さんこんにちは。", taro.name); if let Some(first) = taro.first_char() { println!("先頭の文字は{}です。", first); } }
以下メモ。 ・「'a」という記述があるが、これはライフタイム指定でジェネリクスの一種。 ライフタイムは、Rustの借用チェッカーが参照の有効期間を検証するための仕組み。 「a」という名前は、「src」など任意のものを付けられる。 例えば「Value<'src>」の場合、列挙型「Value」内で参照を持つフィールドのライフタイムが「src」であることを示している。 ・ライフタイムは、コンパイラが参照の有効期間を追跡し、メモリ安全性を保障する。 複数の参照が同じライフタイムを共有するか、または別々のライフタイムを持つかを明示することで、より厳密なメモリ管理を行なう。 ・コンパイラは多くの場合、自動的にライフタイムを推測することができるが、特に複雑なケースでは明示的なライフタイム指定が必要になる。 また構造体や列挙型のような型定義で参照を含む場合、ライフタイム指定が必須になる。 ・ライフタイムは他の言語に似た概念が存在しない。 ・多用するとプログラムが読みづらくなるので、指定は最低限に…というもの?
クレートとモジュール
・クレート … ソースコードが書かれたファイル。実行バイナリとライブラリがある。 ・実行バイナリ … エントリーポイント(main関数)を持つクレート。 ・ライブラリクレート … エントリーポイント(main関数)を持たないクレート。 ・モジュール … クレート内に、階層構造を持った名前空間を定義できる。 ■準備 以下のとおり、改めてプロジェクトを作成するものとする。 >cd C:\Users\refirio\Rust >cargo new example --bin >cd example この時点で、Cargo.toml の内容は以下のとおり。
[package] name = "example" version = "0.1.0" edition = "2021" [dependencies]
src/main.rs の内容は以下のとおり。
fn main() { println!("Hello, world!"); }
以下のとおり実行できる。 >cargo run Compiling example v0.1.0 (C:\Users\refirio\Rust\example) Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.31s Running `target\debug\example.exe` Hello, world! 実行すると Cargo.lock が作成される。 あわせて target フォルダ内に、実行可能ファイルなどが作成される。
# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "example" version = "0.1.0"
■外部クレートの導入 以下のサイトで外部クレートを見つけることができる。 crates.io: Rust Package Registry https://crates.io/ 今回「rand」で検索すると、「rand v0.8.5」が見つかった https://crates.io/crates/rand 上記ページの「Install」部分に、インストールコマンドとして「rand = "0.8.5"」が表示されている。 これをもとに、Cargo.toml の最終行を以下のように調整する。
[dependencies] ↓ [dependencies] rand = "0.8.5"
あとはcargoコマンドが、初回ビルド時に外部クレートの依存関係を解決し、必要なものをダウンロードしてくれる。 (依存関係は target フォルダ内に、プログラム自体は C:\Users\refirio\.cargo\registry フォルダ内に保存されるみたい。) >cargo run Updating crates.io index Locking 8 packages to latest compatible versions Adding cfg-if v1.0.0 Adding getrandom v0.2.15 Adding libc v0.2.155 Adding ppv-lite86 v0.2.17 Adding rand v0.8.5 Adding rand_chacha v0.3.1 Adding rand_core v0.6.4 Adding wasi v0.11.0+wasi-snapshot-preview1 (latest: v0.13.1+wasi-0.2.0) Downloaded cfg-if v1.0.0 Downloaded rand_core v0.6.4 Downloaded rand_chacha v0.3.1 Downloaded rand v0.8.5 Downloaded ppv-lite86 v0.2.17 Downloaded getrandom v0.2.15 Downloaded 6 crates (192.4 KB) in 0.39s Compiling cfg-if v1.0.0 Compiling ppv-lite86 v0.2.17 Compiling getrandom v0.2.15 Compiling rand_core v0.6.4 Compiling rand_chacha v0.3.1 Compiling rand v0.8.5 Compiling example v0.1.0 (C:\Users\refirio\Rust\example) Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.52s Running `target\debug\example.exe` Hello, world! クレートの使い方は、「Documentation」部分からリンクが張られている。 今回は、以下のようにすると0〜1の範囲で値を取得できる。
use rand::prelude::*; fn main() { let mut rng = rand::thread_rng(); let x: f64 = rng.gen(); println!("x={x}"); }
■独自クレートの作成 >cd C:\Users\refirio\Rust >cd example example\src\lib.rs として、以下のコードを作成する。
pub mod module_a; mod module_b;
example\src\module_a.rs を作成する。内容はカラにしておく。 example\src\module_b.rs として、以下のコードを作成する。
mod module_c; mod module_d;
example\src\module_b\module_c.rs を作成する。内容はカラにしておく。 example\src\module_b\module_d.rs を作成する。内容はカラにしておく。 src/main.rs の内容を以下にしておく。
use example::module_a; // モジュールは「use」で呼び出せる //use example::module_b; // 「pub」を指定していないので呼び出せない fn main() { println!("Hello, world!"); }
>cargo run 実行すると「warning: unused import」が表示されるものの、ビルドは通る。 また module_a.rs から module_b.rs を呼び出す場合、example\src\module_a.rs を以下のようにする。 つまり同じパッケージ内なら、クレート名の代わりに「crate」を指定する。
use crate::module_b;
テスト
Rustは標準で自動テスト機能をサポートしているので、別途ライブラリやツールをインストールする必要は無い。 以下のコマンドで単体テストを作成できる。 >cargo new --lib testings 以下のとおり、testings\src\lib.rs にテストが作成される。
pub fn add(left: usize, right: usize) -> usize { left + right } #[cfg(test)] mod tests { use super::*; #[test] fn it_works() { let result = add(2, 2); assert_eq!(result, 4); } }
以下のコマンドでテストを実行できる。 テスト実行時はmain関数が実行されず、アノテーション「#[test]」を付けた部分が実行されるようになる。 …と思ったが、そもそもテストは何も実行されていない?(失敗するテストにしても「ok」となってしまう。) >cargo test Finished `test` profile [unoptimized + debuginfo] target(s) in 0.02s Running unittests src/main.rs (target\debug\deps\hello-19e713fe88107cdf.exe) running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s src\main.rs の最後に testings\src\lib.rs の内容を張り付けると、以下のとおりテストを実行できた。 (成功するテストだと「ok」と表示され、失敗するテストにすると、「error」と表示される。) ※テストはテスト専用ファイルではなく、プログラムと同じファイルに「#[test]」として書くため、このような挙動になっているのかもしれない。 ただしこれは単体テストの場合で、結合テストの場合は tests フォルダ内にテストを置くといいらしい。 >cargo test Compiling hello v0.1.0 (C:\Users\refirio\Rust\hello) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.26s Running unittests src/main.rs (target\debug\deps\hello-19e713fe88107cdf.exe) running 1 test test tests::it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s >cargo test Compiling hello v0.1.0 (C:\Users\refirio\Rust\hello) Finished `test` profile [unoptimized + debuginfo] target(s) in 0.23s Running unittests src/main.rs (target\debug\deps\hello-19e713fe88107cdf.exe) running 1 test test tests::it_works ... FAILED failures: ---- tests::it_works stdout ---- thread 'tests::it_works' panicked at src/main.rs:17:9: assertion `left == right` failed left: 4 right: 3 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace failures: tests::it_works test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s error: test failed, to rerun pass `--bin hello`
コンソールアプリケーション
実践Rustプログラミング入門 - 秀和システム あなたの学びをサポート! https://www.shuwasystem.co.jp/book/9784798061702.html ここでは、逆ポーランド記法で計算できるプログラムを作成する。 以下のとおり、プロジェクトを作成する。 >cd C:\Users\refirio\Rust >cargo new rpncalc --bin >cd rpncalc ■コマンドライン引数の処理(クレートを使用しない場合) src/main.rs の内容を以下のようにする。
use std::env; fn main() { let args: Vec<String> = env::args().collect(); println!("{args:?}"); }
以下のように、引数を処理できるようになる。 >cargo run ["target\\debug\\rpncalc.exe"] >cargo run 1 a xyz 2.0 ["target\\debug\\rpncalc.exe", "1", "a", "xyz", "2.0"] 実行ファイルを呼び出す場合も、以下のとおり処理できている。 >rpncalc ["rpncalc"] >rpncalc 1 a xyz 2.0 ["rpncalc", "1", "a", "xyz", "2.0"] ただしこの場合、引数の位置や順序が固定となっている。 このまま作りこむこともできるが、clapを使用すると容易に実装できる。 ■コマンドライン引数の処理(clapクレートを使用する場合) 「clap v4.5.9」を使用する https://crates.io/crates/clap 上記ページの「Install」部分に、インストールコマンドとして「clap = "4.5.9"」が表示されている。 これをもとに、Cargo.toml の最終行を以下のように調整するが、今回は「derive」を使用するために以下のようにする。
[dependencies] ↓ [dependencies] clap = { version = "4.5.9", features = ["derive"] }
クレートの使い方は、「Documentation」部分からリンクが張られている。 今回は、以下のようにするとコマンドライン引数を取得できる。
use clap::Parser; /// Simple program to greet a person #[derive(Parser, Debug)] #[command(version, about, long_about = None)] struct Args { /// Name of the person to greet #[arg(short, long)] name: String, /// Number of times to greet #[arg(short, long, default_value_t = 1)] count: u8, } fn main() { let args = Args::parse(); for _ in 0..args.count { println!("Hello {}!", args.name); } }
そのまま実行すると「必須の引数が無い」のエラーになるが、「--name Taro」を指定すると名前が表示される。 (cargo run で引数を指定する場合、「--」に続けて指定する。) また「--help」を指定するとヘルプが表示されるが、この内容はプログラム内のコメントも使用されている。 >cargo run error: the following required arguments were not provided: --name <NAME> Usage: rpncalc.exe --name <NAME> For more information, try '--help'. error: process didn't exit successfully: `target\debug\rpncalc.exe` (exit code: 2) >cargo run -- --name Taro Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s Running `target\debug\rpncalc.exe --name Taro` Hello Taro! >cargo run -- --help Compiling rpncalc v0.1.0 (C:\Users\refirio\Rust\rpncalc) Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.56s Running `target\debug\rpncalc.exe --help` Simple program to greet a person! Usage: rpncalc.exe [OPTIONS] --name <NAME> Options: -n, --name <NAME> Name of the person to greet!! -c, --count <COUNT> Number of times to greet!!! [default: 1] -h, --help Print help -V, --version Print version 実行ファイルを直接実行すると、以下のようになる。 >rpncalc error: the following required arguments were not provided: --name <NAME> Usage: rpncalc --name <NAME> For more information, try '--help'. >rpncalc --name Taro Hello Taro! オプションの「-n」「--name」「-c」「--count」は、構造体で定義した「name」「count」をもとに、自動で決定される。 (名前や先頭の1文字が重複していると、正しく決定できないのでエラーになる。) また、例えば以下のようにすると、オプションを自分で決定できる。この場合、自動判定の内容と同じく「-n」「--name」「-c」「--count」とみなされる。 (「short」「long」を指定しない場合、位置引数であると解釈される。)
struct Args { /// Name of the person to greet #[arg(short = 'n', long = "name")] name: String, /// Number of times to greet #[arg(short = 'c', long = "count", default_value_t = 1)] count: u8, }
詳細は以下のページが解りやすい。 clapの使い方まとめ - ぽよメモ https://poyo.hatenablog.jp/entry/2022/10/10/170000 今回はプログラムを以下のように修正し、これをベースに機能を追加していくものとする。
use clap::Parser; /// RPN Calculator #[derive(Parser, Debug)] #[command( version = "1.0.0", author = "refirio", about = "Super awesome sample RPN calculator." )] struct Args { /// Set the level of verbosity #[arg(short, long)] verbose: bool, /// Formulas written in RPN #[arg(name = "FILE")] formula_file: Option<String>, } fn main() { let args = Args::parse(); match args.formula_file { Some(file) => println!("File specified: {}", file), None => println!("No file specified."), } println!("Is verbosity specified?: {}", args.verbose); }
以下のとおり実行できる。 >cargo run Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s Running `target\debug\rpncalc.exe` No file specified. Is verbosity specified?: false >cargo run -- --version Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.10s Running `target\debug\rpncalc.exe --version` rpncalc 1.0.0 >cargo run -- --help Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s Running `target\debug\rpncalc.exe --help` Super awesome sample RPN calculator. Usage: rpncalc.exe [OPTIONS] [FILE] Arguments: [FILE] Formulas written in RPN Options: -v, --verbose Set the level of verbosity -h, --help Print help -V, --version Print version ■ファイルの読み込み
use clap::Parser; use std::fs::File; use std::io::{BufRead, BufReader}; /// RPN Calculator #[derive(Parser, Debug)] #[command( version = "1.0.0", author = "refirio", about = "Super awesome sample RPN calculator." )] struct Args { /// Set the level of verbosity #[arg(short, long)] verbose: bool, /// Formulas written in RPN #[arg(name = "FILE")] formula_file: Option<String>, } fn main() { let args = Args::parse(); if let Some(path) = args.formula_file { let f = File::open(path).unwrap(); let reader = BufReader::new(f); for line in reader.lines() { let line = line.unwrap(); println!("{}", line); } } else { println!("No file is specified."); } }
プログラムを実行する場所に「input.txt」を作成し、その中に「1 1 +」と書いた場合、 以下のように実行してファイルの内容を読み込むことができる。 >cargo run -- input.txt 1 1 + >cargo run No file is specified. ■標準入力の読み取り
use clap::Parser; use std::fs::File; use std::io::{stdin, BufRead, BufReader}; /// RPN Calculator #[derive(Parser, Debug)] #[command( version = "1.0.0", author = "refirio", about = "Super awesome sample RPN calculator." )] struct Args { /// Set the level of verbosity #[arg(short, long)] verbose: bool, /// Formulas written in RPN #[arg(name = "FILE")] formula_file: Option<String>, } fn main() { let args = Args::parse(); if let Some(path) = args.formula_file { let f = File::open(path).unwrap(); let reader = BufReader::new(f); run(reader, args.verbose); } else { let stdin = stdin(); let reader = stdin.lock(); run(reader, args.verbose); } } fn run<R: BufRead>(mut reader: R, verbose: bool) { let mut line = String::new(); if reader.read_line(&mut line).unwrap() > 0 { println!("{}", line.trim_end()); } }
引数なしで実行すると、以下のように「入力した文字がそのまま表示される」となる。 >cargo run test test また今回は、run関数の内容を以下のようにする。
fn run<R: BufRead>(reader: R, verbose: bool) { for line in reader.lines() { let line = line.unwrap(); println!("{line}"); } }
引数なしで実行すると、以下のように「入力した文字がそのまま表示される」となる。 また入力した後にプログラムは終了せず、再度入力できるようになる。(「Ctrl+C」で終了できる。) >cargo run test test ■計算の準備
use clap::Parser; use std::fs::File; use std::io::{stdin, BufRead, BufReader}; /// RPN Calculator #[derive(Parser, Debug)] #[command( version = "1.0.0", author = "refirio", about = "Super awesome sample RPN calculator." )] struct Args { /// Set the level of verbosity #[arg(short, long)] verbose: bool, /// Formulas written in RPN #[arg(name = "FILE")] formula_file: Option<String>, } struct RpnCalculator(bool); impl RpnCalculator { pub fn new(verbose: bool) -> Self { Self(verbose) } pub fn eval(&self, formula: &str) -> i32 { 0 } } fn main() { let args = Args::parse(); if let Some(path) = args.formula_file { let f = File::open(path).unwrap(); let reader = BufReader::new(f); run(reader, args.verbose); } else { let stdin = stdin(); let reader = stdin.lock(); run(reader, args.verbose); } } fn run<R: BufRead>(reader: R, verbose: bool) { let calc = RpnCalculator::new(verbose); for line in reader.lines() { let line = line.unwrap(); let answer = calc.eval(&line); println!("{answer}"); } }
この時点では、入力された内容に関わらず「0」を返す。 >cargo run -- input.txt 0 0 0 >cargo run 1 1 + 0 ■計算の実装
use clap::Parser; use std::fs::File; use std::io::{stdin, BufRead, BufReader}; /// RPN Calculator #[derive(Parser, Debug)] #[command( version = "1.0.0", author = "refirio", about = "Super awesome sample RPN calculator." )] struct Args { /// Set the level of verbosity #[arg(short, long)] verbose: bool, /// Formulas written in RPN #[arg(name = "FILE")] formula_file: Option<String>, } struct RpnCalculator(bool); impl RpnCalculator { pub fn new(verbose: bool) -> Self { Self(verbose) } pub fn eval(&self, formula: &str) -> i32 { let mut tokens = formula.split_whitespace().rev().collect::<Vec<_>>(); self.eval_inner(&mut tokens) } pub fn eval_inner(&self, tokens: &mut Vec<&str>) -> i32 { let mut stack = Vec::new(); while let Some(token) = tokens.pop() { if let Ok(x) = token.parse::<i32>() { stack.push(x); } else { let y = stack.pop().expect("invalid syntax"); let x = stack.pop().expect("invalid syntax"); let res = match token { "+" => x + y, "-" => x - y, "*" => x * y, "/" => x / y, "%" => x % y, _ => panic!("invalid token"), }; stack.push(res); } // もし「-v」オプションが指定されていれば(無名フィールドの先頭要素に値があれば)、この時点でのトークンとスタックの状態を出力する if self.0 { println!("{tokens:?} {stack:?}"); } } if stack.len() == 1 { stack[0] } else { panic!("invalid syntax") } } } fn main() { let args = Args::parse(); if let Some(path) = args.formula_file { let f = File::open(path).unwrap(); let reader = BufReader::new(f); run(reader, args.verbose); } else { let stdin = stdin(); let reader = stdin.lock(); run(reader, args.verbose); } } fn run<R: BufRead>(reader: R, verbose: bool) { let calc = RpnCalculator::new(verbose); for line in reader.lines() { let line = line.unwrap(); let answer = calc.eval(&line); println!("{answer}"); } }
以下のとおり、逆ポーランド記法で計算できる。 またオプションとして「-v」を指定すれば、計算の過程も表示できる。 >cargo run -- input.txt 2 21 1000000 >cargo run 1 1 + 2 ■テストの追加 main.rs の最後に以下を追加する。
#[cfg(test)] mod tests { use super::*; #[test] fn test_ok() { assert_eq!(2 * 2, 4); } }
以下のとおり、テストを実行できる。 >cargo test running 1 test test tests::test_ok ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s 以下のとおり、テストを修正する。(テスト内容の追加。)
#[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 -"), -1); assert_eq!(calc.eval("2 3 *"), 6); 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_eq!(calc.eval("2 3 ^"), 5); } }
以下のとおり、テストを実行できる。 >cargo test running 2 tests test tests::test_ok ... ok test tests::test_ng - should panic ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ■エラーハンドリングの追加 まずはエラーハンドリングしないプログラムを用意する。 エラーが発生したらexpectにより、「panic!」へエラーメッセージを送るようにはしている。
use std::fs; fn get_int_from_file() -> i32 { let path = "number.txt"; let num_str = 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()); }
プログラムを実行する場所に「number.txt」を作成し、その中に「2」と書いた場合、 以下のように実行して2倍にした値を表示する。 >cargo run 4 「number.txt」が見つからない場合、以下のようにpanicが発生する。 >cargo run thread 'main' panicked at src/main.rs:6:44: failed to open the file.: Os { code: 2, kind: NotFound, message: "指定されたファイルが見つかりません。" } note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace error: process didn't exit successfully: `target\debug\rpncalc.exe` (exit code: 101) このプログラムに、エラーハンドリングを追加する。
use std::fs; fn get_int_from_file() -> Result<i32, String> { let path = "number.txt"; let num_str = fs::read_to_string(path).map_err(|e| e.to_string())?; let ret = num_str .trim() .parse::<i32>() .map(|t| t * 2) .map_err(|e| e.to_string()); ret } fn main() { match get_int_from_file() { Ok(x) => println!("{x}"), Err(e) => println!("{e}"), } }
Result型を使って、返す値によって処理を分岐させる。 今回は「正常終了時はi32を返し、エラー時はStringを返す」とする。 get_int_from_file関数内に「?」があるが、これはResult型を返す関数で使える演算子。 今回の場合は「fs::read_to_string(path) がOkなら値を返し、Errならエラー内容を文字列にして関数からエラーを返す」となっている。 mapとmap_errは、Okの場合の処理とErrの場合の処理を記述できるもの。 「.parse::<i32>() がOkなら値を2倍にして返し、Errならエラー内容を文字列にして関数からエラーを返す」となっている。 以下のように実行でき、エラー時はエラーメッセージが出力される。 >cargo run 4 >cargo run 指定されたファイルが見つかりません。 (os error 2) ■エラーハンドリングの追加(anyhowクレートを使用する場合) 「anyhow v1.0.86」を使用する。 anyhow - crates.io: Rust Package Registry https://crates.io/crates/anyhow Cargo.toml の「[dependencies]」部分に以下を追加する。
anyhow = "1.0"
プログラムを以下のように変更する。 get_int_from_fileの返す方は「i32」のみでいい。 エラー部分は「context」もしくは「with_context」とすることで処理できる。
use std::fs; use anyhow::{Context, Result}; fn get_int_from_file() -> Result<i32> { let path = "number.txt"; let num_str = fs::read_to_string(path).with_context(|| format!("failed to read string from {}", path))?; let ret = num_str .trim() .parse::<i32>() .map(|t| t * 2) .context("failed to parse string"); ret } fn main() { match get_int_from_file() { Ok(x) => println!("{x}"), Err(e) => println!("{e}"), } }
以下のとおり実行できる。 >cargo run 4 >cargo run failed to read string from number.txt … ファイルが存在しない場合。 >cargo run failed to parse string … ファイルの内容が数字で無い場合。 ■エラーハンドリングの追加(thiserrorクレートを使用する場合) ※未検証。 anyhowクレートは自分のアプリケーション作成に、 thiserrorは他のアプリケーションから呼び出されるライブラリ作成に、 それぞれ向いているらしい。 ■逆ポーランド記法のプログラムにエラー処理を追加
use clap::Parser; use std::path::PathBuf; use std::fs::File; use std::io::{stdin, BufRead, BufReader}; use anyhow::{bail, ensure, Context, Result}; /// RPN Calculator #[derive(Parser, Debug)] #[command( version = "1.0.0", author = "refirio", about = "Super awesome sample RPN calculator." )] struct Args { /// Set the level of verbosity #[arg(short, long)] verbose: bool, /// Formulas written in RPN #[arg(name = "FILE")] formula_file: Option<PathBuf>, } 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) } pub 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); } // もし「-v」オプションが指定されていれば(無名フィールドの先頭要素に値があれば)、この時点でのトークンとスタックの状態を出力する if self.0 { println!("{tokens:?} {stack:?}"); } } ensure!(stack.len() == 1, "invalid syntax"); Ok(stack[0]) } } fn main() -> Result<()> { let args = Args::parse(); if let Some(path) = args.formula_file { let f = File::open(path)?; let reader = BufReader::new(f); run(reader, args.verbose) } else { let stdin = stdin(); let reader = stdin.lock(); run(reader, args.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").unwrap(), 5); assert_eq!(calc.eval("50").unwrap(), 50); assert_eq!(calc.eval("-50").unwrap(), -50); assert_eq!(calc.eval("2 3 +").unwrap(), 5); assert_eq!(calc.eval("2 3 -").unwrap(), -1); assert_eq!(calc.eval("2 3 *").unwrap(), 6); assert_eq!(calc.eval("2 3 /").unwrap(), 0); assert_eq!(calc.eval("2 3 %").unwrap(), 2); } #[test] #[should_panic] fn test_ng() { let calc = RpnCalculator::new(false); assert_eq!(calc.eval("2 3 ^").unwrap(), 5); } }
anyhowのbailとensureについては、以下が参考になる。 Rust, anyhowのマクロ(anyhow!, bail!, ensure!)の使い方 | rs.nkmk.me https://rs.nkmk.me/rust-anyhow-macro/ またエラー処理に加えて、パスをString型ではなくstd::path::PathBuf型を使うようにも変更している。
Webアプリケーション
実践Rustプログラミング入門 - 秀和システム あなたの学びをサポート! https://www.shuwasystem.co.jp/book/9784798061702.html ここでは、WebアプリケーションでTODOリストを作成する。 以下のとおり、プロジェクトを作成する。 >cd C:\Users\refirio\Rust >cargo new todo --bin >cd todo 「actix-web v4.8.0」「actix-rt v2.10.0」を使用する https://crates.io/crates/actix-web https://crates.io/crates/actix-rt Cargo.toml の最終行を以下のように調整する。
[dependencies] ↓ [dependencies] actix-web = "4.8" actix-rt = "2.10"
src/main.rs の内容を以下のようにする。
use actix_web::{get, App, HttpServer, Responder}; #[get("/")] async fn index() -> impl Responder { format!("Hello, world!") } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new().service(index) }) .bind(("127.0.0.1", 8080))? .run() .await }
以下のとおり実行する。 >cargo run ブラウザソフトで以下にアクセスすると、画面に「Hello, world!」と表示される。 http://localhost:8080/ 以下のようにすると、さらに http://localhost:8080/hello/Taro にアクセスしたとき「Hello, Taro!」と表示されるようになる。
use actix_web::{get, web, App, HttpServer, Responder}; #[get("/")] async fn index() -> impl Responder { format!("Hello, world!") } #[get("/hello/{name}")] async fn hello(name: web::Path<String>) -> impl Responder { format!("Hello, {name}!") } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .service(index) .service(hello) }) .bind(("127.0.0.1", 8080))? .run() .await }
■データベース Rust大好きっ子のためのデータベース考 https://zenn.dev/kyoheiu/articles/c3b6b6f156e57a ■メモ Rust で Web アプリケーションはどこまで開発できるのか - Speaker Deck https://speakerdeck.com/helloyuk13/rust-de-web-apurikesiyonhadokomadekai-fa-dekirufalseka Rustでサイトを再実装 https://r7kamura.com/articles/2021-11-07-ruby-to-rust-at-this-site RustでWebアプリケーションのバックエンドを開発するには ─ 型システムの堅牢性と柔軟性を業務システムにも! - エンジニアHub|Webエンジニアのキャリアを考える! https://eh-career.com/engineerhub/entry/2022/09/12/093000 Rust on Rails!? Rust 版の Rails と呼ばれる "Loco" を試す! https://zenn.dev/collabostyle/articles/45762b07bc16fb
WebAssembly
RustからWebAssembly (wasm)を生成してJavaScriptとブリッジ通信してみる | DevelopersIO https://dev.classmethod.jp/articles/rust-webassembly-javascript/ RustではじめるWebAssembly入門〜JavaScriptを超える高速なWebアプリ開発を実践しよう (1/2):CodeZine(コードジン) https://codezine.jp/article/detail/14567 動かして学ぶ! Rustの言語仕様 (1/2):CodeZine(コードジン) https://codezine.jp/article/detail/15083 Amazon Prime Videoが動画再生にWebAssemblyを採用。再生デバイス上にWasm VMをデプロイ、高フレームレートなど実現 − Publickey https://www.publickey1.jp/blog/22/amazon_prime_videowebassemblywasm_vm.html WebAssemblyの歴史について https://zenn.dev/hodagi/articles/4925afbeb3c4dc 「第4のブラウザ言語」WebAssemblyが変えるフロントエンド開発 - レバテックラボ(レバテックLAB) https://levtech.jp/media/article/column/detail_484/ ウェブエンジニアでもWasmを使いたい! アフタートーク https://zenn.dev/monicle/articles/a543b8ef919058
画像操作
線形代数学+Rustで画像圧縮のアルゴリズムを実装する #Rust - Qiita https://qiita.com/tronicboy/items/cfa9f68f14f1823c813e Rust で画像処理? #入門 - Qiita https://qiita.com/RotaryKer/items/a9897ddccdd0d8e0a1c2 Rustでカメラから取得した画像に対して画像処理をする - Scepter914 Website https://scepter914.github.io/blog/2021/20210813_rust_image_processing/ Rust研究:画像ファイルの処理 #image - Qiita https://qiita.com/mekamaru/items/7ace29cc7a0ad076f452
Excel操作
Rust で Excel オートメーション (windows-rs 版) - Qiita https://qiita.com/benki/items/42099c58e07b16293609 Rust で Excel オートメーション - Qiita https://qiita.com/benki/items/de2e104a5866fad0ebab
テキストエディタ
hecto: Build Your Own Text Editor in Rust | philipp's blog https://www.flenker.blog/hecto/ 以下はC言語版の紹介記事。 C言語1000行でテキストエディタを作るチュートリアルをやった https://zenn.dev/kawarimidoll/articles/69119b3f5e1172
デスクトップアプリケーション
RustでGUIアプリケーションを開発するためのフレームワークを解説! https://jitera.com/ja/insights/33923 [Rust] icedに入門したのでメモを残す #GUI - Qiita https://qiita.com/Yosh31207/items/81632c6e2b54eb2f6f7c Rust によるデスクトップアプリケーションフレームワーク Tauri | 豆蔵デベロッパーサイト https://developer.mamezou-tech.com/blogs/2022/03/06/tauri/ 軽量RustフレームワークTauriでデスクトップアプリ開発をはじめよう | gihyo.jp https://gihyo.jp/article/2022/10/rust-monthly-topics-02 Electron代替を目指す「Tauri 2.0」正式版に到達。デスクトップアプリとモバイルアプリ開発の両方に対応 − Publickey https://www.publickey1.jp/blog/24/electrontauri_20.html
ブラウザソフト
ちいさな Web ブラウザを作ってみよう https://browserbook.shift-js.info/ ちいさなWebブラウザを作ってみようをやってみる https://zenn.dev/nissy_dev/scraps/ea74073a90f3c0
OS
Writing an OS in Rust https://os.phil-opp.com/ Writing an OS in Rust https://os.phil-opp.com/ja/ Writing an OS in Rustをやる https://zenn.dev/razokulover/scraps/8482e2478bfa14 ゼロからのOS自作入門をRustで実装した https://zenn.dev/egu/articles/2435e740b8284c 「ゼロからのOS自作入門」を Rust でやる (第1章〜第4章) - gifnksmの雑多なメモ https://gifnksm.hatenablog.jp/entry/2021/05/09/155149

Advertisement