Rustまとめ-その弐-(随時更新)
これまでのまとめリンク
所有権の基本的規則
- Rustの各値は、所有者と呼ばれる変数と対応している。
- いかなる時も所有者は一つである。
- 所有者がスコープから外れたら、値は破棄される。
変数のスコープとは
スコープとは要素が有効になるプログラムの範囲のことです。
//スコープとは{}で囲まれた間の事
{ //sはまだ宣言されてない
let s = "hellow";//sが有効になる
//sを利用し作業することができる
} //このスコープの終わり、sは有効でなくなる
スタックに保持さえれた変数のみコピー(代入)を行うことができる
//不変変数のs1に10を代入
let s1 = 10;
//s1をコピー
let s2 = s1;
整数は既に固定サイズが分かっているので単純な値となりスタックに積まれるのでコピーすることができます。
//String型の文字列を代入
let s1 = String::from("Hey");
//s1をコピー
let s2 = s1;
s1がスタックで保持する情報は
ptr(ヒープ上にあるデータの宛先)
len(文字列の長さ)
-
capacity(文字の数)
ptr
というヒープな情報にアクセスする必要があるため上記のコードは有効ではありません。
所有権の移動
let s1 = String::from("Hey");
let s2 = s1;
//s1を表示させたい
println!("{},you!",s1);
上記のコードだとエラーが起きます。
二つの変数があり両方とも同じデータポインタがさしている場所を開放すると二重開放エラー
が起きてしまう。(Rustはできるだけ同じもは使いまわせるようにするためコピーしても中身は全く動かないので同じデータをアクセスしていることになっている)
よって、Rustは変数をコピーした場合、先に宣言したものは解放されて無効になります。
所有権の移動が起こらないコピーのやり方
let s1 = String::from("Hey");
//cloneというメソッドを発動してコピー
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
Rustは基本的にヒープ上にあるデータはコピーすることができないのでclone()
というメソッドを使いヒープ上のデータごとコピーすることができます。よって、先に宣言した変数とコピー先の変数は完全な別物となり両方とも有効なものになる。
##スタック上のみにあるデータのコピーによる所有権の移動はおこらない
//不変変数のs1に10を代入
let s1 = 10;
//s1をコピー
let s2 = s1;
println!("s1={},s2={}",s1,s2);
整数などの型はスタック上に保持され簡単にアクセスできるのでコピーするときに制約がない。
関数に変数を渡したときの所有権の移動について
fn main() {
let s = String::from("Hey");
takes_ownership(s); //sの所有権はtakes_ownershipのスコープ内に移る
//これ以降にこののスコープ内でsは呼び出すことはできない
let x = 5;
makes_copy(x);//xは整数なので所有権に縛られない
}
fn takes_ownership(some_string: String) {//sが有効なスコープ
println!("{}", some_string);
}
fn makes_copy(some_integer: i32) {
println!("{}", some_integer);
}
スライス型とは
文字列などの配列からある位置からの一連の要素を参照することができます。
文字列スライスとは
let s1 = String::from("hello world");
let hello = &s[0..5];
let world = &s[6..11];
このようにString
型変数の一部の要素だけ参照することができるようになります。
[0..5]
は文字列の0~5番目までの要素を参照するという意味です。
文字列スライスの省略
let s = String::from("Hellow");
let slice = &s[0..3];
let slice = $s[..3];
上記のslice
は両方とも同じ値を持つことになります。
最初の番号から参照したければ0は省略して書いても良い。
let s = String::from("Hellow");
//変数sの長さを保持
let len = s.len();
//0から最後の文字まで参照
let slice = &s[0..len];
let slice = $s[..];
上記のslice
も両方とも同じ値を持つことになります。
最後の番号を参照したければ省略しても良い。
また、上記のコードのように..
だけにすると最初から最後まで選択することができます。
配列のスライス
//配列を宣言
let s = [1, 2, 3, 4, 5];
//最初(0)から三番目までの値を参照
let slice = &s[..3];
構造体の定義し、インスタンス化する方法
市の天気に関する情報をまとめた構造体を定義
struct SampleArea{
City: String,//市
Direction: String,//方角
InArea: bool,//現在そのエリアにいるのか
sign_in_count: u64//何回そのエリアの天気を見たのか
}
インスタンス化する
let Area1 = SampleArea {
City: String::from("Osaka"),//市
Direction: String::from("East"),//方角
InArea: true,//現在そのエリアにいるのか
sign_in_count: 1//何回そのエリアの天気を見たのか
};
この構造体からどの市かという情報が欲しければ
Area1.City
と書く。
市の情報のみ変更したければ
``Area1.City = String::from("Sapporo");
構造体の一部を取り、そのインスタンスを返す関数を作成
上記の構造体から市と方角の値をとりSampeArea
に返す関数
fn build_area(City: String, Direction: String)-> SampleArea {
SampleArea {
City: City,
Direction: Direction,
InArea: true,
sign_in_count: 1
}
}
フィールドと変数名が同盟の時にフィールド初期化省略記法を利用
fn build_area(City: String, Direction: String)-> SampleArea {
SampleArea {
City,
Direction,
InArea: true,
sign_in_count: 1
}
}
これら二つの関数は同じ行動をします。
構造体更新記法で他のインスタンスからインスタンスを生成する
let Area2 = SampleArea {
City: String::from("Osaka"),//市
Direction: String::from("East"),//方角
InArea: Area1.InArea,//現在そのエリアにいるのか
sign_in_count: Area1.sign_in_count,//何回そのエリアの天気を見たのか
};
このようにインスタンス名.インスタンス化した構造体のフィールド名
を記すことで省略して書くことができます。さらに…
let Area2 = SampleArea {
City: String::from("Osaka"),//市
Direction: String::from("East"),//方角
..Area1
};
..インスタンス名
でそれ以下のフィールドの値をそのインスタンスの値に書き換えることができます。
タプルを使った関数
//通常
fn main() {
let width1 = 40;
let height1 = 50;
println!(
// 長方形の面積は、{}平方ピクセルです
"この長方形の広さは{}ピクセルです。",
area(width1, height1)
);
}
fn area(width: u32, height: u32) -> u32 {
width * height
}
//タプルを使った方法
fn main() {
let react1 = (40,50);
println!(
"この長方形の広さは{}ピクセルです。",
area(rect1)
);
}
fn area(dimensions: (u32,u32)) -> u32 {
dimensions.0 * dimensions.1
}
//構造体を使った方法
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!(
"この長方形の広さは{}ピクセルです。",
area(&rect1)
);
}
//引数に構造体自身を入れそれぞれの要素を入れてもらう
fn area(rectangle: &Rectangle) -> u32 {
rectangle.width * rectangle.height
}
トレイトの導出を使いデバッグ機能を呼び出す
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!("rect1 is {}", rect1);
}
このように書いたとしてもRustの構造体を{}でprintln
出力されることはありません。それは構造体にDisplay
という機能が実装されていないから出力されないのです。
なのでこのようにDisplay
機能を実装します。
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!("rect1 is {:?}", rect1);
}
構造体の上に#[derive(Debug)]
という注釈をつけ、Debugトレイトの機能をつけることで出力されるようにします。
そして{}
内に:?
と付け加えることでDebugトレイトの構造体を出力させる機能を指定し完璧に出力することができるようになります。