std::stringの受け取り方法による速度
C++のムーブと右辺値参照を見直してたら勘違いしていた所あったので、引数の型による速度差を調べてみた。
値渡しだとmoveしても遅いので参照で受ける必要がある。 (/ω\)ハズカシーィ
std::tupleの反復処理
std::tupleで反復処理をさせる方法
#include <tuple> #include <string> #include <iostream> #include <utility> template <class Value> void print_value(std::ostream& stream, std::size_t index, const Value& value){ stream << "index(" << index << ") " << value << std::endl; } template <class Tuple, std::size_t... Index> void print_tuple_impl(const Tuple& tuple, std::index_sequence<Index...>) { (print_value(std::cout,Index, std::get<Index>(tuple)), ...); } template <class Tuple, std::size_t TupleSize = std::tuple_size_v<Tuple>> void print_tuple(const Tuple& tuple) { print_tuple_impl(tuple, std::make_index_sequence<TupleSize>()); } int main() { std::tuple<int, int, int, std::string> value { 1, 2, 3, "end" }; print_tuple(value); return 0; }
std::make_index_sequence とか知らなかってなぁというのとfold expression なんかはすっかり忘れてた。。。
Rustのファイルパース
Rustでログのパース処理を書こうと思った時迷ってたのでメモ
最初ファイルの内容全部読んで改行コードで分割しようと思ったが、このやり方は手間でBuReaderを使って行毎のイテレータを作るのが良さそう
MongoDB基礎
仕事でMongoDBを使うことになりそうなので、調べたことのメモ
MongoDBとは
NoSQLの1つ。
NoSQLの特徴は
- スキーマレス
- テーブル結合ができない
- トランザクションがない
- 水平スケールアウトが用意
RDBとの用語の違い
RDB | MongoDB |
---|---|
table | collection |
row | document |
column | field |
index | index |
primery key | _id フィールド |
※_idについて
_id
は自動で生成する主キー。
これはドキュメント登録時に自動で付与される。
操作
データベース
作成
use 名前
でデータベースの作成が行われる。
mongoDBはインストールされている環境でuseを実行して成功すると
switched to db 名前
と出る。
※注意
useを実行した後にデータベースの一覧を出力する show dbs
を行っても先程作ったDBは表示されない。
これはuseだけでは実際のデータベースはつくられないため。
useを行った後にコレクションを作成すると実際のデータベースが作られる。
情報の確認
db.stats()
を実行するとデータベースの統計情報を確認することができる。
削除
db.dropDatabase()
でデータベースの削除が行える。
削除に成功すると { "dropped" : "データベース名", "ok" : 1 }
と出力される。
コレクション
作成
db.createCollection("コレクション名")
でコレクションを作成できる。
このコマンドを実行すると { "ok" : 1 }
と出力される。
これでコレクションが作られる。
一覧表示
コレクション一覧を確認する場合は show collections
で確認できる。
情報の確認
db.コレクション名.stats()
で出力できる。
削除
db.コレクション名.drop()
でコレクションの削除ができる。
補足 コレクションの中のデータを全て消したい場合データ数によっては削除をして、再作成をしたほうば速い。
ドキュメント
登録
1件だけ登録
db.コレクション名.insertOne(jsonオブジェクト)
を実行することでドキュメントの登録ができる。
登録されるデータは引数に渡したjsonデータに自動付与される_id
がついたデータになる。複数件の登録
db.コレクション名.insertMany([jsonオブジェクト])
で配列に含んでいるオブジェクトを一気に登録してくれる。
※注意
ドキュメントを登録するコレクション名を間違えるとそのコレクションを自動作成をしてドキュメントを登録するので気を名前には気をつける。
検索
全件検索
db.コレクション名.find()
で登録されている全件を出力してくれる。
全件取得ではデフォルトで20件ずつしか取得できない。
ただ、it
を続けて入力すると次の20件を取得してくれる。フィールドを指定して検索
ここでもfindを利用する。
全件取得と違いfindの引数を渡すと個別の検索をしてくれる。
db.コレクション名.find(jsonオブジェクト)
引数に指定したオブジェクトと一致するドキュメントを出力してくれる。
また、第2引数にオブジェクトを指定することで取得するフィールドの制限をすることができる。
{フィールド: 0}
で非表示,{フィールド: 1}
で表示
文字列等で部分一致の検索をしたい場合は検索条件のオブジェクトに正規表現を使うと可能
正規表現なので、前方一致、後方一致など色々と条件を組むことができる。配列を検索する
配列のフィールドを出力する場合は第2引数を指定すればよいが検索をする場合、検索データを配列にすると完全一致での検索になる。{"name" : "test", "skill" : [ "a", "b", "c" ]}
このドキュメント登録があり、skillの一致で検索する場合
db.test.find({skill:["a", "b", "c"]})
このような検索を組む必要がある。
一部一致での検索をする場合オブジェクト型の検索データにすれば良い。
db.test.find({"skill": "a"})
とすることで一部一致の検索が行える。オブジェクトの検索をする 配列では検索する場合完全一致だったが、オブジェクトの場合一部一致での検索が可能。
親フィールド.フィールド
で検索が行える。
"name" : "test", "child" : { "a" : 1, "b" : 2 } }
このドキュメント登録があり、これを検索する場合
db.test.find({"child.a": 1})
とすることで検索ができる。
ちなみにこれは配列のオブジェクトに対しても使える。範囲指定 引数のオブジェクトの値としてオブジェクトを指定すると範囲指定での検索ができる
{$lte: value}
$letで以下{$lt: value}
$ltで未満{$gte: value}
$gteで以上{$gt: value}
$gtでより大きい
noの値が10以下のドキュメントを探す場合db.test.find({no: {$lte: 10}})
こんな指定になる。
複合条件で 以上、以下 のような検索を組みたい場合db.test.find({no: {$lte: 10, $gte: 5}})
となる。and, or
{$and: [検索条件]}
でand検索ができる。
orの場合$and -> $or に変えて{$or: [検索条件]}
とすればよい。
例えば no が 0 or 1のデータを検索したい場合
db.test.find({$or:[{no: 0}, {no:1}]})
このような指定になる。フィールドの存在での検索 特定のが無いドキュメントを検索する場合
{フィールド: {$exists: true or false}}
で検索ができる
trueの場合フィールドが存在するもの、falseの場合フィールドが存在しないものデータ型で検索 $typeを使うことでフィールドの型による検索ができる。
{フィールド: {$type: "型"}}
で検索を型による検索が行える。
型の指定はダブルクォートで指定をするが、各型に割り振られた番号でも検索は可能。
ただ、undefinedのしてはできないため存在しないフィールドの検索はこれでは行えない。配列の中に指定した値をもつ検索 $allを利用
{フィールド: {$all: [値]}}
で指定した値を持つ配列の検索が行える。
ソート
findの結果にsort関数を呼び出すことで検索結果のソートができる。
db.コレクション名.find().sort({ソート条件})
ソート条件にはソート条件にしたいフィールドと1 or -1で指定する。
1は昇順、-1は降順となる。
db.test.find({no: {$exists: true}}).sort({no: 1})
noのフィールドがあるドキュメント取得してnoでソートする場合
件数制限
検索結果が多い場合は結果に件数の制限を指定することができる。
db.コレクション名.find().limit(件数)
で引数に指定した件数結果のみに絞ることができる。
ただ、デフォルトの20件制限を変更するものではないため20件を超えて指定した場合次の結果を見るためにはit
が必要になる。
開始位置の指定
検索結果の開始指定をずらしたい場合skipを指定する。
db.コレクション名.find().skip(件数)
で引数に指定した件数ずらした所から開始する。
limitと組み合わせて、開始番号と終了地点の制御ができる。
組み合わせる場合
db.コレクション名.find().limit(件数).skip(件数)
db.コレクション名.find().skip(件数).limit(件数)
どちらが先でも問題ない。
更新
登録したドキュメントを更新する。
1件だけ更新
db.コレクション名.updateOne({検索条件}, {$set: {更新内容}})
で更新ができる。
検索条件に一致する1件だけ更新がされる。
あくまで更新なので、フィールド削除は行えない。複数件更新
db.コレクション名.updateMany({検索条件}, {$set: {更新内容}})
で複数件の更新ができる。
updateOneと違い検索条件に一致する全てのドキュメントを更新する。ドキュメントを差し替える _id以外のフィールドを削除して全部差し替えることができる。
db.コレクション名.replaceOne({検索条件}, {更新内容})
updateOneと同様に検索条件に一致する1件だけ更新される。
replaceManyは存在しない。配列に要素を追加する
updateOneで書いた更新内容を指定する時に"フィールド名.index"
と指定することで配列の中身に対して更新を行える。db.test.updateOne({"skill": "a"}, {$set: {"skill.3": "3"}})
データ登録、または更新 updateOneなどで検索条件に一致しなかった場合に新たにコレクションを追加する指定ができる。
db.コレクション名.updateOne({検索条件}, {$set: {更新内容}}, {upsert: true})
第3引数に{upsert: true}
と指定すると、update or insertの挙動になる。
なお、追加されるコレクションは 検索条件と更新内容を合わせた内容になる。
削除
指定したドキュメントを削除する。
1. 1件だけ削除
db.コレクション名.deleteOne({検索条件})
updateOneと同様に検索条件を満たすドキュメントが複数あっても削除されるのは1件のみ
- 複数件の削除
db.コレクション名.deleteMany({検索条件})
検索条件を満たすドキュメントを全て削除する。
引数に空のオブジェクトを指定した場合コレクション内のドキュメントを全て削除する。
データの集計
findでは集計が行えない。
集計を行う場合はaggregationを利用する
db.コレクション名.aggregate([{$match: {検索条件}, {$group: {集計条件}}}]
とかく。
$matchは検索条件、$groupは集計の条件
$matchに関しては省略が可能
{"_id":0,"no":1,"param":0} {"_id":1,"no":2,"param":0} {"_id":2,"no":1,"param":1} {"_id":3,"no":2,"param":1} {"_id":4,"no":1,"param":2} {"_id":5,"no":2,"param":2} {"_id":6,"no":1,"param":3} {"_id":7,"no":2,"param":3} {"_id":8,"no":1,"param":4} {"_id":9,"no":2,"param":4}
このドキュメントが登録されているtestコレクション
noで分類分けしてparamの合計を知りたい場合
db.test.aggregate([{$group:{ _id: "$no", total: {$sum: "$param"}}}])
このようになる。
全件を対象に行うため$matchを省略し、$groupから開始
_id: "$no"
でグルーピングし、_idがnoの項目になる新しいドキュメントが作られる。
その後に続くtotal: {$sum: "$param"}
でtotalフィールドーがフィールドが$paramの合計値で作られる。
matchを指定する場合
db.test.aggregate([ {$match: {param: {$gt: 2}}}, {$group:{ _id: "$no", total: {$sum: "$param"}}} ])
この様にすればよい。
※matchとgroupは順番を逆にすると違う意味になる。
match -> groupと指定をした場合フィルタリングをした結果に集計を行ったが、逆にした場合集計の結果に対してフィルタリングをすることになる。
db.test.aggregate([{$group:{ _id: "$no", total: {$sum: "$param"}}}, {$match: {_id: 1}}])
とした場合結果は { "_id" : 1, "total" : 10 }
となる。
aggregateは配列で指定した構文を実行するようなもので match -> group -> matchみたいなこともできる。
その他の集計関数
groupで指定したsumは合計値を出してくれたが、他にも集計の関数はある。
1. $max
最大値
2. $mix
最小値
3. $avg
平均値
$sum
を差し替えると利用ができる。
コレクション全体の集計
コレクション全体の集計をしたい場合 _id
の値をnullにすれば良い。
db.test.aggregate([{$group:{ _id: null, total: {$sum: "$param"}}}])
カウント
集計の中のデータ個数をカウントする。
noが1のデータの個数を取得
db.test.aggregate([{$match:{no: 1}}, {$count: "count"}])
testコレクションに登録されているドキュメント数を取得
db.test.aggregate([{$count: "test_count"}])
グループ毎のカウント
$count
は配列のlengthを取得するようなものなので、グループ毎のカウントを取得することはできない。
1クエリでグループ毎にカウントをする場合$sum
を使うのがよさそう。
db.test.aggregate([ {$group:{_id: "$no", count: {$sum: 1}}} ])
これでnoのフィールド毎のカウントを集計することでグループ毎のカウントを計算できる。
コレクションの結合
$lookup
を利用しコレクションを結合することができる。
結合する条件は2つのコレクション間で一致するフィールドを持つ必要がある。
{"_id":0,"name":"taro"} {"_id":1,"name":"jiro"}
このドキュメントが登録されているtest2コレクションを追加してやった場合testコレクションと_id
で一致できるため結合ができる。
$lookup
は
db.結合元コレクション.aggregate( { $lookup: { from: "結合先コレクション", localField: "結合元コレクション", foreignField: "結合先コレクション", as: "結合したフィールド名" } });
となっている。
先程のtest2コレクションとtestコレクションを_idが同じデータで結合する場合
db.test2.aggregate([ { $lookup: { from: "test", localField: "_id", foreignField: "_id", as: "comb" } } ])
となり、結果は
{ "_id" : NumberInt(0), "name" : "taro", "comb" : [ { "_id" : NumberInt(0), "no" : NumberInt(1), "param" : NumberInt(0) } ] } { "_id" : NumberInt(1), "name" : "jiro", "comb" : [ { "_id" : NumberInt(1), "no" : NumberInt(2), "param" : NumberInt(0) } ] }
こうなる。
結合するのを一部に絞りたい場合 $project
を利用する。
db.test2.aggregate([ { $lookup: { from: "test", localField: "_id", foreignField: "_id", as: "comb" } }, { $project: { "comb._id": 0 } } ])
$project
を実行するタイミングは結合後のデータが対象になるので、結合先の_idがいらない場合 comb._id
この様にフィールド名が必要となる。
設定
インデックス
インデックスを利用すると検索を早くしてくれる。
ほぼ必須の設定
シャーディングのことも考えて複合インデックスで作るのがよさそう。
作成
db.コレクション名.createIndex( { キー: 1, キー: -1 }, {オプション} )
1を指定すると昇順でインデックスを作成
-1を指定すると降順でインデックスを作成
オプション | background | インデックスの作成をバックグラウンドで行うデフォルトfalse | | ----------------------- | ------------------------------------------ | | name | インデックス名の指定 | | unique | インデックスをユニークにする。ユニークの場合nullは扱えない デフォルトfalse | | partialFilterExpression | フィルタの設定をして、一致したドキュメントのみインデックスを作成する。 |
確認
db.コレクション名.getIndexes()
db.test.createIndex({group: 1})
を行った testコレクションに対して実行すると
[ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "luckyfarmer_log.test" }, { "v" : 2, "key" : { "group" : 1 }, "name" : "group_1", "ns" : "luckyfarmer_log.test" } ]
このような結果になる。
1件目は_idフィールドに対してのインデックスでこれはデフォルトで作成される。
2件目はcreateIndexで作った新たなインデックスの設定
削除
db.コレクション名.dropIndex(インデックス名);
で削除が行える。
引数のインデックス名は getIndexes の結果にある nameフィールドの値
再構築
インデックスは再構築をすることができる。
目的としてはデータ量のダイエットのために行われる。
データを入れていくとどんどんインデックスのデータが膨らんでムダなデータができてくる。
そのムダなデータを落として、ディスクの使用量を削減する。
db.コレクション名.reIndex()
再構築を行うとデフォルトインデックスの再構築がまず行われ、その間データベースにロックがかかる。
それ以外のインデックスはバックグラウンドで再構築される。
種類
インデックスにはいくつか種類がある。
- デフォルトインデックス
_id
フィールドに自動的に作られる。削除はできない。 - テキストインデックス
文字列に対して検索クエリをサポートする。 作成する場合 インデックス作成時の値に"text"
を指定 - TTLインデックス
特定の時間にコレクションからドキュメントを削除するために利用
createIndexの引数にcreateIndex({キー}, {expireAfterSeconds: 秒数})
で指定をする。
指定したフィールドの値にはDate型、Date型を含む配列でないと動作しない。
因みに指定は秒だが、チェックは1分間隔らしい
Rust入門③
Rustは1つのリソースに対して単一の所有者を持たせ、必要な時に借用(一時的な貸与)が基本となっている。
ただ、構成によっては対応しきれないことがある。
そういった場合に活用できるデータ構造の紹介
共同所有者
標準ライブラリに複数の所有者をもたせることができるポインタRc<T>
とRrc<T>
がある。
Rc
Rcポインタを作ると対象のリソースはヒープ領域に格納され、参照カウンタによる管理が行われる。
{ let rc = std::rc::Rc::new(1); let rc2 = rc.clone(); println!( "rc count{} rc2 count{}", std::rc::Rc::strong_count(&rc), std::rc::Rc::strong_count(&rc2) ); } { let mut rc = std::rc::Rc::new(1); if let Some(v) = std::rc::Rc::get_mut(&mut rc) { *v = 5; } println!("{}", rc); { let rc2 = rc.clone(); // 参照カウントが2以上の場合の場合Noneが返って来て値の変更ができない println!("{:?} {}", std::rc::Rc::get_mut(&mut rc), rc2); } // weakポインタだと参照カウントは増えない let weak = std::rc::Rc::downgrade(&rc); println!("rc count{}", std::rc::Rc::strong_count(&rc),); // weakポインタで共有している場合も値の変更ができない if let Some(v) = std::rc::Rc::get_mut(&mut rc) { *v = 10; println!("{}", rc); } { // 参照カウントを増やして共有する let rc2 = weak.upgrade().unwrap(); println!("rc2 count{}", std::rc::Rc::strong_count(&rc2)); } std::mem::drop(rc); // Rcが解放しているのでNoneになる println!("{:?}", weak.upgrade()); }
Arc
Rcと似ているがこちらはSyncトレイトを実装しているため複数のスレッドで共有ができる。
ただ、オーバーヘッドが増え速度は遅い。
モジュールはRcと違い std::sync::Arc
になる。
内側のミュータビリティ
コンパイル時の借用チェックを迂回してデータを可変する仕組み。
Rcを使ってcloneした変数がに2つ以上存在する場合は値を変更できないが、この仕組を利用すると変更できるようになる。
let rc = std::rc::Rc::new(std::cell::RefCell::new(1)); // RefCellで包まれているの println!("{:?}", rc); // 値を取り出す場合 // borrowで不変の値を取り出す println!("{:?}", rc.borrow()); // 値を変更する場合 // borrow_mut 可変の参照を取り出す *rc.borrow_mut() = 5; println!("{:?}", rc.borrow()); let rc2 = rc.clone(); *rc2.borrow_mut() = 10; println!("{:?}", rc.borrow());
この様にすることでRcやArcで参照カウントが2以上でも変更できるようになる。
ただ、borrow_mutを行ったとき、他の参照が有効の場合クラッシュする。
クラッシュしないようにする場合 try_borrow_mut
を使う。
これを使うとResult型の値を返し、成否のチェックが行える。
if let Ok(v) = rc2.try_borrow_mut() { println!("ref OK {}", v); }
内側のミュータビリティもRc, Arcのようにシングルスレッド、マルチスレッド向けのものがある。
シングルスレッド
- UnsafeCell
安全装置がない。getでポインタを取得して内部の値を書き換える - Cell
UnsafeCellをラップして作られていて、オーバーヘッドがない。set関数を使って内部で値を書き換える。 - RefCell
参照を得ることができる。ただ、借用ルールの検証があるためオーバーヘッドがある。
マルチスレッド
- Mutex
- RxLock
- AtomicUsize
- AtomicI32
など
Rust入門② 基礎文法
基本
プリミティブ型
ユニット型
空を表す型でサイズも0バイトになる。
空のタプル型で ()
で表現する。
値を返さない関数の戻り値もユニット型になる。
真理値
bool型
値は true
, false
を持つ。
整数
型 | 符号 | ビット幅 |
---|---|---|
i8 | あり | 8 |
u8 | なし | 8 |
i16 | あり | 16 |
u16 | なし | 16 |
i32 | あり | 32 |
u32 | なし | 32 |
i64 | あり | 64 |
u64 | なし | 64 |
i128 | あり | 128 |
u128 | なし | 128 |
isize | あり | アドレスのビット幅 |
usize | なし | アドレスのビット幅 |
usize型は配列のインデックスや長さなどで使われている。
整数リテラル
整数リテラルはデフォルトでi32になる。
型を指定する場合 0u8
このようにサフィックスに型を書いて指定することもできる。
整数リテラルにはアンダースコアを利用して読みやすい形で書くことができる。
let n1 = 10_000; // i32型 let n2 = 0u8; // u8型 let n3 = -100_isize; // isize型 let n4 = 10; let n5 = n3 + n4; // ここで isize型 + n4 の計算をしているので型推論でn4もisize型になる
10進数以外の数値
let n1 = 0xff; // 16進数 let n2 = 0o744; // 8進数 let n3 = 0b1010; // 2進数
アスキーコード
let n1 = b`A`; // ASCII文字'A'の文字コード65u8になる
少数
f32型とf64型がある。
デフォルトはf64型になる。
整数型と同様にサフィックスに型を書いて指定ができる。
let f1 = 10.0; // f64 let f2 = 1_234.567f32; // f32 let f3 = 578.6E+77; // f64 指数指定
文字
Unicodeの1文字を持つ。
charリテラルはシングルクォートで作る。
1文字を表すのに英数でも4バイトを使う。
let c1 = 'A'; // char型 let c2 = 'a'; assert!(c1.is_uppercase()); // 大文字か検証 let c3 = '0'; assert!(c3.is_digit(10)); // 10進数数字か検証 let c9 = '漢'; // マルチバイト文字も可能
参照
メモリ安全なポインタ
&T
, &mut T
で定義する。
- &T
不変参照 readonly - &mut T 可変参照 read&write可能
let c1 = 'A'; let c1_ptr = &c1; // イミュータブル参照 let mut n1 = 0; let n1_ptr = &mut n1; // ミュータブル参照 *n1_ptr = 100; // 参照外しをして値の変更
生ポインタ
メモリ安全ではないポインタ
*const T
, *mut T
で定義する。
- *const T
不変参照 readonly - *mut T 可変参照 read&write可能
生ポインタは他の言語とアドレスの受け渡しによる連携や所有権システムから外したい場合に利用する。
参照外し、他のポインタへの型変換は unsafe
ブロックに書く必要がある。
let c1 = 100; let c1_ptr: *const char = &c1; unsafe{ println!(*c1_ptr); } let mut n1 = 0; let n1_ptr: *mut i32 = &mut n1; unsafe{ println!(*n1_ptr); }
関数ポインタ
関数を示すポインタ サイズはusizeと同じになる。
fn(T) -> T
で定義する。
関数ポインタの場合型注釈は書く必要がある。
書かない場合関数定義型として扱われる。 サイズは0バイト
関数ごとに異なる型になる。
関数定義型は匿名型として扱われるが、プログラム中の型名として利用はできない。
fn type_of<T>(_: T) -> String { let a = std::any::type_name::<T>(); return a.to_string(); } fn double(value: i32) -> i32 { value * 2 } let f1: fn(i32) -> i32 = double; println!("function pointer: {}", f1(5)); let f2 = double; println!("function pointer: {}", type_of(f1)); // function pointer: fn(i32) -> i32 println!("function pointer: {}", type_of(f2)); // function pointer: firststep::double
タプル
複数の型のデータを入れることができる。
(値, 値)
で定義する。
各要素のアクセス
フィールド名
変数名.定数
でアクセスすることができる。
定数はデータの先頭を0から始まる
let t1 = (1, true); let n = t1.0; let mut t2 = (1, true); t2.0 = 2;
パターンマッチ
let (x1, x2) = tuple変数
とすることで、xにタプルの内容を束縛してくれる。
要素を書き換える場合 ref mut
を変数名の前に付ける。
let t1 = (1, true); let (n1, b1) = t1; let t2 = (1, true); let (n2, _) = t2; // 不要な値にはアンダースコアを使うと無視できる。 // 要素を書き換える場合 let mut t3 = (2, false); let (ref mut n3, ref mut b3) = t3;
配列
メモリ上に連続した領域を確保する。
[値, 値]
で定義する。
型指定をする場合 let x: [型;要素数]
と書く。
サイズを調べる場合 x.len()
とすればサイズが返ってくる。
要素に指定するにはCopyトレイトを実装する必要がある。
各要素のアクセス
インデックス
配列外にアクセスした場合panicになる。
getメソッドを利用することで安全なアクセスが可能。
let a1 = [1, 2, 3]; let a2: [f32; 3] = [1.1, 2.1, 3.1]; println!("{}", a1[0]); println!("{}", a1.len()); println!("{}", a2[0]); // let some = a1.get(0); // Option型で返る println!("{:?}", some); let none = a1.get(5); // 配列外ならNoneに println!("{:?}", none);
イテレータ
配列の全要素に対してアクセスをする場合はイテレータを使う。
変数.iter()
で取得できる。 値を変更する場合 変数.iter_mut()
にする。
let a1 = [1, 2, 3]; for i in a1.iter() { print!("{}", *i); } let mut a3 = [1, 2, 3]; for i in a3.iter_mut() { *i += 1; }
len, iter, iter_mutなどは配列の機能ではなく、slice型に暗黙的な変換が行われ呼び出される。 これ以外にもsliceのメソッドはありそれらも利用可能。
スライス
配列要素の範囲に効率よくアクセスするためのビューとして使う。
連続したメモリ領域に同じ型の要素が並んでいるデータ構造であればどれでも対象にすることができる。
不変参照(&[型])、可変参照(&mut [型])、Box(Box<型>)を経由してアクセスする。
配列と違い実体を持たないので、型にサイズは不要
インデックスアクセス以外にも [begin..end]
の指定ができる。
この時endが含まれないことに注意
let a1 = [1, 2, 3]; // let s1 = &a1; 型を見る感じこれだと配列の参照 let s1: &[i32] = &a1; // これでsはスライスになる。 println!("{}", s1[0]); println!("{:?}", a1.first()); // Some(1)が返る println!("{:?}", a1.last()); // Some(3)が返る let s2 = &a1[0..2]; let s3 = &a1[0..]; let s4 = &a1[..3]; println!("{:?}", s2); // [1, 2] println!("{:?}", s3); // [2, 3] println!("{:?}", s4); // [1, 2, 3] // sort() と sort_ unstable() の 違い // // sort() は 安定 ソート なので 同 順 な データ の ソート 前 の 順序 が ソート 後 も 保存 さ れる // // soft_ unstable() は 安定 ソート では ない が、 一般的 に sort() より 高速 // let (s7, s8) = s6.split_at_mut(3); println!("{:?}", s7); // [1, 3, 3] println!("{:?}", s8); // [5, 5, 6, 8, 9, 9]
str
Unicodeの文字で構成された文字列。
strはスライスでアクセスを行うため、文字列スライス型とも呼ばれる。
型は &str
になる。
ダブルクォートで作る文字列はstrになり、型は &'static str
になる。
lenメソッドを使った場合文字数ではなく、バイト数が返ってくる。 インデックスでのアクセス等に関しても文字ではなくバイトデータに対してアクセスすることになる。
let str1 = "abcd"; println!("{}", str1); let str2 = "abcd efg"; // この場合dの後に改行が入る println!("{}", str2); let str3 = "abcd\ efg"; // エスケープを指定すれば改行は入らない println!("{}", str3); let str4 = r#"abc#d"\na"#; // r#""# でraw文字列リテラルになる println!("{}", str4); let str4 = r###"abcd"#\n"###; // r###""### とすれば "# が使える println!("{}", str4); let str5 = "あいうえお"; println!("{}", str5.len()); // 15になる let str6 = "あ,い\nうえお"; let lines = str6.lines().next(); println!("{:?}", lines); // Some("あい") let mut chars = str6.chars(); // char型のイテレータの取り出し println!("{:?}", chars.next()); let mut chars_ind = str6.char_indices(); // char_indicesを使うと文字と位置のタプルになる println!("{:?}", chars_ind.next()); // Some((0, 'あ')) println!("{:?}", chars_ind.next()); // Some((3, ','))
ユーザー定義型
Box
Box型はデータをヒープに移動し、その参照を保持する。
Boxのデータを作るときは Box::new(変数)
と書く。
移動させた変数は破棄されて、アクセスをした場合コンパイルエラーになる。
スコープから外れBox変数が破棄される時にヒープに作られたデータも破棄される。
let t1 = (1, "aaa".to_string()); // (&str).to_string でメモリ確保した文字列になる let b1 = Box::new(t1); let (n1, s1) = &(*b1); // (*b1) で実体を取り出してその参照 println!("{}, {}", n1, s1); println!("{:?}", b1);
ベクタ
可変長配列 配列はスタックに確保するがベクタはデータをヒープに確保する。
データを作るときは vec![]
と書く。
Vec::with_capacity(数値)
で確保サイズの指定ができる。
// ベクタ let mut v1 = vec![1, 2, 3, 4]; println!("legth {}, capacity {}", v1.len(), v1.capacity()); v1.push(5); println!("legth {}, capacity {}", v1.len(), v1.capacity()); v1.pop(); println!("legth {}, capacity {}", v1.len(), v1.capacity()); v1.remove(0); println!("legth {}, capacity {}", v1.len(), v1.capacity()); for i in v1.iter() { println!("{}", *i); } v1.insert(0, 5); println!("legth {}, capacity {}", v1.len(), v1.capacity()); for i in v1.iter() { println!("{}", *i); } let mut v2 = vec![6, 7, 8]; v1.append(&mut v2); // appendだとv2の中身を移動 let mut v2 = vec![6, 7, 8]; v1.extend_from_slice(&mut v2); // extend_from_sliceだとv2の中身をコピー println!("v1: legth {}, capacity {}", v1.len(), v1.capacity()); for i in v1.iter() { println!("{}", *i); } println!("v2: legth {}, capacity {}", v2.len(), v2.capacity()); let mut empty: Vec<i32> = Vec::new(); // 空の配列 let mut v3 = Vec::with_capacity(10); // サイズは0で、メモリは10個分確保 println!("v3: legth {}, capacity {}", v3.len(), v3.capacity()); v3.insert(0, 1); println!("v3: legth {}, capacity {}", v3.len(), v3.capacity()); for i in v3.iter() { println!("{}", *i); } // スライスで参照は可能 let s1: &[i32] = &v2[0..]; println!("{}", s1[0]); // Box<[i32]> に変換サイズ変更ができないため必要最低限に切り詰められる let b1 = v2.into_boxed_slice(); println!("{}", b1[0]); // BoxからVecに変換 let v2 = b1.into_vec(); println!("{:?}", v2);
String
str型と似た性質を持つヒープ領域にメモリ確保をした文字列。
str型と違い文字の追加や削除などの操作が可能。
let s1 = "abcd".to_string(); // std::string::Stringなる let mut s2 = format!("efghi"); println!("{}", type_of(&s2)); s2.push_str(&s1); // s2にs1の内容を追加 s2.push('あ'); // char型データを追加 println!("{}", s2); println!("{}", s2.len()); // str型と同じくchar型とインデックスのタプルを返す for i in s2.char_indices() { println!("{:?}", i); } let format_string = format!("{}", 1); println!("{}", format_string); let parse_test = "1".parse::<i32>(); // 文字列から数値への変換 if let Ok(v) = parse_test { println!("{}", v); }
文字列はこれ以外にも
- CString, CStr
C言語で使われるヌル終端文字列
- OsString, OsStr
OSのネイティブ文字列
- PathBuf, Path
パス文字列 OS間のパス区切りの差異吸収をしてくれる
Range
スライスを作る時に利用した構文は範囲を利用している。
start..end
, ..end
, start..
, start..=end
と多数の指定がある。
Rangeを使うことで特定回数ループをするコードも簡単に書ける
for i in 0..12 { println!("{}", i); }
オプション型
値があるのか不明な戻り値に使われる。
C言語などはnullで表現しているがrustではOption型を利用する。
値がある場合はSomeとなり、ない場合はNoneになる。
let a1 = [1, 2, 3, 4]; let op1 = a1.get(0); // 安全な値の取り出し if let Some(value) = op1 { println!("{}", value); } // unwrapで値の取出し // Noneに対して行うとpanic let value2 = op1.unwrap(); println!("{}", value2); let op2 = None; let value3 = op2.unwrap_or_else(|| 0); // Noneの時にクロージャを実行 println!("{}", value3); fn option() -> Option<i32> { let a1 = [1, 2, 3, 4]; let op1 = a1.get(4)?; // ?で値を取り出す、Noneの場合はこの関数の戻り地もNoneになる let op2 = a1.get(1)?; Some(op1 + op2) }
リザルト型
呼び出しの結果エラーになる可能性を暗示する型
OkとErrの2つの値がある。
オプション型との違いはErrでは失敗した理由の情報を持てる。
オプションで使った機能はリザルトでも同じように使える。
let parse_ok = "1".parse::<i32>(); // 文字列から数値への変換 println!("{:?}", parse_ok); let parse_err = "a".parse::<i32>(); // 文字列から数値への変換 println!("{:?}", parse_err); // Err(ParseIntError { kind: InvalidDigit })になる
新しい型の定義
エイリアス
型に別名をつけて利用できるようにする
type エイリアス = 型
で記述する。
構造体
3種類の構造体が存在していて、それぞれ - 名前付きフィールド構造体 - タプル構造体 - ユニット構造体
の3つがある。
名前付きフィールド構造体
アトリビュートに #[derive(Debug)]
を追加すると
formatやprintで自動出力ができるので便利
struct 型名 { 変数名: 型, }
で定義する。
#[derive(Debug)] struct Human { age: i32, name: std::string::String, } let human = Human { age: 20, name: format!("human"), }; println!("{}", human.age); println!("{}", human.name); // こんな初期化もできる // 省略形の初期化の場合フィールド名を一致する変数を与える必要がある。 let age = 20; let name = format!("human_init1"); let human_init1 = Human { name, age }; // この初期化はageは直接していして、nameはhuman_init1を利用する // ただ、Stringなどムーブされるデータはコピーされないので注意 let human_init2 = Human { name: format!("human_init2"), ..human_init1 }; println!("{:?}", human_init1); println!("{:?}", human_init2); // Defaultアトリビュートを指定すると // デフォルト値使える #[derive(Default, Debug)] struct DefaultStruct { param1: i32, param2: i32, } let default_struct1: DefaultStruct = Default::default(); println!("{:?}", default_struct1); let default_struct2 = DefaultStruct { param1: 20, ..Default::default() }; println!("{:?}", default_struct2); // デフォルト値を指定したい場合 // Defaultトレイトを実装する #[derive(Debug)] struct DefaultStructTrait { param1: i32, param2: i32, } impl Default for DefaultStructTrait { fn default() -> Self { Self { param1: 1, param2: 2, } } } let default_struct_trait: DefaultStructTrait = Default::default(); println!("{:?}", default_struct_trait);
タプル構造体
名前の通りタプル構造体
フィールドに名前を与えてないので変数へのアクセスはタプルと同じように .0
の形になる。
タプル構造体の便利な使い方として、newtypeというデザインパターンがある。
エイリアスだと元の型の別名として扱うだけなので、元の型の変数を代入するなどが可能になっている。
タプル構造体を利用してコンパイラの型チェックを強化することができる。
#[derive(Debug)] struct TupleStruct(i32, i32); let tuple_struct = TupleStruct(1, 2); println!("{}", tuple_struct.0); println!("{}", tuple_struct.1); // newtypeで型チェックを強化 type Int32 = i32; #[derive(Debug)] struct Struct1 { param1: Int32, param2: Int32, } let param1 = 1; let param2 = 2; // Struct1の初期化でi32の変数を使ってもOK println!("{:?}", Struct1 { param1, param2 }); #[derive(Debug)] struct NewType(i32); #[derive(Debug)] struct Struct2 { param1: Int32, param2: NewType, } // これはエラー // println!("{:?}", Struct2 { param1, param2 }); println!( "{:?}", Struct2 { param1, param2: NewType(2) } );
ユニット構造体
値を持たない構造体
フィールドを持たないトレイトの実装をしたい時に使う
struct UnitStruct; println!("{}", std::mem::size_of::<UnitStruct>()); // サイズは0バイト
列挙型
rustのenumは定数だけではなく、各定義固有のデータをもたせることができる。
Optionも列挙型で定義されていて、Someは値を持ちNoneは値を持たな様な差を作ることができる。
データの持たない列挙型はisize型の整数値が割り当てられる。
定数のみ
// PartialEqを付けると == での比較が可能 #[derive(Debug, PartialEq)] enum Enum { Zero, One, Two, } let enum_value = Enum::Zero; match enum_value { Enum::Zero => println!("zero"), Enum::One => println!("one"), Enum::Two => println!("two"), } if enum_value == Enum::Zero {} println!("{}", type_of(Enum::Zero)); enum EnumValueSet { Zero = 1, One = 2, Two = 3, }
データを持つ列挙型
列挙型は整数値以外にも構造体と同じ構文でフィールドをもたせることができる。
データを持つEnumはmatch式を利用してフィールドの値を別変数に束縛できる。
#[derive(Debug, PartialEq)] enum Enum { Zero, One { param1: i32, param2: i32 }, Two, } let data = [ Enum::Zero, Enum::One { param1: 0, param2: 1, }, Enum::Two, ]; for i in data.iter() { match i { Enum::One { param1, param2 } => println!("param1:{}, param2:{}", param1, param2), _ => println!("{:?}", i), } }
型変換
型キャスト
asを利用して行う明示的な型変換。
桁あふれのチェックを行わない。
as はスカラ型のみサポートしていて、タプルや構造体に対して行うことはできない。(Formトレイトを実装すると可能)
let c1 = 'a'; let us1 = c1 as i8; println!("{}", us1); // aの文字コード let n1 = 300; let us2 = n1 as i8; println!("{}", us2); // 桁あふれはしないで44になる 300 % 256
Transmute
std::mem::transmuteは明示的な型変換。
ビット列のサイズが同じデータに対して行うことができる。
asと違い型の情報のみを変換して、ビット列は同じ状態になるので f -> i32 みたいな変換をするとでかい値になったりする。
let c1 = 'a'; // let us1: i8 = unsafe { std::mem::transmute(c1) }; // バイトサイズが違うのでエラー let n1: i32 = unsafe { std::mem::transmute(c1) }; // こっちは可能 println!("{}", n1);
構文
use宣言
他のモジュールに定義されている要素を短く書ける。
例えば型名を取得する std::any::type_name
をuse宣言を利用することで any::type_name
と書ける。
use std::any; fn type_of<T>(_: T) -> String { let a = any::type_name::<T>(); return a.to_string(); }
関数
fn 関数名(引数: 型) -> 戻り値の型 { }
戻り値を書かない場合は強制的にunit型になる
メソッド
構造体に定義された関数
インスタンスに関連付けられている。
impl 型
のスコープ内で関数を定義する。
メソッドの場合必ず引数に self
が必要でこれがインスタンスになる。
#[derive(Debug)] struct Vector2 { x: f32, y: f32, } impl Vector2 { fn length(&self) -> f32 { (self.x * self.x + self.y * self.y).sqrt() } fn add_x(&mut self, value: f32) { self.x += value; } }
関連関数
これも構造体に定義された関数だが、メソッドと違い型に紐付けされている。
定義もメソッドと同じだが、self
を引数に取らなくすることで関連関数になる。
#[derive(Debug)] struct Vector2 { x: f32, y: f32, } impl Vector2 { fn length(&self) -> f32 { (self.x * self.x + self.y * self.y).sqrt() } fn add_x(&mut self, value: f32) { self.x += value; } fn new() -> Vector2 { Vector2 { x: 0.0, y: 0.0 } } }
分岐
if式
if a == 0 { println!("{} is zero", a); } else if a % 2 == 0 { println!("{} is even", a); } else { println!("{} is odd", a); }
rustでのifは式なので値を返すこともできる。
let a = 12; let result = if a == 0 { "even" } else { "odd" }; println!("{} is {}", a, result);
if let式
パターンを1つだけ書くことができる。
主にResult型やOption型などで使う。
let mut s = String::new(); io::stdin().read_line(&mut s).ok(); // parseの戻り値がOkならスコープ内で結果値が利用できる if let Ok(s_r) = s.trim().parse::<i32>() { let type_str = type_of(s_r); println!("{}: {}", s_r, type_str); a = s_r; }
match式
let mut a = 12; let mut s = String::new(); io::stdin().read_line(&mut s).ok(); if let Ok(s_r) = s.trim().parse::<i32>() { let type_str = type_of(s_r); println!("{}: {}", s_r, type_str); a = s_r; } match a { 1 => println!("one"), 10 => println!("ten"), _ => println!("some"), };
matchもif同様に値を返すことができる。
let m_result = match a { 1 => "a", 10 => "b", _ => "z", };
match式ではどのパターンにもマッチしないような結果があるとコンパイルエラーになる。
どのパターンにもマッチしない場合 _
を使って処理を行う。
繰り返し
loop式
終了条件のない無限ループとして動作する。
loopから抜ける場合 break
を使う。
loop式から値を返す場合breakキーワードの後に値を記述する。
loop { break 1; }
while式
条件付きのループ。
loop式と違い値を返すことができない。
返す値は常に()となる。
while true { }
while let式
パターンマッチを行い成功した時にループ本体を実行する。
let value = Some(10); while let Some(v) = value { println!("{}", v); }
for式
ベクタのコレクションなどに繰り返しができる。
let vector = vec!["one", "two", "three"]; for v in vector.iter() { println!("{}", v); }
クロージャ
Rustにおけるクロージャは無名関数になる。
let f = |引数リスト| { // 処理 };
これが無名関数の定義になる。
let mut base = 1; let add = |value| value + base; println!("{}", add(10)); // base = 5; // コンパイルエラー println!("{}", add(10));
このコードでコンパイルエラーになる原因は base を無名関数に渡しているため。
クロージャーにbase変数を貸している状態なので、baseを使おうとエラーになる。
貸すのではなくキーワードを使い値をコピーすれば回避ができる。
moveキーワードを使うと所有権がクロージャに移る。ただ、Copyトレイトを持つ型はコピーをしてくれる。
let mut base = 1; let add = move |value| value + base; println!("{}", add(10)); base = 5; println!("{}", add(10));
モジュール
mod
キーワードを使うことでモジュールごとに分割ができ、外部に公開・非公開ができる。
mod math { #[derive(Debug)] pub struct Vector { pub x: f32, pub y: f32, } impl Vector { pub fn length(&self) -> f32 { (self.x * self.x + self.y * self.y).sqrt() } } } fn main() { let v = math::Vector { x: 5.0, y: 5.0 }; println!("{:?} {}", v, v.length()); }
mod
の中でpub
とつけると公開のフィールドとなり、それ以外は非公開になる。
structのメンバ変数を非公開にするとフィールド名指定での初期化はエラーになる。
また、pub
キーワードはpub(公開先)
とすることで一部の場所に対してだけ公開にすることもできる。
mod hoge { pub(crate) fn echo() { println!("hoge.echo"); self::fuga::echo(); self::fuga::piyo::t(); } mod fuga { pub(super) fn echo() { println!("hoge.fuga.echo"); } pub(super) mod piyo { pub(in crate::hoge) fn t() { super::echo(); println!("hoge.fuga.piyo.echo"); } } } } fn main() { crate::hoge::echo(); }
- crate
ルートのモジュールを指す特別な名前
pub(crate)
とするとcrate::
でアクセスできる。 - super
親のモジュールを指す - self 現在のスコープのモジュールを指す
- in ~~
相対パスで指定して公開先の指摘ができる。
あまり使わなそうな気もする。
ファイル分割
モジュールは別のファイルに切り出すことができる。
src/new.rs というファイルをつくるとnew
モジュールとして扱う。
ディレクトリを使うことで階層を表現することができる。
同階層
// src/new.rs pub fn test() { println!("new.test"); } // src/main.rs mod new; fn main() { new::test(); }
main.rsと同じディレクトリにソースがある場合 mod ファイル名
で使うことができる。
子階層
サブディレクトリにソースがある場合はちょっと手間がかかる。
ディレクトリ名と同じ名前のソースファイルを作り、そこにサブディレクトリに配置したモジュールの公開設定を行うひつようがある。
// src/nest/nestfile.rs pub fn test() { println!("nest.test"); } // src/nest.rs pub mod nestfile; // src/main.rs mod nest; fn main() { nest::nestfile::test(); }
また、use
を使うことで省略できる。
mod nest; use nest::nestfile; fn main() { nestfile::test(); }
Rust入門① 環境について
インストール
公式を見るのが良さそう
インストールをするとホームディレクトリ配下に .rustup
と .cargo
というディレクトリができる。
- .rustup
ツールチェインの本体
- .cargo
rustc, cargo, rustupなどのコマンドが格納されている
※windowsの場合先にVisual StudioのC++開発ツールのインストールをしてください
リンカーのインストール
をそれぞれ行う。
windowsはVisual Studioをインストールをすればついて来るので特別やることはない
インストールをして使えるようになるコマンドのバージョンを確認
rustup --version rustc --version cargo --version
更新
rustup update
で本体の更新ができる。
パッケージ作成
cargo new
コマンドを作ってパッケージを作ることができる。
パッケージの種類はバイナリとライブラリを作る2種類ある。
cargo new
コマンドを実行するときに指定することで、必要な設定をしてくれる。
バイナリとライブラリの指定
- バイナリ
ビルドをして、実行ファイルの出力をしてくれる。
cargo new --bin <パッケージ名>
この様なコマンドになる - ライブラリ
ライブラリファイルを出力してくれる。
cargo new --lib <パッケージ名>
この様なコマンドになる
例えばhelloworldとコンソールに出力するプロジェクトを作る場合
cargo new --bin helloworld
となる
ビルド
cargo build
でビルドができる。
ビルドをすると <package root>/target/debug
に成果物が出力される。
cargo build --release
とするとリリースビルドになり、出力先は<package root>/target/release
になる。
その他コマンド
- 実行
cargo run
で実行ができる。ビルドをしていなくても実行ができる - クリーン
cargo clean
で<package root>
配下にあるtargetディレクトリを削除する
開発環境
neovimならcocの拡張でcoc-rust-analyzerがあるのでこれを使うのがよさそう。
vscodeでも同様にrust-analyzerを利用するパッケージがある。(試してない)
共にrust-analyzerを利用してのコード補完をしてくれる。
rust-analyzerのインストール
coc-rust-analyzerをインストール後にneovimを立ち上げると rust-analyzer
が存在しないのでインストールをするかと聞かれるがインストールしてくれなかったので手動のインストール方法も書いておく
$ curl -L https://github.com/rust-analyzer/rust-analyzer/releases/latest/download/rust-analyzer-linux -o ~/.local/bin/rust-analyzer $ chmod +x ~/.local/bin/rust-analyzer
以上でrust-analyzerをインストールができる。
nvimの場合:CocConfig
で設定ファイルを開いてから rust-analyzer.serverPath
の指定を行う。
デバッガ
vscode
ターミナル
LLDB, GDBを利用する。
これらをインストールした状態でrustツールチェインに含まれる。rust-lldb, rust-gdbを使いデバッガを起動する。
※v1.46.0での注意
v1.46.0でrust-lldbを実行したら file specified in --source (-s) option doesn't exist:
というエラーがでた。
解決方 の手順で起動に成功。
パッケージを作るたびに
$ ln -s ~/repos/rust/src/etc/lldb_commands ./ $ ln -s ~/repos/rust/src/etc/lldb_providers.py ./ $ ln -s ~/repos/rust/src/etc/lldb_lookup.py ./ $ ln -s ~/repos/rust/src/etc/lldb_batchmode.py ./
これを行う。
命名規則
この命名規則に違反をしている場合コンパイルをした時に警告が表示される。
無視をしたい場合アトリビュートを利用して警告の抑制ができる。
#[allow(non_snake_case)]
関数、変数、モジュール#[allow(non_upper_came_globals)]
定数、グローバル変数#[allow(non_camel_case_types)]
ユーザー定義型、ジェネリックス、トレイト
コーディング規約
Rust Style Guideを参照
rustには rustfmt
というフォーマットの整形をしてくれるツールがあるのでこれを動かせば良い。