https://doc.rust-lang.org/book/
感想
- 3-2 まで
- shadowing どういうときに便利なのかわかっていない
- 普通に宣言すればいいのでは?
- release オプションつけてるときに overflow を wrapping される件、デフォルトが panic で困るときに wrap method つけるほうがいいと思うんだけどシステムプログラマ的にはこっちが便利?
- overflow の wrapping で実際とは異なるカウントアップになった場合に業務?的に困る場合もある気がするが、どうなんだろう
- shadowing どういうときに便利なのかわかっていない
- 3-5 まで
- expressions は値を返す, statement は値を返さない
return
break
continue
は値を返す statement って…コト!?
- expressions は値を返す, statement は値を返さない
- 4-1 まで
- return しなかったらスコープから外れて値が使えなくなる、まあ参照渡しじゃないから、、、みたいなことを考えたらそうだねという気もする
- move の挙動は忘れたら、え?てなりそうだけど、再代入って普通しないから気になくてよさそう、、、
- 4-2 まで
- 感想とくにないかも、、
- しいていうなら
&foo
mut &foo
の違いか。let mut foo
であれば&foo // immutable reference
か&mut foo // mutable reference
の違いがあるが、当然let foo
は&foo
しかできず、&mut foo
のパターンはありえない、まあそれはそうって感じ
- 4-3 まで
- Slice というか mutable な値の borrowing を使って、immutable reference として返すやり方は安全でよいなと思った
- 5-1 まで
- やっぱり Rust の ownership 難しい、という印象
- とはいえ、GC無しで安全に使えて… ということを考えるとこういうもんなのかな?GCのある言語ばかり使ってきた身としては難しい…
- immutable borrowing + lifetime のところを読んでみないとまだなんとも言えないか…
1 章
rustfmt について
rustfmt というフォーマット用のツールがある。
rustup component add rustfmt
で導入できるとのこと。
実行すると、
info: component ‘rustfmt’ for target ‘x86_64-apple-darwin’ is up to date
と言われるので最初から入ってるぽかった…。
これは Cargo プロジェクトのフォーマットをするので、Cargo の設定ファイルが必要。
cargo
https://doc.rust-lang.org/cargo/
cargo new hello_cargo --bin
cargo new
で新しい Cargo プロジェクトを作成するcargo build
でビルド、main を実行するだけならcargo run
、コンパイルできるか検証するにはcargo check
(build よりも速い)cargo build --release
で実行ファイルの最適化が効くが、コンパイルが遅い。ベンチマークを取りたいときは –release オプションをつけなさいとのこと
2 章
型
prelude に載っているのが use
しなくても使える型。
変数宣言
let
で変数宣言する。
Rust では let
で宣言する変数は不変なので、 mutable な変数は mut
をつける。
Associated Function
type に実装されている function のことを Associated Function と呼ぶ。
String::new();
みたいなやつ。
参照
&
Enum
Result
は Enum の一種
crate
git clone で ssh と認証を使いたい場合は CLI を使って依存関係解決することもできる。
https://doc.rust-lang.org/cargo/reference/config.html#netgit-fetch-with-cli
[net]
git-fetch-with-cli = true
cargo update
すると依存関係も最新化される。
arms
match
で始まってpattern
にマッチするか
let guess: u32 = match guess.trim().parse() {
Ok(num) => num,
Err(_) => continue,
};
3 章
-
immutable にすると不変なのでわかりやすいけど、でかい structures を扱うときに mutable にするとパフォーマンスが良いということはある
-
const は immutable だが、 計算後の値ではなく runtime の起動時にセットされるようなもの。Rust はコンパイル時に const を計算する
- const の評価については Constant Evaluation - The Rust Reference
-
shadowing は自分的にはしんどい、普通に
spaces_str
で認知負荷的にも困らない -
scalar と compound 型がある
- scalar はひとつの値を意味する: integers, floating-point numbers, Booleans, and characters
- compound は複数の値を持つもの
-
Rust は勝手に型推論するが、複数の型が帰るケースでは書き手が annotation をつけて決定する
let guess: u32 = "42".parse().expect("Not a number!");
-
isize
usize
に関してはアーキテクチャにより 32 か 64 か変わる -
number literals がある。 e.g.
57u8
,98_222
-
scalar 型の overflow について、release タグをつけた場合には panic しない。release の場合は、overflow したら max 値まで wrap して処理を継続する
saturating_*
,overflowing_*
,checked_*
,wrapping_*
とか制御するメソッドがあるので、 handle したい場合はこれらを使う
-
float はデフォルトで
f64
-
char は single quotes で表現、string は double quotes で表現
- char は unicode value である。
U+0000
からU+D7FF
と、U+E000
からU+10FFFF
- char は unicode value である。
-
tuple は fixed length でサイズを変えられない型
-
pattern matching で tuple から値を取り出すこともできる
- destructing
-
あるいは
.{num}
でアクセスすることができる
fn main() {
let tup = (500, 6.4, 1);
let (x, y, z) = tup;
println!("The value of y is: {}", y);
}
-
値がなにもない tuples は
()
で表現され、unit type とも言う(値は unit value)。expression を使ったときに、対象に何もない場合は暗黙的に unit type が返る -
array は tuples と違って単一の型のみ保持するが、tuples と同じところは fixed size であるところ
- vector type というサイズ可変のコレクション型もある
- データを heap ではなく、stackに置きたいときに便利
let a: [i32; 5] = [1, 2, 3, 4, 5];
let a = [3; 5]; // let a = [3, 3, 3, 3, 3];
- 不正な index range アクセスは panic で終了
- array は value の更新不可
fn main() {
let list: [u64; 5] = [1; 5];
println!("list: {}", list[1]);
list[1] = 100; // error
}
- 細かい話だが function に与える concrete value を arguments と言い、function definition の方は parameters というらしい。。。
- コード中でどの型を意図するか指定する必要をなくすため、function の parameters は type 必須にしているらしい
- Rust は expression based language
- statement は値を返さない
let
は statement なので、let x = (let y = 6);
はできない- C とか Ruby だと assignment は値を返すので
x = y = 6
みたいな書き方もできる
- expressions は値を返すために evaluate する
- statement の中に expressions を含めることはできる
- macro を call するのは expressions
{}
block は expression なので以下のようなコードは OK
- statement は値を返さない
fn main() {
let y = {
let x = 3;
x + 1
};
println!("The value of y is: {}", y);
}
- expressions の場合はセミコロンで終わらせない。セミコロンで終わらせるのは statement になる
- function の最後の expressions の戻り値は return value となる / Rust では return は省略するケースが多いっぽい?
fn main() {
let x = plus_one(5);
println!("The value of x is: {}", x);
}
fn plus_one(x: i32) -> i32 {
x + 1;
// Followings work:
// return x + 1;
// x + 1
}
-
if expressions は arms とも呼ばれる(前やったパターンマッチで使ったやつ)
- というか if は expressions なので値の代入に使える
- if condition は bool でなければいけない
- operator
!=
とかを使ってください
- operator
-
branches が複数あるときは match を使う
-
if expressions で let statement で返すときは type が決まらないので error になる場合がある。branch で返す値の型は同一でないとコンパイルできない
-
繰り返し処理は
loop
while
for
-
そして
loop
は expression -
loop
は明示的に終了することを記述しなければずっと繰り返すbreak
で終了break
continue
は statement
continue
で繰り返し処理の残りの部分を無視して最初から
-
繰り返し処理は label 指定でどのループの処理に対する
break
continue
か指示することができる- 通常は inner loop に対する指定となる
fn main() {
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);
}
loop
expression 内で値を返すために;
つけて statement として終了する感じwhile
は condition つけるfor element in list { // ...
fn main() {
// (1..4) generates a Range standard type.
for number in (1..4).rev() {
println!("{}!", number);
}
println!("LIFTOFF!!!");
}
4 章
-
memory の管理方法
- garbage collection
- 自分で allocate and free
- Rust はいくつかのルールセットをもとにオーナーシップで管理される。オーナーシップの機能でプログラム実行時に遅くなることはない(多分 garbage collection との対比)
-
システムプログラミングにおける stack and heap
- stack は size が決められている
- heap は size が可変。heap を使うときは一定の size メモリを確保することになる。memory allocator が一定の size の space を確保して pointer として返す(allocating)。
- stack は heap より速い
- heap space が大きいと allocation はコストが高くなる
- function は stack に入れられる(パラメータとしての pointer は heap かもしれない)
- 何が heap に入れられ、何が stack に入れられるか、 heap の重複データを避けて使っていないデータを削除することができれば space を使い切ることはない。 ownership がそれについて言及する
-
Rust のすべての値は
owner
をもっている -
ある場面では owner は単一である
-
owner がスコープ外になると値は捨てられる
-
通常 data type は size を持っており、stack から取り出されて独立したコピーとして使われる。別のスコープのコードで必要になったら再利用される
let s = String::from("hello");
string
literals は不変String
は size が不定で heap を使う- literals はコンパイル時に埋め込まれるので速い
String
の場合は runtime 時にメモリをリクエストしているString
の利用を完了したときに allocator にメモリを返してもらう必要がある- GC がある場合は GC が使わなくなったメモリを開放する
- Rust の場合は out of scope になったら free とする
{}
curly brackets の終了でdrop
(メモリ開放)する
{
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
以下の場合は 5 が stack に入れられる:
let x = 5;
let y = x;
String
の場合は s1 の "hello"
に対する pointer を s2 が持つことになる:
let s1 = String::from("hello");
let s2 = s1;
この場合、 s1 と s2 を drop しようとするとエラーになってしまう。これを double free error (2回メモリをfreeしようとしてエラー)という。 なので、 Rust の場合は上記のように pointer を代入するときは drop が発生する。以下のコードはコンパイルできない:
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
- shallow copy
- capacity, length などはコピーするが値自体はコピーしない
- Rust は shallow copy ではなく move をする / 最初の参照を無効にして、後の参照のみ有効にする
- Rust は deeply copy しない。そのため runtime performance を損なわない
- deeply copy したい場合は
.clone()
method を使う
以下の場合は heap のデータがコピーされている:
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
以下の場合はコンパイルでき、動作するが、先程の例の move の挙動とは異なっている:
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
- integers などの stack に保持される値はそのままにされる(compile 時にサイズ計算され、stack にに保持されるので、shallow とか deep とか気にする必要がない)
Copy
Drop
trait- integer のような stack に保持する値については
Copy
trait を使うことができる Copy
trait を実装している場合は古い値も参照できるDrop
trait を実装していた場合は、Copy
trait を実装できない。詳細は Derivable Traits参照
- integer のような stack に保持する値については
Here are some of the types that implement 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.
以下の場合、xはスコープに戻ってきてないので drop となる:
fn main() {
let s = String::from("hello"); // s comes into scope
takes_ownership(s); // s's value moves into the function...
// ... and so is no longer valid here
let x = 5; // x comes into scope
makes_copy(x); // x would move into the function,
// but i32 is Copy, so it's okay to still
// use x afterward
} // Here, x goes out of scope, then s. But because s's value was moved, nothing
// special happens.
fn takes_ownership(some_string: String) { // some_string comes into scope
println!("{}", some_string);
} // Here, some_string goes out of scope and `drop` is called. The backing
// memory is freed.
fn makes_copy(some_integer: i32) { // some_integer comes into scope
println!("{}", some_integer);
} // Here, some_integer goes out of scope. Nothing special happens.
以下のように return されてスコープに戻ってきている場合は move, したのちに drop となる:
fn main() {
let s1 = gives_ownership(); // gives_ownership moves its return
// value into s1
let s2 = String::from("hello"); // s2 comes into scope
let s3 = takes_and_gives_back(s2); // s2 is moved into
// takes_and_gives_back, which also
// moves its return value into s3
} // Here, s3 goes out of scope and is dropped. s2 was moved, so nothing
// happens. s1 goes out of scope and is dropped.
fn gives_ownership() -> String { // gives_ownership will move its
// return value into the function
// that calls it
let some_string = String::from("yours"); // some_string comes into scope
some_string // some_string is returned and
// moves out to the calling
// function
}
// This function takes a String and returns one
fn takes_and_gives_back(a_string: String) -> String { // a_string comes into
// scope
a_string // a_string is returned and moves out to the calling function
}
次、一応 return 使わないで ownership 管理する reference の紹介らしい。。。
References and borrowings
- pointer と reference の違いは、必ず valid な値であるということ
- dereference をするには
*
を使う - reference の場合には ownership が function に移らないので、block の終わりに ownership が無効になるということがない
- reference をつくることを borrowing という
- reference の場合も、immutable な値に対する mutate 操作はできない
- ある mutable な値を複数回 borrowing することはできない
- immutable は 複数回 borrowing できる
- これはコンパイル時に data racing を検知できるようにするため。以下のような場合が考えられる:
- 2つ以上の pointer が同じデータに同時にアクセスしている
- 少なくとも1つの pointer がデータに書き込みをしようとしている
- データへのアクセスを同期にする仕組みがない
- immutable な borrowing は複数回できるが、mutable borrowing は immutable 含めて複数回できない
- immutable な borrowing は read だけなので他の操作に影響しないから、という理由とのこと
fn main() {
let mut s = String::from("hello");
let r1 = &s; // no problem as immutable borrowing
let r2 = &s; // no problem as immutable borrowing
let r3 = &mut s; // BIG PROBLEM as mutable borrowing
println!("{}, {}, and {}", r1, r2, r3);
}
let mut foo
は let したブロックで mutable なだけであって、bar(&foo)
というのは、 bar に reference を渡すだけなので、 bar のブロックの中では mutable というわけではない。bar(&mut foo)
という使い方をすると mutable ということになる
以下のコードは r1 と r2 が使われないのでいいらしい。そうなの、、、
fn main() {
let mut s = String::from("hello");
let r1 = &s; // no problem
let r2 = &s; // no problem
println!("{} and {}", r1, r2);
// variables r1 and r2 will not be used after this point
let r3 = &mut s; // no problem
println!("{}", r3);
}
- ある pointer が保存されているメモリが開放されることによって、他者がと参照先がかち合ってしまうことを dangling pointer と呼ぶ
- dangling pointer の例は以下。Rust だと immutable な
s は
dangle()` のブロック外で dropped されてしまうので、 dangleing pointer となってしまう。なのでコンパイラエラーとなる
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String { // dangle returns a reference to a String
let s = String::from("hello"); // s is a new String
&s // we return a reference to the String, s
} // Here, s goes out of scope, and is dropped. Its memory goes away.
// Danger!
- 以下の例だと return された
s
の ownership がstring
に移るので、値は drop されない。そのため dangling pointer には当たらず OK
fn main() {
let string = no_dangle();
}
fn no_dangle() -> String {
let s = String::from("hello");
s
}
Slices
..
range index- String Slices は有効なUTF-8文字の境界で使わなければエラーとなる。UTF-8文字のハンドリングについては Chapter8 で取り扱う
#![allow(unused)]
fn main() {
let s = String::from("hello");
let slice = &s[0..2];
let slice = &s[..2];
}
#![allow(unused)]
fn main() {
let s = String::from("hello");
let len = s.len();
let slice = &s[3..len];
let slice = &s[3..];
}
#![allow(unused)]
fn main() {
let s = String::from("hello");
let len = s.len();
let slice = &s[0..len];
let slice = &s[..];
}
mutable
な値を borrowing してimmutable
として代入した後にmutable
な値を操作しようとするとコンパイルエラーとなる
fn first_word(s: &String) -> &str { // &str is immutable reference
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
fn main() {
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // error!
println!("the first word is: {}", word);
}
deref coercion
; dereference の強制&str
は Slice でもある
5章
Struct の定義と instance 化
#[derive(Debug)]
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
let user2 = User {
email: String::from("another@example.com"),
..user1
};
println!("user1: {:?}", user1);
println!("user2: {:?}", user2);
}
これの結果は以下になる。 user1 の String field の owner が user2 に移っているので user1 の String field が参照できない。 難しすぎるやろw
error[E0382]: borrow of partially moved value: `user1`
--> src/main.rs:22:29
|
17 | let user2 = User {
| _________________-
18 | | email: String::from("another@example.com"),
19 | | ..user1
20 | | };
| |_____- value partially moved here
21 |
22 | println!("user1: {:?}", user1);
| ^^^^^ value borrowed here after partial move
|
= note: partial move occurs because `user1.username` has type `String`, which does not implement the `Copy` trait
For more information about this error, try `rustc --explain E0382`.
error: could not compile `structs` due to previous error
ちなみに以下のように active の場合はアクセスできる。それは、copy
trait を実装しているからとのこと。
println!("user1: {:?}", user1.active);
user1: true
user2: User { active: true, username: "someusername123", email: "another@example.com", sign_in_count: 1 }
上記の例では String
を使っており、 copy
trait がないため ownership が移っており使えないよ、という例示をしているが、じゃあ reference を使って immutable borrowing をしたらどうか、という場合は lifetime
を指定していないよというエラーになる。
lifetime
については10章で説明されるらしい。
struct User {
username: &str, // Missing lifetime specifier
email: &str, // Missing lifetime specifier
sign_in_count: u64,
active: bool,
}
fn main() {
let user1 = User {
email: "someone@example.com",
username: "someusername123",
active: true,
sign_in_count: 1,
};
}