rustのインストール
[!CAUTION]
無知なためタイポやIF文ではなくIF式なことなどさまざまな違いがありました。修正できていない箇所もあるかと思われますのでRust公式のチュートリアルと並行してみることをお勧めいたします。
homebrewを使用している場合
brew install rustup-init
homebrewを使用していない場合
curl --proto '=https' --tlsv1.2 https://sh.rustup.rs -sSf | sh
初期化
rustup-init
...You can uninstall at any time with rustup self uninstall and
these changes will be reverted.
Current installation options:
default host triple: aarch64-apple-darwin
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
Vscodeの拡張機能
Hello, world
本格的にする時はcargo new プロジェクト名でプロジェクトを作りますが今回の場合別になくても動きます
// main.rs
fn main(){
println!("Hello, world!");
}
コンパイル
以下のコマンドを実行するとrustのファイル名と同じファイルが作成されます。(macではUnix実行ファイルとして認識される)
rustc ファイル名.rs
finderから起動する方法もありますが以下のような方法も可能です。
./ファイル名
実行結果
Hello, world!
コード解説(Hello, world!)
fn main(){//関数を定義この関数はmainなため引数がなくても実行される
println!("Hello, world!");//これはC言語などの一般言語とほとんど変わらないが!を書き忘れないようにする
}
println!な理由は大きく2つあります。
1つ目:マクロと関数名が一緒になっても競合しないから
2つ目:可変個の引数に対応できる
からだそうです。
あと;をつけることを忘れないでください。rustの場合以下のようなエラーを吐きます。
error: expected `;`, found `println`
--> main.rs:2:30
|
2 | println!("Hello, world!")
| ^ help: add `;` here
3 | println!("Hello, world!")
| ------- unexpected token
error: aborting due to 1 previous error
豆知識
まずrustはタブではなくスペース4回を推奨しています。理由はコードエディタによってタブの見え方が異なるそうだからだそうです。
cargoのプロジェクト
cargo new プロジェクト名
hello_cargoで作成すると以下のような結果になります。
hello_cargo/
└── src/
└── main.rs
├── Cargo.toml //rustのパッケージやバージョン管理用
└── .gitignore //gitに保存したくないファイルを指定するもの
Cargo.toml
//TOML(yamlより人間が理解しやすくなったもの)
[package]
name = "hello_cargo" //名前
version = "0.1.0" //このプロジェクトのバージョン
edition = "2024" //使用するrustのエディション(rustアップデートによるコードの破損をなくすため)
[dependencies]
//プロジェクトの依存関係の確認
cargoのプロジェクトをコンパイル
cargo build
ターミナル
cargo build
Compiling hello_cargo v0.1.0 (file:///projects/hello_cargo)
Finished dev [unoptimized + debuginfo] target(s) in 0.59s
コンパイルされると以下のような構成になります。
hello_cargo/
├── src/
│ └── main.rs
├── Cargo.toml // Rustのパッケージやバージョン管理用
├── Cargo.lock // 依存関係の正確なバージョンを記録(自動生成)
├── .gitignore // gitに保存したくないファイルを指定
└── target/ // コンパイル後の成果物(バイナリなど)が入るディレクトリ
├── debug/ // デフォルトのビルド成果物(cargo build で生成)
└── hello_cargo // コンパイルされた実行ファイル
Rust公式でもっと便利に使いたい場合
実行
./target/debug/hello_cargo
実行結果は変わらずHello, world!です
しかし何度もこの作業をやっていると時間をロストしてしまうためcargo runでコンパイルから実行までしてくれます。そしてcargo checkの機能により必要なファイル以外コンパイルしないため早くコンパイルすることができます。
Cargo.lock
完全に同じ状況下でコンパイルができるようにプロジェクト内の依存関係の正確なバージョンを記録をしてくれます。rustらしいファイルです。
リリース方法
以下のコマンドを実行するとtargetの中にリリース用のコンパイルファイルが入ります。これを実行すると開発用のファイルとできる限り高速化させた公開用のコンパイルを行ってくれます。
cargo build --release
数字当てゲーム
use std::io;//ユーザーの入力を受け付けるライブラリ(標準)
fn main() {
println!("数字を当ててごらん");
println!("ほら、予想を入力してね");
let mut guess = String::new();//変数作成(mutを取り付けないと不変になってしまう)(Stringでも固定)
io::stdin()//ターミナル入力のハンドル
.read_line(&mut guess)//毎回参照されるように設定(&mut)
.expect("行の読み込みに失敗しました");//エラー検知用(確実にいる)
println!("次のように予想しました:{}",guess);
}
変数について
let mut guess = String::new();//変数作成(mutを取り付けないと不変になってしまう)(Stringでも
まず他の言語と違ってrustは指定としないと不変になってしまいます。なので必ずmutをつけましょう。しかも型も固定です。
ターミナル入力
io::stdin()//ターミナル入力のハンドル
.read_line(&mut guess)//毎回参照されるように設定(&mut)
.expect("行の読み込みに失敗しました");//エラー検知用(確実にいる)
これはuse std::io;という標準ライブラリーを使ってやることができるものである。
rand
main.rs
use std::io;//ユーザーの入力を受け付けるライブラリ(標準)
use rand::Rng;
fn main(){
println!("数字を当ててごらん");
let secret_number = rand::thread_rng().gen_range(1..101);
println!("秘密の数字は次の通り: {}",secret_number);
println!("ほら、予想を入力してね");
let mut guess = String::new();//変数作成(mutを取り付けないと不変になってしまう)(Stringでも固定)
io::stdin()//ターミナル入力のハンドル
.read_line(&mut guess)//毎回参照されるように設定(&mut)
.expect("行の読み込みに失敗しました");//エラー検知用(確実にいる)
println!("次のように予想しました:{}",guess);
}
Cargo.toml
[package]
name = "random"
version = "0.1.0"
edition = "2024"
[dependencies]
rand = "0.9.0"
randについて
let secret_number = rand::thread_rng().gen_range(1..101);
1以上101未満の乱数を作るものでありrand::thread_rng()の部分で現在のスレッド専用の乱数生成器を取得しておる。
Cargo.tomlに書いていないと以下のようなエラーが出る
random % cargo run
Compiling random v0.1.0 (/Volumes/random)
error[E0432]: unresolved import `rand`
--> src/main.rs:2:5
|
2 | use rand::Rng;
| ^^^^ use of unresolved module or unlinked crate `rand`
|
= help: if you wanted to use a crate named `rand`, use `cargo add rand` to add it to your `Cargo.toml`
error[E0433]: failed to resolve: use of unresolved module or unlinked crate `rand`
--> src/main.rs:6:25
|
6 | let secret_number = rand::thread_rng().gen_range(1..101);
| ^^^^ use of unresolved module or unlinked crate `rand`
|
= help: if you wanted to use a crate named `rand`, use `cargo add rand` to add it to your `Cargo.toml`
Some errors have detailed explanations: E0432, E0433.
For more information about an error, try `rustc --explain E0432`.
error: could not compile `random` (bin "random") due to 2 previous errors
もちろんバージョンも最新なのを自動的に取得するタイプのものではありません。
比較
use std::io;//ユーザーの入力を受け付けるライブラリ(標準)
use rand::Rng;//乱数ジェネレーターおよびその他のランダム性機能のライブラリー
use std::cmp::Ordering;//比較用標準ライブラリ
fn main(){
println!("数字を当ててごらん");
let secret_number = rand::thread_rng().gen_range(1..101);
println!("秘密の数字は次の通り: {}",secret_number);
loop {
println!("ほら、予想を入力してね");
let mut guess = String::new();//変数作成(mutを取り付けないと不変になってしまう)(Stringでも固定)
io::stdin()//ターミナル入力のハンドル
.read_line(&mut guess)//毎回参照されるように設定(&mut)
.expect("行の読み込みに失敗しました");//エラー検知用(確実にいる)
println!("次のように予想しました:{}",guess);
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
match guess.cmp(&secret_number) {//match文
Ordering::Less => println!("小さすぎ"),
Ordering::Greater => println!("大きすぎ"),
Ordering::Equal => {
println!("やったね");
break;
}
}
}
}
まず比較をするには標準ライブラリーであるuse std::cmp::Ordering;を入れなくてはならない。そしてrustのmatch文には3つほど列挙子(名前付き定数の集まり)がある。プリントされる文章通りでLessが小さい,Greaterは大きい、Equalは==と同じ意味を表しているそうです。
以下のようにすれば何行もコードを実行できるようになります。
Ordering::Equal => {
println!("やったね");
break;
}
u32について
プログラミングの32ビット符号なし整数型であり小さな整数を表すときに適している型です。
let guess: u32 = guess.trim().parse()
ループについて
rustにはfor,whileの他にloopというものが備わっています。コレを使うこととpythonでいうwhile Trueと同じことができます。
Rustの基礎知識編
変数の不変について
まずrustを使って初めて思うことはなぜわざわざ標準で不変にしてまで使わせたいのかと思うと思います。公式によると変数の値を変えれるということはバグにつながってしまうからだそうです。よく考えてみると自分もいろんな変数をいろんなところで流用していたとき思った値が変数に入っていなく苦労した思い出があります。しかも変数の値は変数を変更する可能性のあるコードを全て見ないとわからないこともあるので公式の考えも納得です。
定数と変数の違い
| 内容 | 定数 | 不変 |
|---|---|---|
| 定義 | すべてのスコープ | スコープ制限あり |
| 有効期間 | プログラムが実行されてる間ずっと | 定義された場所による(インスタンスが破棄されるまでなど) |
| 不変の強度 | 常に不変 | 常に不変ではない |
const 変数名:型 = 中身
シャドーイング
不変変数でも前に定義した変数と同じ名前の変数を定義することが可能で前の変数は新しい変数に上書きされる。しかもletがないとコンパイルエラーが出るため間違えてシャドーイングする心配はありません。
データ型
rustは静的型付き言語であり最初から型が固定されるようです。
符号付き
| 大きさ | 符号付き(正負対応) | 符号なし |
|---|---|---|
| 8-bit | i8 | u8 |
| 16-bit | i16 | u16 |
| 32-bit | i32 | u32 |
| 64-bit | i64 | u64 |
| arch(アーキテクチャ依存) | isize | usize |
符号付きの場合主に上位ビットが符号ビット(正 or 負判断するビット)機能する。(0=正,1=負)この方法のことを2の補数表現というらしく元の2進数のビットをすべて反転(1を0に、0を1に)させて、それに1を足すことで求められるらしいです。
公式には以下のように書いてあるがここに関しては理解できませんでした
各符号付きバリアントは、-(2n - 1)以上2n - 1 - 1以下の数値を保持でき、 ここでnはこのバリアントが使用するビット数です。以上から、i8型は-(27)から27 - 1まで、 つまり、-128から127までを保持できます。符号なしバリアントは、0以上2n - 1以下を保持できるので、 u8型は、0から28 - 1までの値、つまり、0から255までを保持できることになります。
整数リテラル
| 数値リテラル | 例 |
|---|---|
| 10進数 | 89_222 |
| 16進数 | 0xff |
| 8進数 | 0o77 |
| 2進数 | 0b1111_0000 |
| バイト | b'A' |
数値リテラルはソースコードに直接その値を書くものであり数値リテラルの違いはそれが何進数かの差だそうです。
ちなみに公式のおすすめはi32だそうです。理由は64ビットのシステムでも爆速だからだそうです。
詳しい演算子について
浮動小数点型
浮動小数点型とは小数点以下の値をたくさん保存するための型でありrustではIEEE-754規格(標準規格)のf32(単精度浮動小数点数)とf64(倍精度浮動小数点数)が使われています。
数値演算子
数学演算子(プログラミングなどで式を書くときに用いられるやり方)がrustには使われております。
| 名前 | 演算子 |
|---|---|
| 足し算 | addition |
| 引き算 | subtraction |
| 掛け算 | multiplication |
| 割り算 | division |
| あまり | remainder |
詳しい演算子について
論理値型
論理値とは主にif文で使われるTrueやFalseで使われるものです。主にいろんな言語ではboolとして指定されます。
文字型
char型はユニコードのスカラー値(文字に割り当てられる番号)を表すものであり様々な言語や絵文字に対応しています。しかし何かと合致しないものもあるそうです。
fn main() {
let c = 'z';
}
複合型
複数の値を1つにまとめるための型でありリストとの違いはリストでは同じ要素の順序付きの物であり、複合型では異なるデータや型をまとめるものだそうです。
タブル型(複合型)
fn main() {
let tup: (i32, f64, u8) = (500, 6.4, 1);
}
アクセスする場合は以下のようにやります
変数名:数字(0~)
配列型
スタックするためのメモリを確保したい場合にこの配列は有効ならしいです。
| 内容 | ベクタ型 | 配列 |
|---|---|---|
| サイズ | 伸縮可能 | 伸縮不可能 |
リストの使い方
fn main() {
let a: [型; 長さ] = [1, 2, 3, 4, 5];
}
要素のアクセス方法は他の言語と変わらず変数[数]で取得することができます
関数
関数名の入力
fn main{
関数名(5);
}
fn 関数名(変数:型){}
関数の引数
rustは前書いたように変数の定義の時に型を指定しないといけません。そして関数の引数の場合変数の再代入が不可能です。
fn 関数名(変数:型){}
戻り値のある関数
fn five() -> i32 {//returnする型
5//returnするもの
}
fn main() {
let x = five();
println!("The value of x is: {}", x);
}
まずreturnをするためには関数のところに-> i32を入れて型を指定する必要があります。そして他のプログラミング言語と違いreturn 値ではなく値で戻り値を指定します。
コメント
javascriptなどと同じ//testでコメントができます。
制御フロー
if式
fn main() {
let number = 3;
if number < 5 {
println!("5より小さい数");
} else if number > 5 {
println!("5より大きい数");
} else{
println!("その他");
}
}
~~ここは他の言語とは変わらない感じです。~~そしてもちろん型が合わなかったらelseに行くのではなくエラーを吐きます。そしてpythonのようにelifではなくelse ifです。
[!CAUTION]
@scivola様によると見た目は一緒でも
- 条件式の () は必須ではない
- bool 型しか真偽値に使えない
- 式であり,値を持つ(なので let の右辺だけでなく,値を必要とするさまざまなところに書ける)→「if 文」でなく「if 式」
- { } は省略できない(よって「ぶら下がり else」問題は起きない)
- この { } はスコープを作る
- else if は JavaScript などのそれ(else 節に if を入れ子にしているだけ)とは違い,この 2 語で if 式の一つの構成部品になっている
error[E0308]: mismatched types
(型が合いません)
--> src/main.rs:4:8
|
4 | if number {
| ^^^^^^ expected `bool`, found integer
| (bool型を予期したのに、整数変数が見つかりました)
For more information about this error, try `rustc --explain E0308`.
error: could not compile `branches` due to previous error
let内でifを実行する
以下のコードのようにlet内でif文を使うことができます。そして安定の型が違うとエラーです。
fn main() {
let cheak = true;
let cheakd = if cheak {"完了しました"} else {"チェックを行ってください"};
println!("{}", cheakd);
}
ループ
fn main() {
loop {
println!("loop内")
}
}
continue,breakについて
rustのbreakなどはループ内のループにある場合内側のループ(ループ内のループの方)に適応されます。しかし以下のようにすることで一番外側のループから止めることも可能です。それにはラベルが必要でありラベル付きのループは以下のように作成できます。
'outside: loop {}//ラベル付きループ
break 'outside//outsideという名前に適応されるbreak
continue 'outside//outsideという名前に適応されるcontinue
whileで条件付きループ
rustの条件付きwhileは以下のようで特に他の言語と変わったところはないと思います。
while 条件 {}//条件付きループ
所有権
主な言語のプログラムを実行する時に使われるメモリは2つほどの管理方法があります。
- 定期的に使用していないメモリを検索するガベージコレクション
- プログラマが意図的に管理(確保、解放)する方法
しかしrustは第3の方法を使います。 - メモリはコンパイラがコンパイル時にチェックする方法
そしてその規則とともに所有権とも関わってきます。
スタックとヒープ
スタックはpushとpopで追加したり取り除いたりします。そのおかげでスタックは高速で処理できます。理由はいちいちメモリの中を捜索しなくても一番上を取り除けばいいからです。しかもスタックは固定サイズではないといけないのでそれも高速になり要因の一つです。
しかし毎回サイズがわからなかったりサイズが改変されるデータの場合ヒープが利用されます。OSsがヒープに十分な大きさを渡しポインタを渡します。このことをallocating on the heapといい実のデータが欲しい場合はポインタから追いかける必要性があるため多少スタックより時間がかかってしまいます。
| 内容 | push | pop |
|---|---|---|
| 速度 | 爆速 | 低速 |
| 使用メモリ量 | 少ない | 多い |
| 制限 | 強い(push,pop) | 弱い |
プッシュはmacでいう確保されているメモリに該当するのではないでしょうか

所有権の規則
- rustの各値は、所有者と呼ばれる変数と対応している
- いかなる時も所有者は1つである
- 所有者がスコープから外れたら、値は破棄される
変数スコープ
スコープとは要素が有効になる範囲を指すもので例えば以下のコードで説明するとfn testが終了するとsという変数は無効化される
fn main(){
test();
}
fn test(){//ここの処理が終わるとスコープが消えsはなくなる
let s = 0;
}
String型
以下のコードは文字列リテラルからString型を生成するコードです。
let s = String::from("Hello");
::はString型直下のfrom関数を特定する働きをする演算子だそうです。メソッド定義とモジュール定義で詳しくやるそうです。
String型は文字列リテラルと違い可視化することが可能です。理由はメモリの吸い出し方にあるみたいです。そして文字列リテラルの場合コンパイル時に中身が判明しているため高速で効率的に処理できるそうです。
メモリと確保
Stringでは中身が判明していないためコンパイル時には不明な量のメモリをヒープを確保する必要性があります。メモリは実行時にOSに要求され使用し終わったら返却する方法が必要です。その機能を補ってくれるのがString:from関数です。このコードが必要なメモリを要求してくれます。しかしRustはガベージコレクタ付き言語ではないため自動的に返却は行われません。なので意図的に返却を行う必要性があります。なので早すぎず遅すぎないメモリの管理をしなくてはならないのです。しかしそこもRustがメモリをOSに返却する自然な地点(スコープが抜ける時)があります。Rustは閉じ波括弧で自動的にdrop関数を呼び出します。