Rust:テープとparsim JSONをダウンロードする

JSON形式のフィードをダウンロードし、解析され、フォーマットされた形式でコンソール上のノートのリストを表示する小さなプログラムを作成する方法を示します。







すべてが非常に簡潔なコードになりました。 どうやって? カットの下を見てください。







Rustをダウンロード



Rustをダウンロードする通常の方法は、 rustupを使用してコンピューターにダウンロードすることですrustup



配布リポジトリですでに利用可能かどうかを確認してください。







rustup



rustup



管理します。 使用しているRustのバージョンの変更、RLSなどの追加の開発ツールの管理、さまざまなプラットフォーム用の開発ツールのダウンロードが可能です。







Rustをインストールしたら、次のコマンドを入力します。







 rustup install stable
      
      





この例では、少なくともRustバージョン1.20を使用する必要があります。これにはいくつかの依存関係が必要になるためです。







私たちが持っているものを見てみましょう。







このコマンドがインストールされました:









rustup doc



rustup doc



して、ブラウザーでrustup doc



を表示します。







プロジェクトのセットアップ: cargo





cargo



はRustプロジェクトを管理します。 小さな実行可能ファイルをビルドするため、ライブラリではなくプログラムをビルドするようにcargo



に指示します。







 cargo init --bin readrust-cli
      
      





このコマンドはreadrust-cli



を作成します。







このディレクトリにあるものを見てみましょう:







 . |-- Cargo.toml |-- src |-- main.rs
      
      





プロジェクトは単純な構造になっていることに気付くでしょう。プロジェクトのコード( src/main.rs



)と( Cargo.toml



)のみが含まれています。 Cargo.toml



含まれるものを見てみましょう:







 [package] name = "readrust-cli" version = "0.1.0" authors = ["Florian Gilcher <florian.gilcher@asquera.de>"] [dependencies]
      
      





構成ファイルには現在、プロジェクトに関する説明情報が含まれています。 現時点では、 dependencies



セクションは空であり、 main.rs



はデフォルトで小さな「hello world」が含まれていることに注意してください。







実行:







 $ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs Running `target/debug/readrust-cli` Hello, world!
      
      





素晴らしい。 すべてが機能します。 rustc



自体がrustc



コンパイラーを起動し、プログラムをコンパイルしてから起動しました。 cargo



は、我々が行ったコードのすべての変更を検出し、それらを再コンパイルすることもできます。







さあ始めましょう!



事前に行動の計画を立てます。 コマンドラインインターフェイスを介して対話できるユーティリティを作成します。







一方、私たちは問題を解決し、あまり多くの仕事をしたくない。







必要なもの:







 *     * HTTP-    *  JSON    *       .
      
      





機能セットについては、開始するのに十分な柔軟性を少し追加します。









CLAP



- command line argument parser



表しcommand line argument parser





簡単でしたね。 CLAPには詳細なドキュメントがありますが、使用する機能はごくわずかです。







最初に、依存関係としてclap



パッケージを追加する必要があります。







これを行うには、 Cargo.toml



名前とバージョンを指定する必要があります。







 [dependencies] clap = "2.29"
      
      





cargo build



cargo build



を実行cargo build



と、プログラムでclap



がコンパイルされます。 CLAPを使用するには、ライブラリを使用していることをRustに伝える必要があります(Rustの用語ではcrate



)。 また、使用する型を名前空間に追加する必要があります。 CLAPには非常にユーザーフレンドリーなAPIがあり、必要なだけ深く設定することができます。







 extern crate clap; use clap::App; fn main() { let app = App::new("readrust") .version("0.1") .author("Florian G. <florian.gilcher@asquera.de>") .about("Reads readrust.net") .args_from_usage("-n, --number=[NUMBER] 'Only print the NUMBER most recent posts' -c, --count 'Show the count of posts'"); let matches = app.get_matches(); }
      
      





次のステップは、指示メッセージを受け取るために--help



オプションを指定してプログラムをコンパイルおよび実行することです。







 readrust 0.1 Florian G. <florian.gilcher@asquera.de> Reads readrust.net USAGE: readrust [FLAGS] [OPTIONS] FLAGS: -c, --count Show the count of posts -h, --help Prints help information -V, --version Prints version information OPTIONS: -n, --number <NUMBER> Only print the NUMBER most recent posts
      
      





いいね! いくつかの簡単な行があり、プログラムを使用するための本格的な指示が既にあります。







必要な情報を取得する



プログラムをテストするには、必要な資料が必要です。







これを次のシグネチャを持つ関数でラップします。







 fn get_feed() -> String { // implementation }
      
      





良いHTTPクライアントの1つはreqwest



です。 同じ著者からのhyper



もあります。 Hyperはより「低レベル」のライブラリですが、 reqwest



使用reqwest



と、「早くやろう」などのタスクを解決できreqwest









 [dependencies] reqwest = "0.8"
      
      





この関数は非常に簡単に実装されます。







 extern crate reqwest; pub static URL: &str = "http://readrust.net/rust2018/feed.json"; fn get_feed() -> String { let client = reqwest::Client::new(); let mut request = client.get(URL); let mut resp = request.send().unwrap(); assert!(resp.status().is_success()); resp.text().unwrap() }
      
      





これは、他のプログラミング言語で行う方法と非常に似ています。リクエストを行うことができるクライアントを作成します。







send()



呼び出すsend()



により、リクエストを作成してレスポンスを取得します。

応答でtext()



を呼び出すことにより、文字列として取得します。







mutという単語に注意してください。 Rustでは、可変変数はこの方法で宣言されます。 要求を送信して応答を受信することにより、内部状態を変更します

オブジェクトをリクエストするため、可変である必要があります。







最後に、 アンラップしてアサートします。

要求の送信または応答の読み取りは、失敗する可能性のある操作です。たとえば、接続が切断されます。







したがって、送信(要求を送信)およびtext



(応答を「読み取る」)は、 Result



オブジェクトを返します。







Rustは、返されたオブジェクトのコンテンツを分析し、

必要な措置を講じます。 unwrap



はパニックにつながります-プログラムは終了しますが、その前に使用済みのメモリを「消去」します。







エラーがなかった場合、必要な値を取得します。 サーバーが応答したという意味で要求は成功する可能性がありますが、HTTP戻りコードは200 SUCCESS



(内部サーバーエラー?)ではありません。







assert



は、失敗したリクエストからのレスポンスの内容を読み取れないようにします。







この場所の多くのスクリプト言語では、未処理の例外が発生し、同様の効果が得られます。







Rustには例外はありません -代わりにADTを使用します(HaskellのMaybe



など)。







トレーニング中にunwrap



に使用することを恐れないでください。







後で、他の手段の使用方法を学習します。







JSON解析: serde



接続



次に、JSONテープを解析する必要があります。

これを行うために、Rustには(se / des)シリアル化用のserde



ライブラリがあります。

serde



はJSONだけでなく、他の形式もサポートしています。

serde



また、

シリアライズ可能な型の割り当て、いわゆる「 derive



」。







このため、次の3つの依存関係を追加する必要があります。

serde



serde_derive



serde_json









 [dependencies] serde = "1.0" serde_derive = "1.0" serde_json = "1.0"
      
      





serde_json



は、2つの方法でJSONを処理する機能を提供します:文字列をJSONツリーに解析するか、 serde_json



予想される情報の構造を伝える。

2番目の方法は、はるかに高速で便利です。

feedの定義を見ると、主に3つのタイプがあることがわかります。









リボンと要素には作成者がいます。







コードを変更します。







 extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_json; #[derive(Debug, Deserialize, Serialize)] struct Author { name: String, url: String, }
      
      





この例をよりわかりやすくするために、URLを通常の文字列として提示しましたが、将来これを変更できます。 また、2つのフィールドを持つ単純なデータ構造を定義します。 これらのフィールドの名前は、JSONの対応するフィールドと一致します。 最も興味深いのは、 derive



の行にありderive









この構造に基づいて追加のコードを生成するようコンパイラーに指示します。









テープ内の要素を表すための構造を作成しましょう。







 #[derive(Debug, Deserialize, Serialize)] struct FeedItem { id: String, title: String, content_text: String, url: String, date_published: String, author: Author, }
      
      





これは、すでに設定した構造に似ています。 Author



タイプフィールドを含めるために構成を使用したことがわかります。







このタイプが必要な理由をより雄弁に示すため、タイプをFeedItem



と名付けました。







テープの種類がどのようになるか見てみましょう。







 #[derive(Debug, Deserialize, Serialize)] struct Feed { version: String, title: String, home_page_url: String, feed_url: String, description: String, author: Author, items: Vec<FeedItem>, }
      
      





リボン要素を含むベクターであるitems



フィールドを含めたことを除いて、ここには新しいものはありません。







Vec



は、何かのリストを表すためのRustの標準型です。 同じタイプのオブジェクトの任意のセットを含めることができます。







他の言語のジェネリックに慣れている人にとっては、この指定はすでによく知られています。







get_feed



String



代わりにFeed



を返すようにします:







 fn get_feed() -> Feed { let client = reqwest::Client::new(); let mut request = client.get(URL); let mut resp = request.send().unwrap(); assert!(resp.status().is_success()); let json = resp.text().unwrap(); serde_json::from_str(&json).unwrap() }
      
      





追加することは2つだけです。返されたテキストをjson



変数に割り当て、関数を呼び出してこの変数を解析します。







解析が失敗する可能性があるため、プログラムは誤った値を含む結果を返す場合があります。 関数が成功した場合、値を抽出するにはunwrap



を呼び出す必要があります。







get_feed関数で戻り値の型を変更した方法から、RustはJSONテキストをFeed



型の変数に変換する必要があることを発見しました。







json



正しいJSONが含まれていない場合、プログラムはエラーで終了します。そのため、readrust.netがテープのエンコード形式を変更すると、すぐにそれがわかります。







カウント



完成間近ですが、結果をユーザーに表示するためのコードをまだ作成していません。

正解-テープ内の要素の数をユーザーに表示するプログラムを教えます。







 fn print_count(feed: &Feed) { println!("Number of posts: {}", feed.items.len()); }
      
      





&



見てください:Rustは、引数を渡す2つの方法を提供するシステムプログラミング言語です。









所有権 -これは、呼び出し元のコードが送信されたオブジェクトにアクセスできなくなることを意味します(オブジェクトの所有権を転送します)。 あなたが所有する意味で、あなたはすべてをすることができます:それらを破壊し、それらを無視し、それらを使用します。







借用 -これは、オブジェクトを「見る」ことしかできないことを意味します。その後、オブジェクトをその所有者に戻す必要があります。 所有者は、プロパティを変更する許可を与える場合と与えない場合があります。 オブジェクトを変更する必要がなくなったため、不変のリンクからオブジェクトを借用します。







main



外観は次のとおりです。







 let matches = app.get_matches(); let feed = get_feed(); if matches.is_present("count") { print_count(&feed); }
      
      





gen_matches



は、プログラムに渡された引数を決定しました。







is_present



呼び出しにより、ユーザー--count



引数を--count



かどうか--count



ます。 ここで&



を使用して、参照によってオブジェクトを渡したいことをコンパイラに伝える必要があることに注意してください。







実行:







 [ skade readrust-cli ] cargo run -- --count Compiling readrust v0.1.0 (file:///Users/skade/Code/rust/readrust-cli) Finished dev [unoptimized + debuginfo] target(s) in 2.46 secs Running `target/debug/readrust --count` Number of posts: 82
      
      





フォーマットされた出力



ここで、画面に結果を表示するようにプログラムに教える必要があります。 prettytable



ライブラリを使用してテーブルを印刷することにしました







 [dependencies] prettytable-rs = "0.6"
      
      





ライブラリを使用する1つの例を見てみましょう。







 #[macro_use] extern crate prettytable; fn print_feed_table<'feed, I: Iterator<Item = &'feed FeedItem>>(items: I) { let mut table = prettytable::Table::new(); table.add_row(row!["Title", "Author", "Link"]); for item in items { let title = if item.title.len() >= 50 { &item.title[0..49] } else { &item.title }; table.add_row(row![title, item.author.name, item.url]); } table.printstd(); }
      
      





次の点に注意する価値があります。









Rust でvalueを返す式であるif



見てみましょう。

これは、 if



変数の評価結果にtitle



変数を割り当てることができることを意味します。 実行の可能性のある2つの分岐を見ると、文字&



が表示されます-これは「テイクアスライス」( slice )と呼ばれます 。 タイトルが長すぎる場合は、最初の50文字へのリンクを使用するため、コピーする必要はありません。 借用と借用の類似性は偶然ではありません。スライスを借用します。







これにより、署名は次のようになります。

fn print_feed_table<'feed, I: Iterator<Item = &'feed FeedItem>>(items: I)









関数はジェネリックで動作するため、 print_feed_table



実装に追加することにしました。 この関数は、 Iterator



を実装するオブジェクトを受け取り、借用した要素を提供します。







Iterator



が提供するエンティティは、 Item



と呼ばれ、タイプパラメーター、この場合はFeedItem



です。 最後に、 'feed









Rustは、すべてのリンクが何かを指していることをチェックします。リンクが指しているものは存在していなければなりません。







このセマンティクスは、関数シグネチャで表現されます。 要素へのリンクを返すために、これらの要素がメモリ内にあることを確認する必要があります。 大まかに言って、 <'feed, I: Iterator<Item = &'feed FeedItem>>



は、 'feed



間に存在する関数の外側に特定のエンティティがあることを意味し'feed









このエンティティは、借りる「アイテム」を提供します。 イテレータIを取得します。このイテレータは、要素を「実行」して、借りる要素を提供します。 寿命はこれらの比率を表します。







次のようになります。







  if matches.is_present("count") { print_count(&feed); } else { let iter = feed.items.iter(); if let Some(string) = matches.value_of("number") { let number = string.parse().unwrap(); print_feed_table(iter.take(number)) } else { print_feed_table(iter) } }
      
      





ここに、この特定の実装を選択した理由を示します。 --number



オプションのサポートを有効にするために、Iteratorを使用することにしました。







ユーザーが数値を指定した場合、その数値を文字列に変換します(もちろん、ランダムな文字列が渡されると失敗する場合があります)。







残りの要素のセットをTake



イテレーターに変換した後。 Take



は、元のイテレータからいくつかの要素を返し、実行を完了します。







すべて準備完了です! ここでソースコードを見つけることができます







次に何をする?



拡張可能なプログラムを作成しました。







たとえば、次を試すことができます。









まとめ



エラーがないかデータをチェックするプログラムを取得しました。

処理後のプログラム。







JSON



データは安全に解析され、エラーが検出されJSON











完全なソースコードを次に示します。







 extern crate clap; #[macro_use] extern crate prettytable; extern crate reqwest; extern crate serde; #[macro_use] extern crate serde_derive; extern crate serde_json; use clap::App; pub static URL: &str = "http://readrust.net/rust2018/feed.json"; #[derive(Debug, Deserialize, Serialize)] struct Author { name: String, url: String, } #[derive(Debug, Deserialize, Serialize)] struct FeedItem { id: String, title: String, content_text: String, url: String, date_published: String, author: Author, } #[derive(Debug, Deserialize, Serialize)] struct Feed { version: String, title: String, home_page_url: String, feed_url: String, description: String, author: Author, items: Vec<FeedItem>, } fn print_count(feed: &Feed) { println!("Number of posts: {}", feed.items.len()); } fn print_feed_table<'feed, I: Iterator<Item=&'feed FeedItem>>(items: I) { let mut table = prettytable::Table::new(); table.add_row(row!["Title", "Author", "Link"]); for item in items { let title = if item.title.len() >= 50 { &item.title[0..49] } else { &item.title }; table.add_row(row![title, item.author.name, item.url]); } table.printstd(); } fn get_feed() -> Feed { let client = reqwest::Client::new(); let mut request = client.get(URL); let mut resp = request.send().unwrap(); assert!(resp.status().is_success()); let json = resp.text().unwrap(); serde_json::from_str(&json).unwrap() } fn main() { let app = App::new("readrust") .version("0.1") .author("Florian G. <florian.gilcher@asquera.de>") .about("Reads readrust.net") .args_from_usage( "-n, --number=[NUMBER] 'Only print the NUMBER most recent posts' -c, --count 'Show the count of posts'", ); let matches = app.get_matches(); let feed = get_feed(); if matches.is_present("count") { print_count(&feed); } else { let iter = feed.items.iter(); if let Some(string) = matches.value_of("number") { let number = string.parse().unwrap(); print_feed_table(iter.take(number)) } else { print_feed_table(iter) } } }
      
      





この記事の翻訳、校正、編集に貢献してくれたRustycrateコミュニティの皆さんに感謝します。 つまり、born2lose、vitvakatu。







更新 :完全なソースコードを追加しました。








All Articles