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. 1件だけ登録
    db.コレクション名.insertOne(jsonオブジェクト) を実行することでドキュメントの登録ができる。
    登録されるデータは引数に渡したjsonデータに自動付与される _id がついたデータになる。

  2. 複数件の登録
    db.コレクション名.insertMany([jsonオブジェクト]) で配列に含んでいるオブジェクトを一気に登録してくれる。

※注意
ドキュメントを登録するコレクション名を間違えるとそのコレクションを自動作成をしてドキュメントを登録するので気を名前には気をつける。

検索

  1. 全件検索
    db.コレクション名.find() で登録されている全件を出力してくれる。
    全件取得ではデフォルトで20件ずつしか取得できない。
    ただ、it を続けて入力すると次の20件を取得してくれる。

  2. フィールドを指定して検索
    ここでもfindを利用する。
    全件取得と違いfindの引数を渡すと個別の検索をしてくれる。
    db.コレクション名.find(jsonオブジェクト)
    引数に指定したオブジェクトと一致するドキュメントを出力してくれる。
    また、第2引数にオブジェクトを指定することで取得するフィールドの制限をすることができる。
    {フィールド: 0}で非表示, {フィールド: 1}で表示
    文字列等で部分一致の検索をしたい場合は検索条件のオブジェクトに正規表現を使うと可能
    正規表現なので、前方一致、後方一致など色々と条件を組むことができる。

  3. 配列を検索する
    配列のフィールドを出力する場合は第2引数を指定すればよいが検索をする場合、検索データを配列にすると完全一致での検索になる。 {"name" : "test", "skill" : [ "a", "b", "c" ]} このドキュメント登録があり、skillの一致で検索する場合
    db.test.find({skill:["a", "b", "c"]}) このような検索を組む必要がある。
    一部一致での検索をする場合オブジェクト型の検索データにすれば良い。
    db.test.find({"skill": "a"}) とすることで一部一致の検索が行える。

  4. オブジェクトの検索をする 配列では検索する場合完全一致だったが、オブジェクトの場合一部一致での検索が可能。
    親フィールド.フィールド で検索が行える。
    "name" : "test", "child" : { "a" : 1, "b" : 2 } } このドキュメント登録があり、これを検索する場合
    db.test.find({"child.a": 1}) とすることで検索ができる。
    ちなみにこれは配列のオブジェクトに対しても使える。

  5. 範囲指定 引数のオブジェクトの値としてオブジェクトを指定すると範囲指定での検索ができる
    {$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}})となる。

  6. and, or {$and: [検索条件]} でand検索ができる。
    orの場合$and -> $or に変えて {$or: [検索条件]} とすればよい。
    例えば no が 0 or 1のデータを検索したい場合
    db.test.find({$or:[{no: 0}, {no:1}]}) このような指定になる。

  7. フィールドの存在での検索 特定のが無いドキュメントを検索する場合
    {フィールド: {$exists: true or false}} で検索ができる
    trueの場合フィールドが存在するもの、falseの場合フィールドが存在しないもの

  8. データ型で検索 $typeを使うことでフィールドの型による検索ができる。
    {フィールド: {$type: "型"}} で検索を型による検索が行える。
    型の指定はダブルクォートで指定をするが、各型に割り振られた番号でも検索は可能。
    ただ、undefinedのしてはできないため存在しないフィールドの検索はこれでは行えない。

  9. 配列の要素数で検索 $sizeを利用
    {フィールド: {$size: 要素数}} で要素数の検索が行える。

  10. 配列の中に指定した値をもつ検索 $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. 1件だけ更新 db.コレクション名.updateOne({検索条件}, {$set: {更新内容}}) で更新ができる。
    検索条件に一致する1件だけ更新がされる。
    あくまで更新なので、フィールド削除は行えない。

  2. 複数件更新 db.コレクション名.updateMany({検索条件}, {$set: {更新内容}}) で複数件の更新ができる。
    updateOneと違い検索条件に一致する全てのドキュメントを更新する。

  3. ドキュメントを差し替える _id以外のフィールドを削除して全部差し替えることができる。
    db.コレクション名.replaceOne({検索条件}, {更新内容})
    updateOneと同様に検索条件に一致する1件だけ更新される。
    replaceManyは存在しない。

  4. 配列に要素を追加する
    updateOneで書いた更新内容を指定する時に "フィールド名.index" と指定することで配列の中身に対して更新を行える。   db.test.updateOne({"skill": "a"}, {$set: {"skill.3": "3"}})

  5. データ登録、または更新 updateOneなどで検索条件に一致しなかった場合に新たにコレクションを追加する指定ができる。
    db.コレクション名.updateOne({検索条件}, {$set: {更新内容}}, {upsert: true})
    第3引数に{upsert: true} と指定すると、update or insertの挙動になる。
    なお、追加されるコレクションは 検索条件と更新内容を合わせた内容になる。

削除

指定したドキュメントを削除する。 1. 1件だけ削除 db.コレクション名.deleteOne({検索条件})
updateOneと同様に検索条件を満たすドキュメントが複数あっても削除されるのは1件のみ

  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 StudioC++開発ツールのインストールをしてください

リンカーのインストール

をそれぞれ行う。
windowsVisual 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 というフォーマットの整形をしてくれるツールがあるのでこれを動かせば良い。

Deno入門


Denoとは?

  • 読み方「での」らしい
  • JavaScript、TypeScriptの動作環境
  • 作者はNode.jsと同じ人(ライアン・ダール)
  • Node.jsの後悔をもとに再設計をしたもの
  • JavaScriptのエンジンにはV8を採用

Node.jsとの違い


TypeScriptのビルドイン

Node.jsでTypeScriptを利用する場合npmで外部モジュールが必要だった


標準ライブラリで
async/awaitをサポート

非同期関数はコールバックではなくPromiseが返る


Secure Model

デフォルト起動でネットワーク、ファイルへのアクセスなどの外へのアクセスが行えない


ES Modulesに統一

requireが削除されモジュールの取り込み構文がimportに統一された
絶対パス相対パス、URLが利用できる


パッケージという概念の破棄

npmの代わりとなるものはなく、外部モジュールを利用する場合import文にurlを渡すことで利用できる


インストール方法

macOS/Linux

curl -fsSL https://deno.land/x/install/install.sh | sh  

実行すると $HOME/.deno に
バイナリがインストールされる
パスは手動で通す必要があるので注意

Windows

iwr https://deno.land/x/install/install.ps1 -useb | iex

まだ試してない


サンプルコードを動かす

const url = Deno.args[0];
const res = await fetch(url);

const body = new Uint8Array(await res.arrayBuffer());
await Deno.stdout.write(body);
deno run --allow-net sample1.ts https://google.co.jp

--allow-net がインターネットの利用許可
なしで実行するとPermissionDeniedと表示される


今までとちょっと変わった実行

deno run https://deno.land/std/examples/welcome.ts

こんな風にURLでソースを指定して
実行することもできる


その他便利なツール紹介


デバッガ

chromeの開発ウィンドウでデバッグが可能
ブレークポイントも使える!!

使う場合実行時に
--inspect-brk --inspect
をつけるだけ


コードフォーマット

TypeScript、JavaScript
コードフォーマッター付属

# カレントディレクトリ以下のコードをフォーマット
deno fmt
# ファイルを指定してのフォーマット
deno fmt myfile1.ts
# フォーマットに問題のあるファイルと場所の割り出し
deno fmt --check
`

nginxでhttps2設定

仕事の都合でローカルにhttp2を立てたのでその記録
ローカルで行う事を前提にしているので環境としてdockerを採用して証明書は自己証明書で進める。

nginxの用意

dockerでnginxのイメージからコンテナを用意

docker run -it -d --name http2 -p 80:80 -p 443:443 nginx

docker内で証明書作ったり設定変えたりするので-itを指定
httpsでの接続になるので443のポートフォワーディング設定をするが、最初はポート80番で受け付ける様になっているので確認のために80番も追加しておく

この状態でブラウザ等のhttp通信が行えるものでhttp://localhostに対してアクセスしてWelcome to nginx!が表示されればOK

f:id:YuukTsuchida:20180917234453p:plain

コンテナの立ち上げまで終わったらbashログインを設定を変更をしていく。

docker exec -it http2 bash

ただ、nginxのコンテナにはvi等普通はインストールされているものがいろいろないのでインストールしておく。

$ apt-get update
$ apt-get install vim
$ apt-get install less 
$ apt-get install openssl 

余計なパッケージインストールすることになるのであまりよくなさそうだが目的がhttp2設定なので気にしないでおく!!

nginxの設定ファイルを確認

nginx のグローバル設定は /etc/nginx/nginx.conf にある。
ひとまずこいつの中身を確認

$ less /etc/nginx/nginx.conf

# 以下 nginx.conf

user  nginx;            # nginxのプロセス起動ユーザー
worker_processes  1;    # 実行プロセス数の指定 auto とすると自動設定をしてくれる。 CPUのコア数と揃えるのが良さそう

# エラーログの指定 最初は出力先 その後にレベル
# レベルは debug, info, notice, warn, error, crit, alert, emerg がある
error_log  /var/log/nginx/error.log warn;   

# PIDが格納されるファイル 基本みることはなさそう
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;   # コネクション数
}

# httpモジュールの設定
http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

この中にルーティングの設定を書けばよいのだが、別の設定ファイルに逃がすことも可能になっている。(というかデフォルトでそうなっている)
include /etc/nginx/conf.d/*.conf; となっているのが該当の設定
/etc/nginx/conf.d/.conf をおいておけば読み込んで有効にしてくれるのでルート毎でファイルを作ってやるのが良いのかも

ルーティングの設定を確認

/etc/nginx/conf.d/ を見ると default.conf が存在している。(wllcome to nginx! を表示する設定がすでにある)
今度はこいつの中身を確認

less /etc/nginx/conf.d/default.conf

# 以下 default.conf
server {
    # ポートの指定
    # default_serverを指定すると他の全てにマッチしない場合に使われるサーバー
    listen       80;  
    server_name  localhost;     # ドメインの指定

    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

    # URI に対してどこをルートにするか
    # ただ、ディレクトリやファイルに対して適切なアクセス権を設定してやる必要がある
    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    // エラーページの設定
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

実際に設定をしてやる

中身の確認はしたので実際に設定をしていく

http2 の設定をするためには証明書が必要なので事故証明書で生成をする

$ openssl req -new -x509 -sha256 -newkey rsa:2048 -days 365 -nodes -out /etc/nginx/ssl/ssl.pem -keyout /etc/nginx/ssl/ssl.key

生成が終わったらsslの設定と同じ様な設定をする。
具体的には ssl_certificate ssl_certificate_key の追加を行い、listenにhttp2の設定をする。

server {
    listen       443 ssl http2 default_server;
    server_name  localhost;

    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

    ssl_certificate /etc/nginx/ssl/ssl.pem;
    ssl_certificate_key /etc/nginx/ssl/ssl.key;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #    root           html;
    #    fastcgi_pass   127.0.0.1:9000;
    #    fastcgi_index  index.php;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
    #    include        fastcgi_params;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #    deny  all;
    #}
}

default.confを書き換えたらnginxの再起動を行う

$ nginx -s reload

再起動あとにブラウザでアクセスをして開発ツールでプロトコルがh2になっていればOK。

とりあえず設定完了。
Dockerログインしたりしていろいろ作業しているのであまりやりたくないな(これならvagrantと変わらん)
Dockerfile覚えたらそっちでの設定方法も調べるか

シーンからGameObjectを検索して自動アタッチ

Unityを使っているとよくやるのがクラスの変数にシーン上にあるGameObjectをアタッチするという行為
GameObjectの数が少なければ簡単なんだけれど
* 階層が深い
* デザイナーから納品されたもの

などといった場合にはどこに目的のものがあるのか探したり、間違えて選択してしまうとインスペクタが変わって即座にアタッチできなかったりと少し手間がかかってしまう。

その手間を多少減らすためにAttributeを追加してシーン上から検索して自動でアタッチしてくれるようにEditor用の拡張を追加

機能紹介

こんな風に参照の右にボタンを追加して押せばシーンから該当のオブジェクトを検索してアタッチしてくれる。

[SerializeField, UniUtil.Attribute.FindObjects("GameObject")]
private GameObject obj;

この機能を使う場合変数のAttributeを追加してやるだけ。
Attributeの引数に指定したオブジェクトが検索条件となる。

ただ、この方法で配列を行おうとするとすべて同じオブジェクトをアタッチしてしまってとても使えない。

なので配列のときに利用できる特殊な指定方法として

[SerializeField, UniUtil.Attribute.FindObjects("GameObject ({0})")]
private List<GameObject> bbb;

このように記述することが可能になっている。
FindObjectsAttributeは配列のときには指定した検索条件にstring.Format("条件", index);を実行するためオブジェクト名に連番をつけておけばインデックスを考慮したオブジェクトをアタッチしてくれる。

開発メモ

この機能は実際とても簡単なコードで実現している。
単にPropertyDrawerでGameObject.Findを実行するだけだ。

ただ配列の時だけ少し特殊なことをやってる。

Editor上でFindObject

PropertyDrawerの引数にあるSerializedPropertyは配列全体ではなく要素の1つ分になっている。
配列のプロパティパスを見ると【変数名】.Array.data[【インデックス】]となっている。 現在のプロパティの親のプロパティさえ取得できれば親なのか判別できるため

37行目以降でおこなっているように.でパスを切って3以上の個数になる場合は配列の可能性があるのでチェックを行う。
配列なのか判別できればdata[【index】]正規表現を利用してインデックスを取得してstring.Format("条件", index);で検索条件を利用するだけだ。
string.Formatを利用しているので
* 001のような形式
* プレフィックス
などある程度のパターンはどうにかなる。 今後はこの拡張をして少しずつ更新して便利にしていこうかな。