Rustに入門してみる

インストール
以下のコマンドでRustのインストールを行う
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
ツールチェーンの管理
rustupと言うツールで管理される。
rustup update
を実行することでアップデートできる。
PATH環境変数を設定する
~/.cargo/bin
ディレクトリに全てのツールがインストールされ、rustc
, cargo
, rustup
を含むツールチェーンが置かれる。
そのため、~/.cargo/bin
をPATH環境変数に含めるのが通例。
インストール後は以下の手順で環境変数の設定を確認できる。
% source "$HOME/.cargo/env"
% rustc --version
rustc 1.73.0 (cc66ad468 2023-10-03)

上記で基本を学ぶ

1. 事始め
Hello World
main.rs
と言うファイルを作成する。
Rustではファイルは常に.rs
と言う拡張子でおわる。
2単語以上を使うならアンダースコアで区切る(hello_world.rs
)
fn main() {
// 世界よ、こんにちは
println!("Hello, world!")
}
rustcコマンドでコンパイルしてから実行する。
% rustc main.rs
% ./main
Hello, world!
プログラムの解剖
fn main() {
}
fn
がRustにおける関数定義。
関数名でmain
は特別で、全ての実行可能なRustプログラムで最初に走るコードになる。
スタイルについてはrustfmt
がフォーマッターとしてあり、Rustの配布に含まれている。
println!("Hello, world!");
println!
はRustのマクロを呼び出している。
もし関数を読んでいたらprintln
になっている(!がつかない)
今の時点では!がついていればマクロを読んでいると言うことだけ理解しておけばOK
% tree
.
├── main
└── main.rs
rustcでコンパイルすると実行可能ファイルが生成される。
RustはAOTコンパイル(ahead-of-time; 訳注: 予め)言語です。つまり、プログラムをコンパイルし、 実行可能ファイルを誰かにあげ、あげた人がRustをインストールしていなくても実行できる
Hello, Cargo
CargoはRustのビルドシステム兼パッケージマネージャ。
コードのビルド、コードが依存するライブラリのダウンロード、それらのライブラリのビルドなどを扱ってくれる。
CargoはRustの配布に含まれており、以下のコマンドでインストールを確認できる。
% cargo --version
cargo 1.73.0 (9c4383fb5 2023-08-26)
Cargoでプロジェクトを作成する
以下のコマンドでcargoでプロジェクトが作成できる。
% cargo new hello_cargo
Created binary (application) `hello_cargo` package
Cargo.toml
とsrcディレクトリにmain.rs
ファイル(と.gitignore)を自動で生成してくれている。
% cd hello_cargo
% tree
.
├── Cargo.toml
├── .gitignore
└── src
└── main.rs
1 directory, 2 files
Cargo.tomlの中身は以下のようになっている。
[package]
name = "hello_cargo"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
Cargoはソースファイルがsrcディレクトリにあることを期待し、ルートディレクトリにはREADMEなどのコードに関連しないものだけを置く。
Cargoプロジェクトのビルドと実行
cargo build
でプロジェクトをビルドできる。
% cargo build
\ Compiling hello_cargo v0.1.0 (/Users/panyoriokome/workspaces/rust_exer/hello_cargo)
Finished dev [unoptimized + debuginfo] target(s) in 0.63s
% tree
.
├── Cargo.lock
├── Cargo.toml
├── src
│ └── main.rs
└── target
├── CACHEDIR.TAG
└── debug
ビルドすると実行ファイルがカレントディレクトリではなくtarget/
に作成され、以下のコマンドで実行ファイルを実行できる。
% ./target/debug/hello_cargo
Hello, world!
また、cargo run
を使うと、コードのコンパイルから、できた実行ファイルの実行までの全体を一つのコマンドで行える。
% cargo run
Finished dev [unoptimized + debuginfo] target(s) in 0.02s
Running `target/debug/hello_cargo`
Hello, world!
cargoはcargo check
と言うコマンドも提供しており、コンパイル可能かチェックをしてくれる。
% cargo check
Checking hello_cargo v0.1.0 (/Users/panyoriokome/workspaces/rust_exer/hello_cargo)
Finished dev [unoptimized + debuginfo] target(s) in 0.29s
リリースに向けたビルド
リリースに向けたビルドを生成するならcargo build --release
コマンドを使う。
このコマンドは実行ファイルをtarget/debug
ではなく、target/release
に作成する。
% cargo build --release
Compiling hello_cargo v0.1.0 (/Users/panyoriokome/workspaces/rust_exer/hello_cargo)
Finished release [optimized] target(s) in 0.40s
releaseビルドでは最適化によってコードの実行速度が上がるが、その分コンパイルにかかる時間が長くなる。
そのために用途に応じてビルドの仕方がdebug用(開発用)とリリース用の2つがある。
既存リポジトリでの作業時
gitでクローン後にcargo buildを使ってインストールができる。
$ git clone example.org/someproject
$ cd someproject
$ cargo build

2. 数当てゲームのプログラミング
use
use std::io;
ユーザ入力を受け付けるためにio
ライブラリが必要になる。
io
ライブラリはstd
と呼ばれる標準ライブラリに入っている。
Rustはデフォルトで標準ライブラリのアイテムの一部を全てのプログラムのスコープに取り込む。
これはpreludeと呼ばれ、標準ライブラリのドキュメントの中でその一覧を確認できる。
使いたい型がpreludeにない場合にはuse
文で明示的にスコープに入れる必要がある。
変数
let mut guess = String::new();
let
を使って変数を定義。
Rustでは変数はデフォルトでimmutableになる。(詳細は第3章の「変数と可変性」の節参照)
変数をmutableにするには変数名の前にmut
をつける。
String::new()
関数はString
型の新しいインスタンスを返す。
String
は標準ライブラリによって提供される文字列型。
::
構文はnewがString型の関連変数であることを示している。
関連変数とはある型に対して実装される関数のこと。
ユーザの入力を受け取る
io::stdin()
.read_line(&mut guess)
&
は変数が参照であることを示し、これによりコードの複数の部分が同じデータにアクセスしても、そのデータをメモリに何度もコピーしなくて済む。
ここで覚えておくべきは変数のように参照もデフォルトで不変であること。
だから&guess
ではなく&mut guess
と書いて可変にする必要がある。
Result型で失敗の可能性を扱う
read_lineメソッドは渡された変数にユーザの入力内容を設定するが、それと同時に値(io::Result)も返す。
Rustの標準ライブラリにはResult
と言う名前の型がいくつかあり、汎用のResultとio::Resultなどのサブモジュール用の特殊な型に分かれる。
Result型はEnum
ランダムな値の生成
標準ライブラリに乱数を生成する機能はないがクレートという形で提供されている。(rand
クレート)
クレートは他の言語でいうライブラリのようなものっぽい。
まず、Cargo.tomlにインストールしたいライブラリとそのバージョンを以下のように追記する。
[dependencies]
rand = "0.8.3"
その上でcargo buildを実行すると↑で追加したクレートのインストールが行われる。
% cargo build
Updating crates.io index
Downloaded getrandom v0.2.10
Downloaded rand_core v0.6.4
Downloaded cfg-if v1.0.0
Downloaded rand_chacha v0.3.1
Downloaded ppv-lite86 v0.2.17
Downloaded rand v0.8.5
Downloaded libc v0.2.149
Downloaded 7 crates (905.8 KB) in 7.61s
Rustは依存関係をcrates.ioを使って解決しようとする。
他の言語と同じように依存関係のバージョニングを保つ方法としてはlockファイルの仕組みを使っており、Cargo.lockに全ての依存関係のバージョンが保存されており、明確に更新しない限り常に同じバージョンが使われる。
Cargo.lockを更新してみる
cargo updateで更新できるっぽい?
乱数を生成してみる
use std::io;
use rand::Rng; // 追加でimport
fn main() {
println!("Guess the number!");
let secret_number = rand::thread_rng().gen_range(1..101); // 乱数の生成
println!("The secret number is: {}", secret_number);
便利な機能
cargo doc --open
コマンドを使うと依存ライブラリのdocumentをローカルでビルドしてブラウザで確認可能にしてくれる。
実際にビルドしてみるとrandを追加した状態なので以下の画像のようにrandの説明をローカルでブラウザから確認できる。
match
println!("You guessed: {}", guess);
match guess.cmp(&secret_number) {
Ordering::Less => println!("Too small!"),
Ordering::Greater => println!("Too big!"),
Ordering::Less => println!("You win!"),
}
error[E0308]: mismatched types
--> src/main.rs:22:21
|
22 | match guess.cmp(&secret_number) {
| --- ^^^^^^^^^^^^^^ expected `&String`, found `&{integer}`
| |
| arguments to this method are incorrect
|
= note: expected reference `&String`
found reference `&{integer}`
note: method defined here
--> /rustc/cc66ad468955717ab92600c770da8c1601a4ff33/library/core/src/cmp.rs:775:8
guessはStringでsecret_numberがi32なので型が不一致でコンパイルエラーになる。
let guess: u32 = guess.trim().parse() // シャドーイングで同じ変数名
.expect("Please type a number!")
Stringインスタンスのtrimメソッドは文字列の先頭と末尾の空白や\nや\r\nをすべて削除する。
parseメソッドは文字列をパース(解析)して何らかの数値に変換する。
さまざまな数値型へとパースできるので、let guess: u32としてRustに正確な数値型を伝える必要がある。
parseメソッドは論理的に数値に変換できる文字にしか使えないので、よくエラーになる。
read_lineと同じくResult型を返すのでErrの際にはexpectでクラッシュさせる

3. 一般的なプログラミングの概念
変数とか可変性
- 変数は標準で不変だが、可変にする選択肢も残されている。(
mut
) - ただし、
mut
は値は可変だがデータ型を変えることは許容されていない
let x = 5;
x = 6;
上記のように一度immutableで定義した変数を再代入すると以下のようなエラーが出る。
error[E0384]: cannot assign twice to immutable variable `x`
--> src/main.rs:4:5
|
2 | let x = 5;
| -
| |
| first assignment to `x`
| help: consider making this binding mutable: `mut x`
3 | println!("The value of x is: {}", x);
4 | x = 6;
| ^^^^^ cannot assign twice to immutable variable
Rustでは、値が不変であると宣言したら、本当に変わらないことをコンパイラが担保してくれます。 つまり、コードを読み書きする際に、どこでどうやって値が変化しているかを追いかける必要がなくなります。 故にコードを通して正しいことを確認するのが簡単になるのです。
逆にmutableな変数として定義したときには、その変数の値が変わることを明示的に伝えることができる。
考えるべきトレードオフはバグの予防以外にも、いくつかあります。例えば、大きなデータ構造を使う場合などです。 インスタンスを可変にして変更できるようにする方が、いちいちインスタンスをコピーして新しくメモリ割り当てされたインスタンスを返すよりも速くなります。
変数と定数の違い
- Rustにも定数の概念がある
- 定数は
const
で定義し、定義時に値の型を指定しないといけない(注釈というらしい) - 定数は常に不変なので
mut
を使えない - Rustの定数の命名規則は
MAX_POINTS
のように全て大文字でアンダースコア区切り
const MAX_POINTS: u32 = 100_000;
// constatnts
const THREE_HOURS_IN_SECONDS: u32 = 60 * 60 * 3;
println!("The value of THREE_HOURS_IN_SECONDS is: {}", THREE_HOURS_IN_SECONDS);
シャドーイング
- letを使って再定義することができる(シャドーイング)
- letを使うことでシャドーイングの時はその変数に加工ができるが、加工が終わったら変数は不変に戻る
- その点で変数を
mut
で定義するのとは違う
fn main() {
let x = 5;
let x = x + 1;
{
let x = x * 2;
println!("The value of x in the inner scope is: {}", x);
}
println!("The value of x is: {}", x);
}
The value of x in the inner scope is: 12
The value of x is: 6
- letを使って明示的に再定義(シャドーイング)することでコンパイルエラーにならず、同じ名前を再利用することができる
// シャドーイング
let spaces = " "; // string type
let spaces = spaces.len(); // number type
// これはエラー
let mut spaces = " ";
spaces = spaces.len();

3. 一般的なプログラミングの概念
データ型
- Rustは静的型付き言語でありコンパイル時に全ての変数の型が判明している必要がある
- 複数の型が推論される可能性がある場合にはtype annotatioが必須。以下の例のように
let guess = "42".parse().expect("Not a number!"); // 数字ではありません!
$ cargo build
Compiling no_type_annotations v0.1.0 (file:///projects/no_type_annotations)
error[E0282]: type annotations needed
(型注釈が必要です)
// 以下のようにtype annotationをつける必要がある
let guess: u32 = "42".parse().expect("Not a number!"); // 数字ではありません!
スカラー型(Scalar Types)
single valueを表し、Rustでは以下の4つの型がある。
- 整数型(Integer)
- 浮動小数点型(Floating Point)
- 論理型(Boolean)
- 文字型(String)
Integer
整数を表す。
bit数と符号の有無(Signed/Unsigned)に応じて以下の型がある。
Length | Signed | Unsigned |
---|---|---|
8-bit | i8 | u8 |
16-bit | i16 | u16 |
32-bit | i32 | u32 |
64-bit | i64 | u64 |
128-bit | i128 | u128 |
arch | isize | usize |
Signedの各variantが保有できる幅は
例えばi8
は
Unsignedはu8
だと
isize
とuseize
はプログラムが実行される環境のアーキテクチャに依存する()
Literalの書き方にはいくつかのフォームがある。
具体的にどのように使い分けるかでいうと、Rustのintegerのデフォルト型はi32
なのでここを参考にすると良さそう。isize
とusize
のメインの使い所はcollection要素のindexに使われる。
Floating-Point Types
f32
とf64
の2つがあり、それぞれ32bitsと64bitsになっている。
デフォルトはf64
。どちらもSigned
Boolean
bool
型で表され、true or falseの値を取る
Character Type
char
で表される。
Stringがダブルクォートで表されるのに対して、charはシングルクォートで表される。
Rust’s char type is four bytes in size and represents a Unicode Scalar Value, which means it can represent a lot more than just ASCII
複合型
- タプル型
- 配列型
The Tuple Type
tupleの長さは決まっていて、定義した後で変えられない。要素ごとに異なる型でOK。
カンマ区切りで定義して、アクセスするにはdestructureかindexの2つの方法がある
let tup : (i32, f64, u8) = (500, 6.4, 1);
// destructureでアクセス
let (_x, y, _z) = tup;
println!("The value of y is: {y}")
// indexでアクセス
let five_hundred = tup.0;
let six_point_four = tup.1;
let one: u8 = tup.2;
The Array Type
全ての要素が同じ型じゃないとダメ。
他の言語とは異なり、RustのArrayは長さが固定。
Arrays are useful when you want your data allocated on the stack rather than the heap (we will discuss the stack and the heap more in Chapter 4) or when you want to ensure you always have a fixed number of elements.
スタックではなくヒープにデータを配置したい場合に便利。
standard libraryで提供されているvector
は長さを後から自由に変えられるので、vector
と比べると使い所が少ないかも?
Arrayは要素数が変わらないのが確定してる場合にも便利(月の一覧を持つとか)
// array
let a = [1, 2, 3, 4, 5];
// let a: [i32; 5] = [1, 2, 3, 4, 5];
let five_three_array = [3; 5]; // [3, 3, 3, 3, 3]
// indexでアクセス
let first = a[0];
let second = a[1];
Invalid Access
要素数を超えたインデックスを指定すると以下のようなエラーが出る。
thread 'main' panicked at 'index out of bounds: the len is 5 but the index is 10', src/main.rs:19:19
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
This is an example of Rust’s memory safety principles in action. In many low-level languages, this kind of check is not done, and when you provide an incorrect index, invalid memory can be accessed. Rust protects you against this kind of error by immediately exiting instead of allowing the memory access and continuing. Chapter 9 discusses more of Rust’s error handling and how you can write readable, safe code that neither panics nor allows invalid memory access.

3. 一般的なプログラミングの概念
Functions
fn
で定義する。
Rustの関数はsnake caseで書く
fn main() {
another_function();
}
fn another_function() {
}
Parameters
他の言語と特に違う点はない
fn print_labeled_measurement(value: i32, unit_label: char) {
...
}
Statements and Expressions
Rustはexpression-based language。expressionとstatementの違いを知るのが大事
- Statements
- 値を返さない。変数の定義や関数自体とか
- Expressions
- それ以外のほとんど
let y = 6; // statement
let x = (let y = 6); // returns error because statement doesnt return value
Functions with Return Values
return valueに;をつけるとstatement扱いになってエラーになる
fn five() -> i32 {
5
}
Control Flow
if
基本的な使い方は他の言語と同じで、変数定義の中でifが使えるところの記法が違うぐらい。
let number = 3;
if number < 5 {
println!("true");
} else {
println!("false");
}
// boolしか使えないのでエラーになる
// if number {
// println!("")
// }
// ifはexpressionなのでletの中で使える
let condition = true;
let number = if condition { 5 } else { 6 };
println!("the value of number is: {number}")
// 型が一致しないとエラーになる
// let number = if condition { 5 } else { "six "};
Loop
-
break
- ループから抜ける
-
continue
- 次のイテレーションに進む
返り血を受け取りたい時
loop
の前で変数定義することでloopで返した値を受け取れる
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2
}
};
println!("The result is {result}");
loopから途中で抜ける
多重ループをした時、continueやbreakは現在のループを抜ける。
Rustではループにラベル付けをして、continueやbreakでどのループから抜けるかを指定することができる。
let mut count = 0;
'counting_up: loop {
println!("count = {count}");
let mut remaining = 10;
loop {
println!("remaining = {remaining}");
if remaining == 9 {
break;
}
if count == 2 {
break 'counting_up;
}
remaining -= 1;
}
count += 1;
}
println!("End count = {count}");
while loop
conditionがtrueの間ループしたいならwhileが使える。
これをloop(とif, else. break)を使って実現するよりもずっとシンプルにかける。
let mut number2 = 3;
while number2 != 0 {
println!("{number2}!");
number2 -= 1;
}
println!("LIFTOFF!!!")
for
Collectionをループするならforが便利。
便利にかけるだけでなく、whileの時にコレクション数とindexの条件が合わなければpanicが起きてしまうが、そうした心配もなくなりよりsafeになったと言える。こうした理由からRustではforが一番よく使われる。(while loopの例もforループにRange
を使って書く人がほとんど)
// for
let c = [10, 20, 30, 40, 50];
let mut index = 0;
while index < 5 {
println!("the value is: {}", c[index]);
index += 1;
}
for element in c {
println!("The value is: {element}");
}

4. Ownership
Ownership is Rust’s most unique feature and has deep implications for the rest of the language. It enables Rust to make memory safety guarantees without needing a garbage collector
What is Ownership
- いくつかの言語ではガーベージコレクションがあってプログラムの実行時にもう使われていないメモリーを探して掃除する
- その他の言語ではプログラマが明示的にメモリのallocateと解放を行う必要があった
- Rustは上の2つとは別に3つ目のアプローチを取ってる
- RustではOwnershipとコンパイル時にチェックされるいくつかのルールをもとにメモリを管理する
- ルール違反をしてたらコンパイルが通らない
- なので、Ownershipの概念はプログラムの実行時間に影響を及ぼさない
Stack and Heap
多くの言語ではstackとheapについて考える必要がないケースが多いが、Rustのようなシステムプログラミング言語では値がstackにあるのかheapにあるのかによって振る舞いが変わるケースがあり、Owenershipの概念もstackとheapに関連するのでまずこれらの概念について理解する必要がある。
-
どちらもコードの実行時に使えるメモリの領域
-
データの貯め方が違う
-
stack
- LIFO(last in, first out)
- stackに置かれるデータは全てfixed sizeじゃないといけない
- TupleとArrayはstackに置かれる?
-
heap
- heapのメモリ確保のやり方はもっとアバウトで、memory allocatorがheapの中で十分な領域がある場所を探して割り当て、pointerを返す(このプロセスをallocation on the heap, 単純にallocationとも言う)
- heapへのpointer自体はfixed sizeなのでstackに格納することができる
-
stackへのpushは新しいデータを格納する場所を探す必要がないのでallocatingより早い(常にstackのtopにpushされる)
-
heapへのデータアクセスは同様の理由でstackへのデータアクセスより遅い
-
関数を読んだ際には値が関数に渡され、関数のlocal variablesとしてstackにpushされ、関数の処理が終わった際にstackからpopされる
-
Rustの世界ではどのコードでheapのデータを使っているかを考えることが大事
Ownership Rules
- Each value in Rust has an owner.
- There can only be one owner at a time.
- When the owner goes out of scope, the value will be dropped.
Variable Scope
variableはscopeが終わった時点で無効になる
{ // s is not valid here, it’s not yet declared
let s = "hello"; // s is valid from this point forward
// do stuff with s
} // this scope is now over, and s is no longer valid
The String Type
これまで学んできたデータ型は全て事前にサイズがわかっていて、stackに格納され、scopeが終わった時に消されるもの。
String Valueを使うものとしてstring literalを学んできたが、以下の理由から全ての状況で相応しいものではない。
- immutable
- コードを書く時点でvalueがわからないものもある
こういうケースに対応するためにString
という型がRustにはある。
String
はheapにメモリをallocateされ、コンパイル時にわからない値も格納できる。
// String::fromで定義する
let s = String::from("hello");
// mutateできる
let mut s = String::from("hello");
s.push_str(", world!"); // push_strで指定したstring literalをStringに追加する
println!("{}", s)
string literalはimmutableだけどString typeはmutable。
これはこの2つの型がメモリをどう扱うかの違いにある
Memory and Allocation
事前に値が決まってるstring literalではテキストの内容がfinal executableにハードコードされている。だから、string literalは早くて効率的。テキストの内容が決まってないStringではこの恩恵には与れない。
Stringでmutableを実現するためには以下の動きが行われている。
- The memory must be requested from the memory allocator at runtime.
- We need a way of returning this memory to the allocator when we’re done with our String.
String::from
を呼んだ時に必要なメモリのリクエストが行われている(これはどの言語でも同じ)。
2点目が言語によって違う。
- GCがある言語では使用されてないメモリをGCが探して消す
- GCがない言語ではメモリを解放とそのタイミングは明示的に指定する
- Rustでは別のアプローチを取っている
{
let s = String::from("hello"); // s is valid from this point forward
// do stuff with s
} // this scope is now over, and s is no
// longer valid
variableがスコープから外れた時に、Rustは特別な関数を自動で呼んでくれる。
この関数はdropと呼ばれ、上記の例では{}の最後の}が終わったタイミングで呼ばれる。
Variables and Data Interacting with Move
integerは値自体をコピーして新たなメモリを確保する。
Stringの場合はpointer(とlenとcapacity)をコピーする。
後者で問題になるのがdrop
functionの実行で
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world", s1);
上記のコードを実行すると以下のエラーが出る。
error[E0382]: borrow of moved value: `s1`
--> src/main.rs:10:27
|
7 | let s1 = String::from("hello");
| -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
8 | let s2 = s1;
| -- value moved here
9 |
10 | println!("{}, world", s1);
| ^^ value borrowed here after move
|
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
|
8 | let s2 = s1.clone();
| ++++++++
Rustの上記の挙動はshallow copyのように聞こえるが、Rustでは最初に定義された変数が無効化される。
これはRustではmove
と呼ばれる。
最初に定義したs1は無効化され、s1だけがメモリから解放される。
In addition, there’s a design choice that’s implied by this: Rust will never automatically create “deep” copies of your data. Therefore, any automatic copying can be assumed to be inexpensive in terms of runtime performance.
Rustでは設計上の選択としてdeep copyを行わないようになっている。
→ integerの挙動はdeep copyにあたらないのかな...?
shallow copy, deep copy についてまとめてみる
Variables and Data Interaction with Clone
heapに格納されているStringのデータをdeep copyするにはclone
メソッドを使う。
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
stack-only data: copy
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
上記のコードはエラーになってなかった。
その理由はintegerのようにコンパイル時にサイズがわかってる値はheapではなくstackに値が格納されており、copyするコストが低くて早い。
だからyを作った後にyを無効にする理由がなく、Rustの言語仕様として禁止されていない。
- stackに格納されるデータ型
- shallow copyとdeep copyの差がなく、moveが起こらない(明示的にcloneしなくても良い)
- heapに格納されるデータ型
- shallow copyされるが、moveは禁止されていてエラーになる(明示的にcloneする必要がある)
- deep copyはコストが高いから禁止されており、moveは安全性のために禁止されている?
Rustの言語仕様としてstackに格納されるデータ型にはcopy
traitが実装されており、copy
traitが実装されているデータ型ではmoveが発生せず、他の変数に代入された後も有効なままになる。
逆にDrop
traitが実装されているデータ型にはcopyの実装ができないようになっている。(Stringも多分そう)
公式ドキュメントにも記載がある。
You cannot implement both Copy and Drop on the same type. Types that are Copy get implicitly duplicated by the compiler, making it very hard to predict when, and how often destructors will be executed. As such, these types cannot have destructors.
Copy and Drop are exclusive
これは関数がスコープ外になったら自動でdropが呼ばれるというのと矛盾しない?Integerでdrop traitが実装されてないなら、integerのvariableがスコープ外になったらどういう動きになるのか...?
一般的にScalar Valueをとるデータ型はCopyを実装できる。
- All the integer types, such as u32.
- The Boolean type, bool, with values true and false.
- All the floating-point types, such as f64.
- The character type, char.
- Tuples, if they only contain types that also implement Copy. For example, (i32, i32) implements Copy, but (i32, String) does not.
Ownership and Functions
関数に渡した時も新しい変数に代入した時と同じような挙動になる。
以下の例だとheapに格納されるStringは関数に渡した時点で無効になり、
stackに格納されるintegerは関数に渡した後も有効な状態になっている。
let s = String::from("hello");
takes_ownership(s); // sの値がfuncionにmoveする
// ここでもうsの値が無効になる
// println!("{}", s); エラーになる
let x = 5;
makes_copy(x); // xがfunctionにmoveする?
// しかしi32は
println!("{}", x); // エラーにならない
Return Values and Scope
関数の返り値についても同じで、以下の例でs2は引数として渡された後、返り値がs3に代入されたタイミングで無効になる。
let s1 = gives_ownership();
let s2 = String::from("hello");
let s3 = takes_and_gives_back(s2);
println!("s1 = {}", s1);
// println!("s2 = {}", s2) // s3にmoveしてるのでエラーになる
println!("s3 = {}", s3);
毎回owenershiptが移譲されてしまうのは面倒。
ownershipを委譲せずに値を渡す方法としてRustではreferences
というものがある。
References and Borrowing
referenceはpointerのようなものでデータの領域を指し示すアドレス。
違うのは特定の型を持った有効な値であることが保証されている。
Referenceは&
をつけることで定義できる。(Referenceの反対派dereferenceで*
で定義できる)
let s1 = String::from("hello");
let len = calculate_length(&s1);
println!("The length of '{}' is {}.", s1, len)
}
fn calculate_length(s: &String) -> usize {
s.len()
}
&をつけてreferenceとして指定しないとvalue borrowed here after move
のエラーが出る。