RustでRESTサービスを作成します。 パート3:コンソールからデータベースを更新する

前のパートでは、データベース構成ファイルを解析して、そこから接続パラメーターを読み取りました。



次に、データベースの更新操作(レコードの作成、更新、削除、および対応するコマンドラインインターフェイス)を直接実装しましょう。



始めるために、プログラムの引数を見てみましょう。 そのインターフェースは次のようになります。



const HELP: &'static str = "Usage: phonebook COMMAND [ARG]... Commands: add NAME PHONE - create new record; del ID1 ID2... - delete record; edit ID - edit record; show - display all records; show STRING - display records which contain a given substring in the name; help - display this help.";
      
      





すでにいくつかの興味深い点があります。 constは定数を宣言し、使用場所で単に構築されるようにします。 したがって、メモリ内に独自のアドレスはありません-Cの#defineのように見えます。定数のタイプは常に指定する必要があります-この場合、少し恐ろしく見えるかもしれません。 & '静的str? これは何ですか



私の記憶が私に役立つなら、私たちはまだ明示的に指定されたライフタイムを見ていません。 したがって、これはリンク&strであり、& 'foo str。 通常、ライフタイムを明示的に示す必要はありません。 コンパイラはそれ自体を出力できます-つまり 'fooはただ落ちます。



また、「foo could be」barまたはそれ以外のもの-変数の名前にすぎないことにも注意してください。 この場合、次のように考えることができます。HELP:&strリンクの有効期間は「foo」で、静的です。



さて、「静的」について。 これは、プログラムの寿命と等しい寿命です。 この行はプログラムのイメージに直接埋め込まれているため、初期化や明示的な破棄は必要ありません。 したがって、プログラムの実行中は常に使用可能です。 「静的」について詳しくはこちらをご覧ください



したがって、常に使用可能な文字列定数を宣言しました。



そして、ここに引数を解析するためのコードがあります-いつものように、最初はその全体です。 次に、より詳細に検討します。



コマンドライン解析コード
  let args: Vec<String> = std::env::args().collect(); match args.get(1) { Some(text) => { match text.as_ref() { "add" => { if args.len() != 4 { panic!("Usage: phonebook add NAME PHONE"); } let r = db::insert(db, &args[2], &args[3]) .unwrap(); println!("{} rows affected", r); }, "del" => { if args.len() < 3 { panic!("Usage: phonebook del ID..."); } let ids: Vec<i32> = args[2..].iter() .map(|s| s.parse().unwrap()) .collect(); db::remove(db, &ids) .unwrap(); }, "edit" => { if args.len() != 5 { panic!("Usage: phonebook edit ID NAME PHONE"); } let id = args[2].parse().unwrap(); db::update(db, id, &args[3], &args[4]) .unwrap(); }, "show" => { if args.len() > 3 { panic!("Usage: phonebook show [SUBSTRING]"); } let s; if args.len() == 3 { s = args.get(2); } else { s = None; } let r = db::show(db, s.as_ref().map(|s| &s[..])).unwrap(); db::format(&r); }, "help" => { println!("{}", HELP); }, command @ _ => panic!( format!("Invalid command: {}", command)) } } None => panic!("No command supplied"), }
      
      







最初の行を見てみましょう:



  let args: Vec<_> = std::env::args().collect();
      
      





std :: env :: args()は、コマンドライン引数のイテレータを返すだけです。 なぜ静的配列ではなくイテレータなのですか? すべての引数が必要というわけではないかもしれませんが、潜在的に多くの引数があるかもしれません。 したがって、イテレータが使用されます-それは「遅延」です。 これはRustの精神に基づいています-必要のないものに対しては支払いません。



したがって、ここでは引数が少ないことを知っており、引数をインデックスから取得できる通常のベクトルを保持する方が簡単です。 .collect()を実行して、すべての要素を調べて特定のコレクションにアセンブルします。



どのコレクションですか? 微妙な点があります。 実際、 .collect()は、要素が配置されるコレクションのfrom_iter()メソッドを呼び出します。 結局、そのタイプを知る必要があります。 そのため、argsタイプを省略して次のように記述することはできません。



  let args = std::env::args().collect();
      
      





コンパイラが言うことは次のとおりです。



 main.rs:61:9: 61:13 error: unable to infer enough type information about `_`; type annotations or generic parameter binding required [E0282] main.rs:61 let args = std::env::args().collect(); ^~~~ main.rs:61:9: 61:13 help: run `rustc --explain E0282` to see a detailed explanation
      
      





ただし、型推論はその仕事をすることに注意してください。Vec<_>を型として指定するだけで十分です。ベクトルに含まれる型は、コンパイラがすでに知っています。 必要なコレクションを明確にするだけです。



さて、なぜこれらすべての困難なのでしょうか? 次に、たとえば、必要に応じて引数をリンクリスト(または他のコレクション)に収集できます。



  let args: std::collections::LinkedList<_> = std::env::args().collect();
      
      





from_iterを実装するコレクションのリストは、特性ドキュメントのページにあります。



次に見るもの:



  match args.get(1) {
      
      





.get()は、ベクター要素が存在する場合はOk(要素)を返し、それ以外の場合はNoneを返します。 これを使用して、ユーザーがコマンドを指定しなかった状況を検出します。



  } None => panic!("No command supplied"), }
      
      





コマンドが定義済みのコマンドのいずれとも一致しない場合、エラーが表示されます。



  command @ _ => panic!( format!("Invalid command: {}", command))
      
      





テキストの任意の値についてこのブランチに入ります。したがって、_、「任意の値」がこのブランチの値として使用されます。 ただし、この非常に誤ったコマンドを出力したいので、コマンド@ _コンストラクトを使用して、一致コマンドを名前コマンドに関連付けます。 この構文の詳細については、 こちらこちらをご覧ください。



さらなる分析は次のようになります。



  Some(text) => { match text.as_ref() { "add" => { // handle add },
      
      





チームがある場合、Some(text)ブランチに分類されます。 次に、チーム名を一致させるために再度一致を使用します-ご覧のとおり、一致はかなり普遍的です。



コマンドはほとんど同じなので、最も興味深いものを見てみましょう:削除。 削除する必要があるレコード識別子のリストを受け入れます。



  "del" => { if args.len() < 3 { panic!("Usage: phonebook del ID..."); } let ids: Vec<i32> = args[2..].iter() .map(|s| s.parse().unwrap()) .collect(); db::remove(db, &ids) .unwrap(); },
      
      





まず識別子が必要です。次のようにコマンドライン引数から識別子を取得します。



  let ids: Vec<i32> = args[2..].iter() .map(|s| s.parse().unwrap()) .collect();
      
      





foo:Vec <_> = ... .collect()はすでによく知っています。 この行の中で何が起こっているのかを理解することは残っています。



args [2 ..]は、ベクターのスライスを取得します-3番目の要素からベクターの最後まで。 Pythonのスライスのように見えます。



.iter()は、このスライスのイテレータを取得し、これに.map()を使用して匿名関数を適用します。



  .map(|s| s.parse().unwrap())
      
      





匿名関数は単一の引数-s-を取り、整数として解析します。 彼女はこれが全体でなければならないことをどのように知っていますか? ここから:



  let ids: Vec<i32> =
      
      





(実際、ここからでもなく、db :: remove関数のシグネチャから-スライスと[i32]が必要です。型推論はこの情報を使用して、 FromStr :: from_strをi32で呼び出す必要があることを理解します。 Vec <_>を使用するためにここにいる可能性があります-ただし、コードを文書化するために、タイプを明示的に指定しました。db:: remove自体について-以下。)



一般に、.map()のようなイテレーターアダプターの使用は、Rustコードの一般的なパターンです。 それは、最も頻繁に必要とされる実行の制御された遅延を取得することを可能にします-データを読み込んでストリーミングするとき。



素晴らしい、準備作業はすべて完了しました。 データベース自体を更新するために残ります。 挿入は本当に退屈に見えます 。 removeを見てみましょう。



ところで、なぜdb :: removeと書かれているのですか? 別のモジュールにあるためです。 ファイルレベルでは、これは別のソースにあることを意味します:src /db.rs。 このモジュールはメインファイルにどのように含まれていますか? このように:



 mod db;
      
      





シンプル! この命令は、モジュールのソースコード全体を、記述されている場所に挿入することと同じです。 (しかし、実際にはこれは起こりません。プリプロセッサではありません。コンテナ全体が一度にコンパイルされるため、コンパイラはモジュールをメモリに読み込んで、ソースコードをテキストとしてコピーするのではなく、中間表現レベルでリンクを確立できます。)コンパイラがファイルsrc / db.rsおよびsrc / db / mod.rsでモジュールを探すこと-これにより、モジュールの階層をきれいに整理できます。



次に、関数のコード:



 pub fn remove(db: Connection, ids: &[i32]) -> ::postgres::Result<u64> { let stmt = db.prepare("DELETE FROM phonebook WHERE id=$1").unwrap(); for id in ids { try!(stmt.execute(&[id])); } Ok(0) }
      
      





だから、ここではほとんどすべてを知っています。 順番に。



pubは、モジュールの外部から機能にアクセスできることを意味します。 そうしないと、mainから呼び出すことができません。 デフォルトでは、モジュール内のすべての関数は非表示になっています。



 main.rs:81:21: 81:31 error: function `remove` is private main.rs:81 db::remove(db, &ids) ^~~~~~~~~~
      
      





戻り値の型は奇妙に見えます。 :: postgres ::結果?



2つのコロンは、現在のモジュールからではなく、コンテナのルートからpostgresモジュールを検索する必要があることを意味します。 このモジュールは、外部クレートpostgresを実行すると、main.rsで自動的に宣言されます。 ただし、db.rsで自動的に表示されるわけではありません! したがって、:: postgresを使用して名前空間のルートに移動します。 db.rsのpostgresコンテナーのバインディングを再要求することもできますが、これは良い習慣とは見なされません-すべてのバインディング要求が1か所にあり、残りのモジュールがメインのもので利用可能なものを使用する方が良いです。



さて、モジュールで少し整理しました。 詳細はこちらをご覧ください。



次に、前例のないマクロがあります: try!







彼は、彼の名前が示すように、ある種の操作を実行しようとしています。 成功した場合、try!()の値はOk(_)に埋め込まれた値になります。 そうでない場合は、Err(エラー)を返すのと同様の処理を行います。 これは定数.unwrap()の代替です。エラーが発生してもプログラムはパニックに陥ることはありませんが、呼び出し側の関数による処理のためにエラーを返します。



このマクロは、それ自体がResultを返す関数で使用できます。そうしないと、マクロはErrを返すことができません。 戻り値の型と戻り値の型が一致しません。



すべてを削除します。 次に、残りの操作を選択的に実行して、まだわかっていないことを説明します。



ここで、たとえば、トランザクションの処理方法:



 { let tx: ::postgres::Transaction = db.transaction().unwrap(); tx.execute( "UPDATE phonebook SET name = $1, phone = $2 WHERE id = $3", &[&name, &phone, &id]).unwrap(); tx.set_commit(); }
      
      





ご覧のとおり、これはRAIIの典型的なアプリケーションです。 txをどこにも転送しないだけで、ブロックから出ると破棄されます。 デストラクタを実装すると、成功フラグに応じてトランザクションが保存またはロールバックされます。 tx.set_commit()を実行していない場合、txデストラクタはそれをロールバックします。



画面に出力せずに文字列をフォーマットする方法は次のとおりです。



  Some(s) => format!("WHERE name LIKE '%{}%'", s),
      
      





ベクトルを作成すると、メモリを割り当てる要素の数をすぐに示すことができます。



  let mut results = Vec::with_capacity(size);
      
      





そして最後に、機能スタイルコードの別の例:



  let max = rs.iter().fold( 0, |acc, ref item| if item.name.len() > acc { item.name.len() } else { acc });
      
      





Ordトレイトが実装された型を比較す​​ると、このコードは簡単に記述できます。



  let max = rs.iter().max();
      
      





または、Recordにこの特性を実装できます。 これには、PartialOrdとEq、およびEqの部分的な実装が必要です。 したがって、実際には4つの特性を実装する必要があります。 幸いなことに、実装は簡単です。



特性の実装
 use std::cmp::Ordering; impl Ord for Record { fn cmp(&self, other: &Self) -> Ordering { self.name.len().cmp(&other.name.len()) } } impl PartialOrd for Record { fn partial_cmp(&self, other: &Self) -> Option<Ordering> { Some(self.name.len().cmp(&other.name.len())) } } impl Eq for Record { } impl PartialEq for Record { fn eq(&self, other: &Self) -> bool { self.id == other.id && self.name == other.name && self.phone == other.phone } } pub fn format(rs: &[Record]) { let max = rs.iter().max().unwrap(); for v in rs { println!("{:3} {:.*} {}", v.id, max.name.len(), v.name, v.phone); } }
      
      







そのような実装の意味が疑問視されていることは注目に値します-いずれかのフィールドの長さに沿ってデータベースレコードを比較する価値はまだほとんどありません。



ところで、Eqタイプはマーカータイプの1つの例です。メソッドの実装は必要ありませんが、特定のタイプのプロパティがあることをコンパイラに伝えるだけです。 これらのタイプの他の例は、送信と同期です。これについては、後で詳しく説明します。



今日はこれですべてです-投稿はシリーズの中で最も長いことが判明しました。



これでアプリケーションは実際に動作しますが、RESTインターフェースはまだありません。 次回Webパーツが登場します。



All Articles