今日の勉強会ではスタックとヒープの理解が必要です。
プログラムの変数や関数のあ使い方についての話ですね。
https://www.ipa.go.jp/security/awareness/vendor/programmingv2/contents/c005.html
https://www.ipa.go.jp/security/awareness/vendor/programmingv2/contents/c006.html
https://keens.github.io/blog/2017/04/30/memoritosutakkutohi_puto/
メモリは配列
2^64 byteの配列のうち使いたい文だけ占有します。
メモリの中
* text領域: プログラムを置く
* data領域: 初期化されたグローバル変数を置く
* bss領域: 初期化されていない(データ領域だけ確保された)グローバル変数を置く
* stack領域: 関数の引数やローカル変数を置く
* heap領域: プログラムのデータを置く
所有権のルール:
スコープとは、 要素が有効になるプログラム内の範囲のことです。
fn main() { // sは、ここでは有効ではない。まだ宣言されていない
{
let s = "hello";// sは、ここから有効になる
// sで作業をする
}
} // このスコープは終わり。もうsは有効ではない
言い換えると、ここまでに重要な点は二つあります:
ヒープに確保されるデータ型
この型はヒープにメモリを確保するので、 コンパイル時にはサイズが不明なテキストも保持することができるのです。
文字列リテラルからString型を生成
let s = String::from("hello");
可変化することができます
let mut s = String::from("hello");
s.push_str(", world!"); // push_str()関数は、リテラルをStringに付け加える
println!("{}", s); // これは`hello, world!`と出力する
文字列リテラルはコンパイル時に判明しているので、テキストは最終的なバイナリファイルに直接ハードコードされます。
String型では、可変かつ伸長可能なテキスト破片をサポートするために、コンパイル時には不明な量のメモリを ヒープに確保して内容を保持します。
Rustは、異なる道を歩んでいます: メモリを所有している変数がスコープを抜けたら、 メモリは自動的に返還されます。:
{
let s = String::from("hello"); // sはここから有効になる
// sで作業をする
} // このスコープはここでおしまい。sは
// もう有効ではない
「値5をxに束縛する; それからxの値をコピーしてyに束縛する。」
let x = 5;
let y = x;
両方、値は5
整数は既知の固定サイズの単純な値で、これら二つの5という値は、スタックに積まれる
エラーから見てみる
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
https://doc.rust-jp.rs/book/second-edition/ch04-01-what-is-ownership.html#a変数とデータの相互作用法-ムーブ
"shallow copy"と"deep copy"聞いたことある?
スタック上のデータだけでなく、本当にString型のヒープデータのdeep copyが必要ならば、 cloneと呼ばれるよくあるメソッドを使うことができます。
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
shallow copyになります
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
関数に値を渡すことと、値を変数に代入することは似ています。
関数に変数を渡すと、 代入のようにムーブやコピーされます。
fn main() {
let s = String::from("hello"); // sがスコープに入る
takes_ownership(s); // sの値が関数にムーブされ...
// ... ここではもう有効ではない
let x = 5; // xがスコープに入る
makes_copy(x); // xも関数にムーブされるが、
// i32はCopyなので、この後にxを使っても
// 大丈夫
} // ここでxがスコープを抜け、sもスコープを抜ける。ただし、sの値はムーブされているので、何も特別なことは起こらない。
//
fn takes_ownership(some_string: String) { // some_stringがスコープに入る。
println!("{}", some_string);
} // ここでsome_stringがスコープを抜け、`drop`が呼ばれる。後ろ盾してたメモリが解放される。
//
fn makes_copy(some_integer: i32) { // some_integerがスコープに入る
println!("{}", some_integer);
} // ここでsome_integerがスコープを抜ける。何も特別なことはない。
値を返すことでも、所有権は移動します。
fn main() {
let s1 = gives_ownership(); // gives_ownershipは、戻り値をs1に
// ムーブする
let s2 = String::from("hello"); // s2がスコープに入る
let s3 = takes_and_gives_back(s2); // s2はtakes_and_gives_backにムーブされ
// 戻り値もs3にムーブされる
} // ここで、s3はスコープを抜け、ドロップされる。s2もスコープを抜けるが、ムーブされているので、
// 何も起きない。s1もスコープを抜け、ドロップされる。
fn gives_ownership() -> String { // gives_ownershipは、戻り値を
// 呼び出した関数にムーブする
let some_string = String::from("hello"); // some_stringがスコープに入る
some_string // some_stringが返され、呼び出し元関数に
// ムーブされる
}
// takes_and_gives_backは、Stringを一つ受け取り、返す。
fn takes_and_gives_back(a_string: String) -> String { // a_stringがスコープに入る。
a_string // a_stringが返され、呼び出し元関数にムーブされる
}
変数の所有権は、毎回同じパターンを辿っています: 別の変数に値を代入すると、ムーブされます。 ヒープにデータを含む変数がスコープを抜けると、データが別の変数に所有されるようムーブされていない限り、 dropにより片付けられるでしょう。
タプル
fn main() {
let s1 = String::from("hello");
let (s2, len) = calculate_length(s1);
//'{}'の長さは、{}です
println!("The length of '{}' is {}.", s2, len);
}
fn calculate_length(s: String) -> (String, usize) {
let length = s.len(); // len()メソッドは、Stringの長さを返します
(s, length)
}
所有権を取り、またその所有権を戻す、ということを全ての関数でしていたら、ちょっとめんどくさいですね。
値の所有権をもらう代わりに引数としてオブジェクトへの参照を取るcalculate_length関数
fn main() {
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()
}
エラーになります
fn main() {
let s = String::from("hello");
change(&s);
}
fn change(some_string: &String) {
some_string.push_str(", world");
}
正しいやり方
fn main() {
let mut s = String::from("hello");
change(&mut s);
}
fn change(some_string: &mut String) {
some_string.push_str(", world");
}
可変な参照には大きな制約が一つあります: 特定のスコープで、ある特定のデータに対しては、 一つしか可変な参照を持てないことです。
エラーになります
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
データ競合が起こるコードをコンパイルさえしない
複数の可変な参照
let mut s = String::from("hello");
{
let r1 = &mut s;
} // r1はここでスコープを抜けるので、問題なく新しい参照を作ることができる
let r2 = &mut s;
可変と不変な参照を組み合わせることに関しても、似たような規則が存在しています。
let mut s = String::from("hello");
let r1 = &s; // 問題なし
let r2 = &s; // 問題なし
let r3 = &mut s; // 大問題!
ポインタのある言語では、誤ってダングリングポインタを生成してしまいやすいです。ダングリングポインタとは、 他人に渡されてしまった可能性のあるメモリを指すポインタのこと
ダングリングポインタ:その箇所へのポインタを保持している間に、 メモリを解放してしまうこと
fn main() {
let reference_to_nothing = dangle();
}
fn dangle() -> &String {
let s = String::from("hello");
&s
}
所有権のない別のデータ型は、スライスです。
文字列を受け取って、その文字列中の最初の単語を返す関数を書いてください。
fn first_word(s: &String) -> usize {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return i;
}
}
s.len()
}
文字列スライスとは、Stringの一部への参照
let s = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
問題を修正すると
fn first_word(s: &String) -> &str {
let bytes = s.as_bytes();
for (i, &item) in bytes.iter().enumerate() {
if item == b' ' {
return &s[0..i];
}
}
&s[..]
}
let a = [1, 2, 3, 4, 5];
let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];