Open48
【メモ】The Rust Programming Language
不変がデフォルトで、可変にしたければ mut
つける。
let apples = 5; // immutable
let mut bananas = 5; // muttable
-
read_line
ユーザが標準入力に入力したものを文字列に追加する- 上書きではないので可変
-
&
参照であることを伝えてる- 受け渡し先が参照であることを伝えたい
std::io::stdin().read_line(&mut guess)
TODO:
- 引数で型も渡すのなぜ
型推論
- rustが型推論できるから指定不要
let mut guess = String::new();
シャドーイング
// guessはString型
let mut guess = String::new();
// 前の値を覆い隠す
// 型変換のために変数をダブらせないため
let guess: u32 = guess.trim().parse().expect("Please type a number!");
TODO:
- 型注釈でparseに伝えられるのなぜ?
変数let
- デフォルト不変
- グローバルスコープで定義できない
-
mut
で可変にできる
定数const
- 必ず型注釈いる
- グローバルスコープで定義できる
const MAX_POINTS: u32 = 100_000;
上書きではなく、元の値が覆い隠される
let x = 5;
let x = x + 1;
// こっちはだめ
let x = 5;
x = x + 1;
可変のやつに違う型入れるのもだめ
let mut spaces = " ";
spaces = spaces.len();
複数の型が想定される場合は必ず型注釈がいる。
let guess: u32 = "42".parse().expect("Not a number!");
タプル
let x: (i32, f64, u8) = (500, 6.4, 1);
let five_hundred = x.0;
配列
let a = [1, 2, 3, 4, 5];
let b = [3; 5]; // 3を5要素
let first = a[0];
- 式:なんらかの結果の値によって評価される。文の一部。
-
5 + 6
:値11
に評価される式 -
{}
ブロック - 関数呼び出し
-
- 文:なんらかの動作をして値を返さない命令
let y = 6;
- 関数自体
let x = [式が来ることを期待]
// `let y = 6`は文であり値を返さないので、`x`に束縛するものがない
// → エラー
let x = (let y = 6);
- 式は終端に
;
を含まない。 -
;
が付くと式になる。
// 値4に評価されるブロック(式)
{
let x = 3;
x + 1
}
関数
- 戻り値の型を指定
fn plus_one(x: i32) -> i32 {
x + 1
}
制御
if [bool型を想定した条件式] {}
else {}
-
if [条件式]
も式なので、let
文の左辺に持って来れる -
number
は、if
式の結果に基づいた値に評価される
let number = if condition { 5 } else { 6 };
- はじめに出てくる値で型を推測する
| let number = if condition { 5 } else { "six" };
| - ^^^^^ expected integer, found `&str`
| |
| expected because of this
所有権
- Rustの各値は、所有者と呼ばれる変数と対応している。
- いかなる時も所有者は一つである。
- 所有者がスコープから外れたら、値は破棄される。
メモリ領域
- スタック:サイズ固定。中身がコンパイル時に確定している。
- 文字列リテラルなど
- ヒープ:可変。コンパイル時にサイズが不明。
-
String
など - 関数を呼んだ時点で、OSにメモリを要求。
- 終了時に返還する。
allocate
とfree
を良いタイミングで1対1対応させないとバグる。 - Rustではスコープを抜けたタイミングで返還される。
- 閉じ括弧のタイミングで、自動で
drop
を呼んでくれる。
- 閉じ括弧のタイミングで、自動で
-
- 単純なコピー
- 整数は固定サイズが既知
- 5という値がそのままスタックに積まれる
let x = 5;
let y = x;
- 「ヒープ領域へのポインタ・長さ・キャパ」の値がスタックに積まれる
let s1 = String::from("hello");
let s2 = s1;
二重解放を防ぐ
-
s1
がs2
にコピーされた時点で、コピー元のs1
は無効化されて使えなくなる。 - スコープ抜ける時は
s2
だけ解放すれば良い。 - deep copyは不採用。
-
clone
使うとできる
-
- 関数に渡したら無効化される
- → 関数を抜けるとき、返り値として戻すことで呼び出し元にmoveできる
- 毎回戻す作業だるい
- → 参照を使うとgood
参照
-
&
で渡す - 所有権を渡さずに参照先を取得できる
- 借用:関数の引数に参照を取ること
let s1 = String::from("hello");
let len = calculate_length(&s1);
- 借用した値は変更不可
let s = String::from("hello");
change(&s);
fn change(some_string: &String) {
some_string.push_str(", world");
^^^^^^^^^^^ `some_string` is a `&` reference, so the data it refers to cannot be borrowed as mutable
}
- 変えたいなら
mut
で可変にする
let mut s = String::from("hello");
change(&mut s);
同じスコープで可変値は一つしか持てない
- 可変は認めるが制約をかける
let mut s = String::from("hello");
let r1 = &mut s;
let r2 = &mut s;
データ競合が起こる状況
- 2つ以上のポインタが同じデータに同時にアクセスする。
- 少なくとも一つのポインタがデータに書き込みを行っている。
- データへのアクセスを同期する機構が使用されていない。
可変値を一つしか許容しないことで、データの競合を防ぐ。
- 不変値に可変値の借用を渡せない
- 複数の不変参照はOK
- ダングリングポインタ
- 無効なメモリ領域を指すポインタ
- move済みのやつとか
- Rustにはダングリングを防ぐ仕組みがある
- ポインタが有効な間に解放しない
- データへの参照が生きてる間は、データのメモリを解放しない
スライス
[starting_index..ending_index]
- スライスの内部構造
- 開始位置へのポインタ:
starting_index
- 長さ:
ending_index
-starting_index
- 開始位置へのポインタ:
let s = "hello world";
let hello = &s[0..5];
let world = &s[6..11];
- 他の言語は配列は実質参照渡しだった気がした → Rustでは明示的にやってるということ?
- 不変として
first_word
に渡してるので、返り値は不変参照となるため変更できない
let mut s = String::from("hello world");
let word = first_word(&s);
s.clear(); // error!
fn first_word(s: &String) -> &str { }
- 文字列リテラルの型は
&str
-
&str
は不変参照なので文字列は不変 - 文字列リテラルは「スライス」
-
let s = "Hello, world!";
// String型
let my_string = String::from("hello world");
// &str型
let my_string_literal = "hello world";
-
String
str
スライス 関係性が微妙にわかってない - リテラル文字列指定 ==
&str
構造体
- 名前指定なので柔軟性が高い
- ↔︎タプルは番号指定
struct User {
username: String,
email: String,
sign_in_count: u64,
active: bool,
}
// 明示的に与えられてないフィールドは同じ値が入る
let user2 = User {
email: String::from("another@example.com"),
username: String::from("anotherusername567"),
..user1
};
タプル構造体
-
Color
とPoint
は違う型 -
struct
は型を作っている
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
// 異なるタプル構造体からインスタンスを生成
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
ユニット様構造体
- フィールドのない構造体
- Cに無名構造体みたいなのあった気がする
- 構造体が有効な期間は全フィールドが有効であって欲しい
- 参照型のフィールドを持ちたいときはライフタイム機能を使う必要がある
- 下記はコンパイルエラー
struct User {
username: &str,
email: &str,
sign_in_count: u64,
active: bool,
}
fn main() {
let user1 = User {
email: "someone@example.com",
username: "someusername123",
active: true,
sign_in_count: 1,
};
}
- 変数間の関係性にも意味づけすべき、みたいな倫理観がある
- Rustがそうなのか、実は他のプログラミング言語にも共通した価値観?
メソッド記法
- methodsとfunctionsがある
- 書き方は一緒だがメソッドは構造体の中で呼ばれる
- 最初の引数は
self
で構造体自身を指す。- 所有権を奪わずに参照だけ欲しい
- 書き込みしたいなら所有権を奪う形で
&mut self
になる。
-
impl
でとる
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle { width: 30, height: 50 };
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}
- 関連関数:
impl
ブロック内にあるが&self
をとらない- 構造体に関連づいている
- 関数でありメソッドではない
- メソッドであるもの:対象となる構造体のインスタンスが存在するかどうか
- コンストラクタによく使われる
- メソッドとの違い
- methodはクラスメソッド?
- functionがインスタンスメソッド?
列挙型
- 構造体とどちらを採用すべきか
- どれか一つの状態をとるものは列挙
- 構造体同様、メソッド定義できる
-
::
名前空間- 列挙型は識別子のもとに名前空間わけされている
- 列挙子は、[識別子]型
enum IpAddrKind {
V4,
V6,
}
fn route(ip_type: IpAddrKind) { }
route(IpAddrKind::V4);
route(IpAddrKind::V6);
State: 内的。自身の性質を表現?自然?
Status: 外的。自身が周囲に与える価値を表現?人工的?
いかに表現方法を定義するか
// 素朴な例
enum IpAddrKind {
V4,
V6,
}
struct IpAddr {
kind: IpAddrKind,
address: String,
}
let home = IpAddr {
kind: IpAddrKind::V4,
address: String::from("127.0.0.1"),
};
// 列挙子に紐付ける方法
enum IpAddr {
V4(String),
V6(String),
}
let home = IpAddr::V4(String::from("127.0.0.1"));
nullがない
- 型を確実に特定できる
- 値不在の概念
- 既に組み込まれてるので、
Some
None
が使える
- 既に組み込まれてるので、
- ジェネリック型引数<T>: あらゆる型のデータを1つ持てることを意味する
enum Option<T> {
Some(T),
None,
}
let x: i8 = 5;
let y: Option<i8> = Some(5);
// 実際に使うにはOption<T>をTに変換する必要がある
// let y = 5;
let sum = x + y;
6 | let sum = x + y;
| ^ no implementation for `i8 + Option<i8>`
|
= help: the trait `Add<Option<i8>>` is not implemented for `i8`
match
- 型はなんでもよい。↔︎ if は bool のみ
- アームに紐づくのは式
fn value_in_cents(coin: Coin) -> u32 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
},
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
- クレートルート
- Rustコンパイラの起点。クレートのルートモジュールを作るsrcファイル
-
Cargo.toml
- クレートのビルド方法を定義
- Cargoのデフォルト認識
-
src/main.rs
- パッケージと同じ名前を持つ Binary crate root
-
src/bin
- library crate root。他のクレートからimportされる
-
参考
公開設定
- 構造体
- 構造体の可視性に関わらず要素はデフォルト全pri
- pubにしたい要素だけ指定
- enum
- enumの可視性が全フィールドに適用
コレクション
- ベクタ型:可変長の値を並べて保持
- 文字列:String とか
- ハッシュマップ:key: value
ベクタ型
- 複数の値をメモリ上に隣り合わせで保持
- 同時に代入しない時は、コンパイラに値の型を知らせるためジェネリクス指定が必要
-
push
,get
let v: Vec<i32> = Vec::new();
// 与えられた型をベクタに変換
let v = vec![1, 2, 3];
要素サイズ知らなくていいのか?
-
push
したらメモリ足りなくなったときは、新しい場所にコピーして元のメモリ領域は解放される -
push
以降は参照が無効になる(コンパイルエラー)
let mut v = vec![1, 2, 3, 4, 5];
let first = &v[0];
v.push(6);
値変更できる
- 可変値を参照として渡す
- 変更するには参照外し演算子*を使う
let mut v = vec![100, 32, 57];
for i in &mut v { *i += 50; }
違う型を保持したいときはEnumを使う
- なんでも入る状態だとコンパイルがエラーを検知できない
enum SpreadsheetCell {
Int(i32),
Float(f64),
Text(String),
}
let row = vec![
SpreadsheetCell::Int(3),
SpreadsheetCell::Text(String::from("blue")),
SpreadsheetCell::Float(10.12),
];
ジェネリック型
-
T
なら任意の型を取れる -
impl
のメソッド定義、T
でも特定の型に限るのもどっちもいける
struct Point<T> {
x: T,
y: T,
}
// impl 直後の<T>の必要性
// Point<ここが具体的な型でないことを伝えたい>
impl<T> Point<T> {
fn x(&self) -> &T {
&self.x
}
}
// こっちはPoint<具体的な型>なので上記対応は不要
impl Point<f32> {
fn distance_from_origin(&self) -> f32 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
fn main() {
let p = Point { x: 5, y: 10 };
// let q = Point { x: 5.0, y: 10.0 };
println!("p.x = {}", p.x());
// pはf32じゃないのでエラー
// qならいける
println!("p.x = {}", p.distance_from_origin());
}
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
// other(渡される側)は違う型の可能性あるから、違うやつ指定しないとだめ
// 型不明な分だけ渡す
// other: Point<V, &str>なら, mixup<V>でOK
fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y,
}
}
}
fn main() {
let p1 = Point { x: 5, y: 10.4 };
let p2 = Point { x: "Hello", y: 'c'};
let p3 = p1.mixup(p2);
println!("p3.x = {}, p3.y = {}", p3.x, p3.y);
}
Trait
- モジュール的なやつ?
- 継承をなくした?
- デザインパターンであった気がする:継承よりもComposition
各型に実装要求
// trait
impl Summary for NewsArticle { }
// method
impl NewsArticle { }
デフォルト実装
pub trait Summary {
fn summarize(&self) -> String {
}
}
トレイト境界構文
- 特定のトレイトを実装する型を許容
pub fn notify(item1: &impl Summary, item2: &impl Summary) { }
// 複数の場合
pub fn notify(item: &(impl Summary + Display)) { }
// ジェネリック型の場合
pub fn notify<T: Summary + Display>(item: &T) {
ライフタイム注釈
- ライフタイム異なるとバグるやつには指定が必要
- 参照返す場合は引数と一致させる
- 関数抜けるタイミングで消えるため
- 所有権の移動の回避に
clone
使える- ライフタイム考慮不要になる
- 返り値、タプルではなく構造体使う
- 意味を持たせる
- 呼び出し側で分割代入みたいなことしてたら、置き換え考える
- Rubyでいうインスタンス化
fn parse_config(args: &[String]) -> Config {
let query = args[1].clone();
let filename = args[2].clone();
Config { query, filename }
}
コンストラクタ
- Class == 構造体
- newを定義する
impl Config {
fn new(args: &[String]) -> Config {
// バリデーション
if args.len() < 3 {
panic!("not enough arguments");
}
let query = args[1].clone();
let filename = args[2].clone();
// インスタンス返す
Config { query, filename }
}
}
impl Config {
fn new(args: &[String]) -> Result<Config, &'static str> {
if args.len() < 3 {
return Err("not enough arguments");
}
let query = args[1].clone();
let filename = args[2].clone();
Ok(Config { query, filename })
}
}
例外処理(独自エラー実装)
- unwrap_or_else
let config = Config::new(&args).unwrap_or_else(|err| {
println!("Problem parsing arguments: {}", err);
process::exit(1);
});
-
Box<dyn Error>>
- トレイトオブジェクト
- 関数がErrorトレイトを実装する型を返すことを意味する
- 戻り値の型を具体的に指定しなくても良い
- エラーケースによって異なる型のエラー値を返せる
- dynamic
fn run(config: Config) -> Result<(), Box<dyn Error>> {