メモ > 技術 > プログラミング言語: Rust > 基本の文法
基本の文法
以下を参考に、基本の文法を確認する
プログラミング言語「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);
}
なお Rust 1.58 から、フォーマット文字列内で変数名を直接参照できる
(「vec!」については、後述の「ベクター」を参照)
Rust 1.58からフォーマット文字列で変数の直接参照が可能に! - dotTrail
https://dottrail.codemountains.org/rust-format-strings/
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;
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={}, b={}, c={}", a, b, c);
let x = t.0;
let y = t.1;
let z = t.2;
println!("x={}, y={}, z={}", x, y, z);
}
■配列
配列のサイズは固定で、コンパイル時に決まっている必要がある
要素数を可変させたい場合、ベクターを使う必要がある
型の異なる要素を含むことはできない
fn main() {
let a = [1, 2, 3, 4, 5];
let x = a[0];
let y = a[1];
let z = a[2];
println!("x={}, y={}, z={}", x, y, z);
}
■条件分岐
条件は、必ず「論理値を返すbool型」の結果にする必要がある
fn main() {
let age = 20;
if age >= 25 {
println!("選挙権と被選挙権があります。");
} else if age >= 18 {
println!("選挙権があります。");
} else {
println!("選挙権がありません。");
}
}
値を返す条件式
セミコロンの有無に注意
fn main() {
let age = 20;
let s = if age >= 25 {
"選挙権と被選挙権があります。"
} else if age >= 18 {
"選挙権があります。"
} else {
"選挙権がありません。"
};
println!("{}", s);
}
switch文に似た分岐として、match文がある
fn main() {
let size = 'S';
match size {
'Z' => println!("{}はアルファベット最後の文字です。", size), // 単一値のマッチング
'S' | 'M' | 'L' => println!("{}はサイズを表すアルファベットです。", size), // 複数値のマッチング
'0'..='9' | 'A'..='F' => println!("{}は16進数で使う文字です。", size), // 範囲を指定したマッチング
_ => 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!("関数を呼び出しました。");
}
戻り値のある関数
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!"; // このように書くこともできる
let s2 = s1;
println!("s1は{}です。", s1); // コンパイルエラーになるので実行されない
println!("s2は{}です。", s2);
}
■借用と参照
検索対象の文字列を後から使用するために、「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()); // 「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.");
}
}
実行すると以下のように表示される
sign のように代入して後から使う必要が無い場合、変数名を「_sign」とすることで「warning: unused variable: `sign`」の警告を回避できる
(つまり、使わない変数の名前の先頭に「_」を付けることで、「使わないが無視したくない」を示すことができる)
+
-
No sign found.
■if-let / let-else
一例だが以下のようにすると、「値が数値か否かで分岐」ができる
fn main() {
let value = "10";
//let value = "ABC";
let val = if let Ok(n) = value.parse::<i32>() {
println!("{} is a number.", value); // 10 is a number.
println!("n = {}", n); // n = 10
} else {
println!("{} is not a number.", value);
};
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
■ファイル入出力とエラー処理
プロジェクト直下に 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から読み書きします。
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」と表示される
}
ミュータブルな参照を用いることで、参照先の値を変更することができる
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);
}
■ジェネリクス型
型を指定せずに処理を作ることができる
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());
}
■クラス
Rustにはクラスが無い(代わりに構造体を使う)
オブジェクト指向経験者のためのRust入門 #Rust - Qiita
https://qiita.com/nacika_ins/items/cf3782bd371da79def74