- 以下のページで学習を開始
- Rust は rustup と rust-analyzer を homebrew でインストール
- bootstrap で .cargo フォルダがないことを確認の上で rustup-init を実行
- vscode の Rust 機能拡張を追加
"[rust]": { "editor.formatOnSave": true, },
- 単一ファイルの記述
fn main() { println!("Hello, world!"); }
- rustc でコンパイルして実行。これは cc と変わらない
rustc main.rs ./main
- でも実際のプロジェクトはこんな簡単ではないので、cargo を使う。ビルドツールであり、環境依存マネージャーでもあるとのこと。cargo new でプロジェクトを作成
cargo new hello-cargo
- 設定などは Cargo.toml で記述。toml ファイルは vim 以来か。cargo run でプログラム実行
cargo run
- 関数について。関数は fn で記述。Ruby の def と同じ感じ。行は ; で終わるが、終わらない時は次の行を 4 インデントする。このあたりはフォーマッタがなんとかしてくれると信じる
fn main() { // The function declaration is not indented // First step in function body // Substep: execute before First step can be complete // Second step in function body // Substep A: execute before Second step can be complete // Substep B: execute before Second step can be complete // Sub-substep 1: execute before Substep B can be complete // Third step in function body, and so on... }
- println! はマクロ。{} は C 言語の %s みたいな感じ。数字などが渡されても文字列変換メソッドが入るので、型を区別しなくていいということ。
println!("The first letter of the English alphabet is {} and the last letter is {}.", 'A', 'Z');
- {} を書きたい時にはどうするんだろう。と思って色々やってみたが、これでいいのかな。\{\}ではなかった。
fn main() { println!("{}{{}}", 1); }
- 変数について。変数は let で設定。変数は不変(immutable)なので再代入は不可。
let a_number; let a_word = "Ten"; a_number = 10; a_number = 15; // エラー
- 可変(mutable)にしたければ mut を付ける。デフォルトではないということは滅多には使わない感じか。後で出てくる構造体の一部だけ変更するような時に使うくらいではないかとのこと。
let mut a_number = 10; a_number = 15;
- 変数のシャドウ処理。mut だといつでも変更可能になってしまうので、シャドウ処理で同じ変数名を付けることができる。これだとあまり意味がないが、スコープが変わると意味があるかも。
let shadow_num = 5; let shadow_num = shadow_num + 5;
- 型推論は自動で行われるが、自分で型を指定することもできる。
u32
は符号なし32ビット整数
let number: u32 = 14;
- 型を指定しているのに違うものを入れるとコンパイル時点でエラーになる。静的にエラーになるのは嬉しい。
let number: u32 = "14"; // コンパイルエラー
- 整数型
- i8, i16, i32, i64, isize が符号あり
- u8, u16, u32, u64, usize が符号なし
- 型推論できない時は i32
- 浮動小数点型
- f32 と f64。デフォルトは f64
let number_64 = 4.0; let number_32: f32 = 5.0;
- ブール値
- true または false。条件式を実行すると true か false が生成される
let is_bigger = 1 > 4;
- 文字
- '' で括ると文字
- 内部は 21ビットの Unicode code (UTF-8 ではない)
- 文字列1 (str 型)
- "" で括ると文字列。文字列リテラルなどで長さが既知
- 参照する場合には &str になる。& は参照を示す
- 文字列2 (String 型)
- 長さがわかっていない場合。作り方は後で出てくると思う
- タプル
- 表記は Swift と同じ。
- タプルの長さは固定。作成時に決定
- 中身はなんでも良い。下の例では文字列、数値、ブーリアン値が入っている
let tuple_e = ('e', 5i32, true);
- if は条件式を記述。Ruby のように式には () はいらない
if 1 == 2 { println!("True, the numbers are equal."); // } else { println!("False, the numbers are not equal."); }
- Ruby と同じく if 自体が値を返すので、C の三項演算子のように使える。let の文になるので最後にセミコロンが必要。Ruby だと「;」がいらないので気にならないが、Rust だと「};」の形になることに注意。
let formal = true; let greeting = if formal { // if used here as an expression "Good day to you." // return a String } else { "Hey!" // return a String };
- 複数の条件分岐は else if となる。
let num = 500 // num variable can be set at some point in the program let out_of_range: bool; if num < 0 { out_of_range = true; } else if num == 0 { out_of_range = true; } else if num > 512 { out_of_range = true; } else { out_of_range = false; }
- 構造体の種類は3種類
- 従来の構造体: key-value 形式。Ruby の Hash に近いイメージだが、定義した時以上にキーは増えない
- タプル構造体: 中身はタプルでキーはない。要素の並びが固定化されたタプルの雛形
- ユニット構造体: ここでは説明がないので、後で出てきた時に確認
- 構造体の命名規則
- Ruby の Class 名のように大文字で始める
- 従来の構造体
- 定義方法(従来の構造体)
- key-value をあらかじめ宣言する。要素はフィールドと呼ぶとのこと
- 他の二つと違って、最後に「;」を書かないとのこと。これは間違えそうなので注意。
// Classic struct with named fields struct Student { name: String, level: u8, pass: bool }
- 作り方は構造体名を記述後に、Ruby の Hash と同じ形式で記載する。
- name は String なので、"" の str 型は入れられない。String::from で str 型から String 型に変換するようだ
let user_1 = Student { name: String::from("Constance Sharma"), remote: true, level: 2 }; let user_2 = Student { name: String::from("Dyson Tan"), level: 5, remote: false };
- 構造体のフィールドへのアクセスは C 言語と同じく、.フィールド名と書けばよい
println!("{}, level {}. Remote: {}. Grades: {}", user_1.name, user_1.level, user_1.remote); println!("{}, level {}. Remote: {}. Grades: {}", user_2.name, user_2.level, user_2.remote);
- タプル構造体
- 定義方法(タプル構造体)
- タプルには名前がないので、定義時には型だけを並べる
- 通常の構造体と異なり、「;」は必要
// Tuple struct with data types only struct Grades(char, char, char, char, f32);
- タプル構造体は構造体名の後にタプルの生成が入る。
- 通常のタプルとの違いは型の並びが固定化されていること
// Instantiate tuple structs, pass values in same order as types defined let mark_1 = Grades('A', 'A', 'B', 'A', 3.75); let mark_2 = Grades('B', 'A', 'A', 'C', 3.25);
- タプルの要素には.1, .2 のように要素番号を記述する。
println!("{}, {}, {}. Average: {}", mark_1.0, mark_1.1, mark_1.2, mark_1.3, mark_1.4); println!("{}, {}, {}. Average: {}", mark_2.0, mark_2.1, mark_2.2, mark_2.3, mark_2.4);
- 列挙型
- 列挙型について
- C 言語の列挙型とは全く意味が違う。
- elm のカスタム型と同じで、複数のバリアントを同じ枠にハメるための仕組み
- 静的型付けが必要なため、多様性が必要な入力などを取りまとめるために利用
- 列挙型の定義
- 以下の例では WebEvent という列挙型を定義している
- ここでは、
WELoad
,WEKeys
,WEClick
という三つのバリアントをまとめている WELoad
はまだ説明されていないユニット構造体、WEKeys
はタプル構造体、WEClick
は従来の構造体。それぞれは匿名構造体- 列挙型を受け付ける場合には、全てに対して処理を受け付けるようにしなければならない
enum WebEvent { // An enum variant can be like a unit struct without fields or data types WELoad, // An enum variant can be like a tuple struct with data types but no named fields WEKeys(String, char), // An enum variant can be like a classic struct with named fields and their data types WEClick { x: i64, y: i64 } }
- 匿名構造体で処理するのは面倒なので、定義された構造体を使って列挙型を作る方がよいらしい
// Define a tuple struct struct KeyPress(String, char); // Define a classic struct struct MouseClick { x: i64, y: i64 } // Redefine the enum variants to use the data from the new structs // Update the page Load variant to have the boolean type enum WebEvent { WELoad(bool), WEClick(MouseClick), WEKeys(KeyPress) }
- 単純なバリアント。boolean の引数だけを一つもつ場合
let we_load = WebEvent::WELoad(true);
// Instantiate a MouseClick struct and bind the coordinate values let click = MouseClick { x: 100, y: 250 }; // Set the WEClick variant to use the data in the click struct let we_click = WebEvent::WEClick(click);
// Instantiate a KeyPress tuple and bind the key values let keys = KeyPress(String::from("Ctrl+"), 'N'); // Set the WEKeys variant to use the data in the keys tuple let we_key = WebEvent::WEKeys(keys);
- {:#?}を使っている。ただしこのままでは動作しない
println!("\nWebEvent enum structure: \n\n {:#?} \n\n {:#?} \n\n {:#?}", we_load, we_click, we_key);
#[derive(Debug)]
を付ける必要がある。// Define a tuple struct #[derive(Debug)] struct KeyPress(String, char); // Define a classic struct #[derive(Debug)] struct MouseClick { x: i64, y: i64, } // Redefine the enum variants to use the data from the new structs // Update the page Load variant to have the boolean type #[derive(Debug)] enum WebEvent { WELoad(bool), WEClick(MouseClick), WEKeys(KeyPress), }
- 関数の基本
- Rust の関数は fn で始まる。Ruby の def と同じイメージで。ただし、関数のスコープは {}。引数なしでも () は必要の様子。
fn main() { println!("Hello, world!"); goodbye(); } fn goodbye() { println!("Goodbye!"); }
- 関数の入力引数
- 引数は変数名の後ろに型を書く。Swift と同じ形だが、Swift や Objective-C のようにラベルはつけない。Swift の _ 付きのような感じ。今は IDE やエディタのサポートがあるので、ラベルがなくてもミスらないということか。
fn is_divisible_by(dividend: u32, divisor: u32) { // If the divisor is zero, stop execution if divisor == 0 { println!("\nError! Division by zero is not allowed."); } else if dividend % divisor > 0 { println!("\n{} % {} has a remainder of {}.", dividend, divisor, (dividend % divisor)); } else { println!("\n{} % {} has no remainder.", dividend, divisor); } } fn main() { is_divisible_by(12, 4); is_divisible_by(13, 5); is_divisible_by(14, 0); }
- 関数の戻り値
- 関数の戻り値は Swift と同じ形で関数名の後ろに
->
で型を記述する。 - 値を途中で返すときには、return 文を使うことができる。これはステートメントなので「;」が必要
- Ruby と同じで最後に評価した式も返り値にになる。これはステートメントでないので、「;」をつけてはいけない。
- デフォルトは最後に式を書く形だと思われる。
fn is_zero(input: u8) -> bool { if input == 0 { return true; } false } fn main() { if is_zero(0) { println!("The value is zero."); } }
- 演習の解説は省略
- 演習結果はこんな感じになりました。PartialEq だけがまだ説明がないですね。
// Declare Car struct to describe vehicle with four named fields struct Car { color: String, transmission: Transmission, convertible: bool, mileage: u32, } #[derive(PartialEq, Debug)] // Declare enum for Car transmission type // TO DO: Fix enum definition so code compiles enum Transmission { Manual, SemiAuto, Automatic, } // Build a "Car" by using values from the input arguments // - Color of car (String) // - Transmission type (enum value) // - Convertible (boolean, true if car is a convertible) fn car_factory(color: String, transmission: Transmission, convertible: bool) -> Car { // TO DO: Complete "car" declaration to be an instance of a "Car" struct // Use the values of the input arguments // All new cars always have zero mileage Car { color: color, transmission: transmission, convertible: convertible, mileage: 0, } } fn main() { // We have orders for three new cars! // We'll declare a mutable car variable and reuse it for all the cars let mut car = car_factory(String::from("Red"), Transmission::Manual, false); println!( "Car 1 = {}, {:?} transmission, convertible: {}, mileage: {}", car.color, car.transmission, car.convertible, car.mileage ); car = car_factory(String::from("Silver"), Transmission::Automatic, true); println!( "Car 2 = {}, {:?} transmission, convertible: {}, mileage: {}", car.color, car.transmission, car.convertible, car.mileage ); car = car_factory(String::from("Yellow"), Transmission::SemiAuto, false); println!( "Car 3 = {}, {:?} transmission, convertible: {}, mileage: {}", car.color, car.transmission, car.convertible, car.mileage ); }
- 配列
- 配列の定義は2種類
- コンマ区切りで要素を並べる
- 初期値と配列の長さで指定
// Declare array, initialize all values, compiler infers length = 7 let days = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; // Declare array, first value = "0", length = 5 let bytes = [0; 5];
- 配列の要素の方は T で固定。Ruby などと違ってどんな型が入ってもいいわけでない
- 配列のサイズも size で固定。可変長ではない
- 配列の要素は他の言語と同様に 0 から始まる。特に問題はなし
- 範囲外のアクセスについて、コンパイラが判断できる場合にはコンパイルエラーになる
// Set first day of week let first = days[0]; // Set second day of week let second = days[1]; // Set seventh day of week, use wrong index - should be 6 let seventh = days[7]; // コンパイルエラー
- ベクター (翻訳の表記が揺れているがベクターにしておく)
- ベクターの概念
- 配列は固定長だが、ベクターは可変長
<vector>u32
、<vector>String
のように作成する- 型がわかっていない場合には、
<vector><T>
のようにジェネリック(未知)データ型で定義できる - マクロを使ったベクターの初期化
- vec! というマクロを使って、配列からベクターを作成できる
// Declare vector, initialize with three values let three_nums = vec![15, 3, 46]; println!("Initial vector: {:?}", three_nums); // Declare vector, first value = "0", length = 5 let zeroes = vec![0; 5]; println!("Zeroes: {:?}", zeroes);
- new() メソッドで空のベクターが作成できる
- 要素の追加などをしたい場合は、mut にする必要がある
// Create empty vector, declare vector mutable so it can grow and shrink let mut fruit = Vec::new();
- push(<value>) を使って一番後ろに値を追加できる。
- ジェネリックで作成した場合、最初に push した値で型が確定
- 以降は同じ型のものしか入れられない
// Push values onto end of vector, type changes from generic `T` to String fruit.push("Apple"); fruit.push("Banana"); fruit.push("Cherry"); println!("Fruits: {:?}", fruit); fruit.push(1); // コンパイルエラー
// Pop off value at end of vector // Call pop() method from inside println! macro println!("Pop off: {:?}", fruit.pop()); println!("Fruits: {:?}", fruit);
- 要素アクセスは配列と同じ
// Declare vector, initialize with three values let mut index_vec = vec![15, 3, 46]; let three = index_vec[1]; println!("Vector: {:?}, three = {}", index_vec, three);
// Access vector with out-of-bounds index let beyond = index_vec[10]; println!("{}", beyond); パニックメッセージ thread 'main' panicked at 'index out of bounds: the len is 3 but the index is 10', src/main.rs:7:18
- ハッシュマップ
- ハッシュマップの作成
- HashMap<K, V> の形式
- ベクターと同様に拡大可能
- use で HashMap を入れる必要あり
- key, value を追加できるようにするには mut を付ける
- Hash は .insert で key, value を追加
- toString() は String の実態を作成するメソッド。Hash が実態を所有する必要があるため
use std::collections::HashMap; let mut reviews: HashMap<String, String> = HashMap::new(); reviews.insert("Ancient Roman History".to_string(), "Very accurate.".to_string()); reviews.insert("Cooking with Rhubarb".to_string(), "Sweet recipes.".to_string()); reviews.insert("Programming in Rust".to_string(), "Great examples.".to_string());
- get() で値を取得できる
- get の引数は参照文字列(&str)で良いので、直接 "Programming in Rust" でもよい
- この例ではそのことを理解させるために、book という変数に入れている(型は &str)
- get の返り値は取得できない場合もあるので、Optional になるとのこと。
- Rust では Some(中身) という形になるらしい
- Optional の外し方はまだ説明はない
// Look for a specific review let book: &str = "Programming in Rust"; println!("\nReview for \'{}\': {:?}", book, reviews.get(book)); 結果 Review for 'Programming in Rust': Some("Great examples.")
- remove() で値を削除できる
- 削除したものを get すると None になる
// Remove book review let obsolete: &str = "Ancient Roman History"; println!("\n'{}\' removed.", obsolete); reviews.remove(obsolete); // Confirm book review removed println!("\nReview for \'{}\': {:?}", obsolete, reviews.get(obsolete)); 結果 'Ancient Roman History' removed. Review for 'Ancient Roman History': None
- Rust のループは3つ
- loop: 無限ループ
- whlie: 条件が true の間ループ
- for: コレクションを順にループ
- loop
- break まで止まらない
- loop 自体も値を返せる
- break の後に返す値を示せる
- break が複数ある場合には、全てが同じ型でないとエラーになる
- 値が明示的に返されなかった場合には、空のタプルが返却される
let mut counter = 1; // stop_loop is set when loop stops let stop_loop = loop { counter *= 2; if counter > 100 { // Stop loop, return counter value break counter; } }; // Loop should break when counter = 128 println!("Break the loop at counter = {}.", stop_loop);
- while
- 条件を満足する間ループする
let mut counter = 0; while counter < 5 { println!("We loop a while..."); counter = counter + 1; }
次のページはこちらRust 学習記録(2)