JSON形式のフィードをダウンロードし、解析され、フォーマットされた形式でコンソール上のノートのリストを表示する小さなプログラムを作成する方法を示します。
すべてが非常に簡潔なコードになりました。 どうやって? カットの下を見てください。
Rustをダウンロード
Rustをダウンロードする通常の方法は、 rustupを使用してコンピューターにダウンロードすることです 。 rustup
配布リポジトリですでに利用可能かどうかを確認してください。
rustup
はrustup
管理します。 使用しているRustのバージョンの変更、RLSなどの追加の開発ツールの管理、さまざまなプラットフォーム用の開発ツールのダウンロードが可能です。
Rustをインストールしたら、次のコマンドを入力します。
rustup install stable
この例では、少なくともRustバージョン1.20を使用する必要があります。これにはいくつかの依存関係が必要になるためです。
私たちが持っているものを見てみましょう。
このコマンドがインストールされました:
- rustc-コンパイラ
- 貨物 -パッケージマネージャーおよびプロジェクトビルダー
- ドキュメント
- 他のツール。
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 * .
機能セットについては、開始するのに十分な柔軟性を少し追加します。
- プログラムは
--count
を受け入れ、ストリーム内のノートの数を表示するようプログラムに指示します - プログラムは
--number [NUMBER]
を受け取り、プログラムが出力するノートの数を設定します。
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
。
この構造に基づいて追加のコードを生成するようコンパイラーに指示します。
- デバッグはデバッグ文字列ビューを生成します。これは多くの場合便利です
- 生成コードをシリアル化/非シリアル化して、データ構造をシリアル化します。 これは、まだ使用する予定のJSON形式とは関係ありません。
テープ内の要素を表すための構造を作成しましょう。
#[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つの方法を提供するシステムプログラミング言語です。
- 所有権の譲渡
(pass ownership
、 所有 ) - リンク転送-借用(
borrow
、 borrowed )。
所有権 -これは、呼び出し元のコードが送信されたオブジェクトにアクセスできなくなることを意味します(オブジェクトの所有権を転送します)。 あなたが所有する意味で、あなたはすべてをすることができます:それらを破壊し、それらを無視し、それらを使用します。
借用 -これは、オブジェクトを「見る」ことしかできないことを意味します。その後、オブジェクトをその所有者に戻す必要があります。 所有者は、プロパティを変更する許可を与える場合と与えない場合があります。 オブジェクトを変更する必要がなくなったため、不変のリンクからオブジェクトを借用します。
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(); }
次の点に注意する価値があります。
- ループを使用して要素を調べ、
それらを引き出す - 一部の著者は、見出しを付けてメモを取ることを好みますが、
50文字を超える場合は、それらを短くします
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
は、元のイテレータからいくつかの要素を返し、実行を完了します。
すべて準備完了です! ここでソースコードを見つけることができます。
次に何をする?
拡張可能なプログラムを作成しました。
たとえば、次を試すことができます。
- フィードノート統計を計算する
- 見出しが正規表現に一致するノートのみを選択する
- より詳細なメッセージでエラー処理を改善
- 文字列(
String
)ではなく、対応する型の変数で日付を表します - リンクをたどり、メモの内容を取得します。
まとめ
エラーがないかデータをチェックするプログラムを取得しました。
処理後のプログラム。
JSON
データは安全に解析され、エラーが検出されJSON
。
このプログラムは、予期しない状況にうまく対処します。 たとえば、配列へのインデックス付きアクセスの代わりに反復を使用すると、配列外の要素へのアクセスから保護されます=> segfaultはありません。
このプログラムは、十分に文書化されたライブラリを使用して記述されているため、コードの記述にすぐに進むことができます。 Rustの図書館の状況は、たとえば2年前よりもはるかに良くなりました。すでに多くの成熟した図書館があります。
- プログラムは移植性があります( クロスプラットフォーム )=>実行可能ファイルをコピーするだけでプログラムを共有できます。
完全なソースコードを次に示します。
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。
更新 :完全なソースコードを追加しました。