tag:crieit.net,2005:https://crieit.net/tags/Rust/feed 「Rust」の記事 - Crieit Crieitでタグ「Rust」に投稿された最近の記事 2023-09-27T12:17:24+09:00 https://crieit.net/tags/Rust/feed tag:crieit.net,2005:PublicArticle/18579 2023-09-27T12:17:24+09:00 2023-09-27T12:17:24+09:00 https://crieit.net/posts/get-recording-time-using-ruby-and-rust 【Rust/Ruby】OBS Studioの録画時間をスクリプトから取得してみる <p>こんにちは、しきゆらです。<br /> 連休は出かけていてブログ書けませんでした。</p> <p>今回は、最近ちょっと触っているOBSをプログラムから操作したり情報を取得できるということを知ったので、サンプルを見ながら情報を取得してみようと思います。</p> <p>OBSのドキュメントは以下になります。 詳しく知りたい方はこの辺を参考にするとよいかなと思います。</p> <p><a target="_blank" rel="nofollow noopener" href="https://docs.obsproject.com/">Welcome to OBS Studio’s documentation! — OBS Studio 29.1.3 documentation https://docs.obsproject.com/</a></p> <p>上記を見るとC系での話が主なようですが、今回はよく使う言語であるRubyと、最近ちょっと触っているRustで書けることを目的として調べてみました。<br /> どうやらOBSはWebSocket経由で操作することができるようです。</p> <p>公式のリポジトリにもOBSのWebSocketプラグインがあるようで、v28以降のバージョンに対応しています。 手元の環境では、初期状態で入っていてちょっと設定してあげればすぐ使えるようになったので、初めはこの辺を触ってみるのが良いかなと思います。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/obsproject/obs-websocket">GitHub - obsproject/obs-websocket: Remote-control of OBS Studio through WebSocket https://github.com/obsproject/obs-websocket</a></p> <h2 id="Rubyから録画時間を取得する"><a href="#Ruby%E3%81%8B%E3%82%89%E9%8C%B2%E7%94%BB%E6%99%82%E9%96%93%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">Rubyから録画時間を取得する</a></h2> <p>上記を受けてRubyから操作するgemがないかな、と思って調べてみると非公式ながらありました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/onyx-and-iris/obsws-ruby">GitHub - onyx-and-iris/obsws-ruby: Ruby clients for OBS Studio WebSocket v5.0 https://github.com/onyx-and-iris/obsws-ruby</a></p> <p>まずは、RubyからOBSを操作するためにサンプルを見たり内部のスクリプトを見ながら録画時間を取得するコードを書いてみました。</p> <pre><code class="ruby">require "obsws" client = OBSWS::Requests::Client.new( host: "IPアドレスなど", port: ポート番号, password: "PWの文字列" ) loop do p client.get_record_status.output_timecode sleep 1 end </code></pre> <p>上記コードはシンプルに1秒ごとに録画時間を取得してターミナルへ表示するコードです。</p> <p>IPアドレスやポート番号などは、OBSの「ツール」>「WebSocketサーバ設定」から確認・設定可能です。</p> <p>実際にスクリプトを実行しつつOBSで録画すると、録画時間が表示されました。</p> <h2 id="Rustから録画時間を取得する"><a href="#Rust%E3%81%8B%E3%82%89%E9%8C%B2%E7%94%BB%E6%99%82%E9%96%93%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B">Rustから録画時間を取得する</a></h2> <p>では、同様のことをRustでやってみます。<br /> RustからOBSのWebSocketを操作するcrateは、obs-websocketにリンクがあったこちらを使ってみました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/dnaka91/obws">GitHub - dnaka91/obws: The obws (obvious) remote control library for OBS https://github.com/dnaka91/obws</a></p> <p>サンプルや内部コード、エラーを修正しつつできたのがこちら。</p> <pre><code class="rust">use std::{thread, time}; use anyhow::Result; use obws::{Client, client, requests::EventSubscription}; #[tokio::main] async fn main() -> Result<()> { let sleep_time = time::Duration::from_secs(1); let config = client::ConnectConfig { host: "IPアドレス", port: ポート番号, password: Some("PW文字列"), event_subscriptions: Some(EventSubscription::all()), broadcast_capacity: Some(100), }; let client = Client::connect_with_config(config).await?; loop { let outputs = client.recording(); let status = outputs.status().await?; println!("outputs: {}", status.timecode); thread::sleep(sleep_time) }; Ok(()) } </code></pre> <p>サンプルコードから動かしてみたのですが、諸々エラーだったりcrateが足りなかったりと動かず悪戦苦闘しながらここまで持っていきました。<br /> Rustのお作法をきちんと理解できていなので躓いただけかもですが、動く状態まで持っていけてよかったなと。</p> <p>これにて、Rustでも同様にターミナルに録画時間を表示することができました。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>今回は、OBS Studioの操作をスクリプトからできることを知ったのでRuby/Rustで簡単なサンプルを作ってみました。<br /> どちらもライブラリが用意されているので、そこまで難しくはありませんでした。<br /> また、Rustの勉強にもなったのでちょっと自信が付いたかもしれません。</p> <p>今回はここまで。</p> <p>おわり</p> しきゆら tag:crieit.net,2005:PublicArticle/18430 2023-05-11T12:46:43+09:00 2023-05-11T12:46:43+09:00 https://crieit.net/posts/Rust-const-generics Rustのconst genericsの引数によって型の定義を変える(っぽいこと) <p>若干釣りっぽいタイトルです</p> <h2 id="やりたいこと"><a href="#%E3%82%84%E3%82%8A%E3%81%9F%E3%81%84%E3%81%93%E3%81%A8">やりたいこと</a></h2> <p><code>Vector<T, const D: usize></code>のような要素型と次元数を受け取る<code>Vector</code>型があったとして</p> <pre><code class="rust">// x, yを持つ let v2 = Vector::<f32, 2> { x: 1.1, y: 2.2 }; // x, y, zを持つ let v3 = Vector::<f32, 3> { x: 1.1, y: 2.2, z: 3.3, }; // x, y, z, wを持つ let v4 = Vector::<f32, 4> { x: 1.1, y: 2.2, z: 3.3, w: 3.3, }; // 要素数5の配列を持つ let v5 = Vector::<f32, 5> { elements: [1.1, 2.2, 3.3, 4.4, 5.5], }; </code></pre> <p>のように<code>Vector::<T, 2></code>は<code>x</code>, <code>y</code>を持つ、<br /> <code>Vector::<T, 3></code>は<code>x</code>, <code>y</code>, <code>z</code>を持つ、<br /> <code>Vector::<T, 4></code>は<code>x</code>, <code>y</code>, <code>z</code>, <code>w</code>を持つ、<br /> <code>D >= 5</code>となる<code>Vector::<T, D></code>は配列<code>elements: [T; D]</code>を持つ、<br /> というような実装にしたい。</p> <h2 id="実装"><a href="#%E5%AE%9F%E8%A3%85">実装</a></h2> <p>実装全文。<br /> 重要なのは<code>VectorTypeHolder</code>と<code>VectorTypeResolver</code>です。</p> <pre><code class="rust">use std::marker::PhantomData; pub struct Vector2<T> { pub x: T, pub y: T, } pub struct Vector3<T> { pub x: T, pub y: T, pub z: T, pub w: T, } pub struct Vector4<T> { pub x: T, pub y: T, pub w: T, } pub struct ArrayWrapper<T, D> { elements: [T; D], } pub trait VectorTypeHolder<T, const D: usize> { type Vector; } pub struct VectorTypeResolver<T, const D: usize> { _marker: PhantomData<fn() -> [T; D]>, } impl<T> VectorTypeHolder<T, 2> for VectorTypeResolver<T, 2> { type Vector = Vector2<T>; } impl<T> VectorTypeHolder<T, 3> for VectorTypeResolver<T, 3> { type Vector = Vector3<T>; } impl<T> VectorTypeHolder<T, 4> for VectorTypeResolver<T, 4> { type Vector = Vector4<T>; } impl<T> VectorTypeHolder<T, 5> for VectorTypeResolver<T, N> { type Vector = ArrayWrapper<T, 5>; } pub type Vector<T, const D: usize> = <VectorTypeResolver<T, D> as VectorTypeHolder<T, D>>::Vector; </code></pre> <h2 id="解説"><a href="#%E8%A7%A3%E8%AA%AC">解説</a></h2> <p><code>Vector2</code>, <code>Vector3</code>, <code>Vector4</code>と配列をラップした<code>ArrayWrapper</code>はそれぞれ2~4次元の時に使用する型と5次元以上のときに使用する型です。<br /> 下記の<code>VectorTypeHolder</code>は要素型と次元数をgenerics引数として受けとり<code>Vector</code>という関連型を持つtraitです。</p> <pre><code class="rust">pub trait VectorTypeHolder<T, const D: usize> { type Vector; } </code></pre> <p>ここから<code>VectorTypeHolder</code>と型を取り出すために<code>VectorTypeResolver</code>という型を定義し、<code>VectorTypeResolver</code>に対して<code>VectorTypeHolder</code>を実装します。</p> <pre><code class="rust">pub struct VectorTypeResolver<T, const D: usize> { _marker: PhantomData<fn() -> [T; D]>, } </code></pre> <p><code>VectorTypeResolver</code>が持つ<code>_marker</code>はジェネリクス引数を消費するだけのものなので無視して大丈夫です。気になる方は<code>PhantomData</code>を調べてください。<br /> traitを実装するときconst genericsの値を指定して実装することができるため以下のようにDが2のときのみの実装を行うことができます。</p> <pre><code class="rust">impl<T> VectorTypeHolder<T, 2> for VectorTypeResolver<T, 2> { type Vector = Vector2<T>; } </code></pre> <p>同様に<code>VectorTypeHolder::Vector</code>がDが3,4のときに<code>Vector3</code>, <code>Vector4</code>を、Dが5のときは<code>ArrayWrapper<T, 5></code>となるように実装します。</p> <pre><code class="rust">impl<T> VectorTypeHolder<T, 2> for VectorTypeResolver<T, 2> { type Vector = Vector2<T>; } impl<T> VectorTypeHolder<T, 3> for VectorTypeResolver<T, 3> { type Vector = Vector3<T>; } impl<T> VectorTypeHolder<T, 4> for VectorTypeResolver<T, 4> { type Vector = Vector4<T>; } impl<T> VectorTypeHolder<T, 5> for VectorTypeResolver<T, 5> { type Vector = ArrayWrapper<T, 5>; } </code></pre> <p>最後に<code>Vector<T, D></code>として型を取得できるように</p> <pre><code class="rust">pub type Vector<T, const D: usize> = <VectorTypeResolver<T, D> as VectorTypeHolder<T, D>>::Vector; </code></pre> <p>と型エイリアスを定義します。</p> <p>ここまででで最初のやりたいことが実現できるようになりました。</p> <h2 id="Dが5より大きい場合"><a href="#D%E3%81%8C5%E3%82%88%E3%82%8A%E5%A4%A7%E3%81%8D%E3%81%84%E5%A0%B4%E5%90%88">Dが5より大きい場合</a></h2> <p>実はこの実装だとD > 5の場合にコンパイルエラーになります。<br /> それを解消するには</p> <pre><code class="rust">impl<T> VectorTypeHolder<T, 6> for VectorTypeResolver<T, 6> { type Vector = ArrayWrapper<T, 6>; } impl<T> VectorTypeHolder<T, 7> for VectorTypeResolver<T, 7> { type Vector = ArrayWrapper<T, 7>; } impl<T> VectorTypeHolder<T, 8> for VectorTypeResolver<T, 8> { type Vector = ArrayWrapper<T, 8>; } // // 以下D >= 9の実装が続く // </code></pre> <p>というように使用する分<code>VectorTypeHolder</code>の実装をする必要があります。<br /> <code>seq_macro</code>クレートの<code>seq</code>マクロを使用すると以下のようにまとめて実装できます。</p> <pre><code class="rust">// use seq_macro::seq; seq!(N in 5..256 { impl<T> VectorTypeHolder<T, N> for VectorTypeResolver<T, N> { type Vector = ArrayWrapper<T, N>; } }); </code></pre> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://recruit.cct-inc.co.jp/tecblog/rust/template-rust/">テンプレートの特殊化(Rust版)</a></p> block tag:crieit.net,2005:PublicArticle/17789 2021-11-27T13:51:05+09:00 2024-05-02T23:53:20+09:00 https://crieit.net/posts/etude-grpc-tonic tonicを使ってgRPCサーバーを立ててみる <p>gRPCサーバー+grpc-webをバックエンドにしてWiki的なのをつくろうという気持ちがあるので、そのための実験をしています。今回はgRPCサーバーをRustでつくれるかの調査です。</p> <h1 id="ライブラリ選定"><a href="#%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E9%81%B8%E5%AE%9A">ライブラリ選定</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://medium.com/nttlabs/rust-and-grpc-cb10a783c9da">Rustで、Hello Worldを卒業した?次は、gRPCだよね! | by FUJITA Tomonori | nttlabs | Medium</a> というRust教な記事があり、ここで <strong><a target="_blank" rel="nofollow noopener" href="https://github.com/hyperium/tonic">tonic</a></strong> が推されています。</p> <p>私は別にRust教信者ではないですが、Rustだけで完結していると環境構築が楽そうなので、とりあえずtonicを試してみることにしました。<a target="_blank" rel="nofollow noopener" href="https://lib.rs/search?q=grpc">‘grpc’ search // Lib.rs</a> でもtonicが一番人気っぽいですし、まあたぶんよくできているのでしょう。</p> <h1 id="Hello world"><a href="#Hello+world">Hello world</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/hyperium/tonic/blob/master/examples/helloworld-tutorial.md">tonic/helloworld-tutorial.md at master · hyperium/tonic</a>に従ってHelloWorldします。</p> <h2 id="環境準備"><a href="#%E7%92%B0%E5%A2%83%E6%BA%96%E5%82%99">環境準備</a></h2> <p>Rust 2021 Editionに依存しているのでRust 1.56以降が必要というのはなかなか面白いですね。</p> <p>エディタにはVSCode、補完にはrust-analyzerを使います。ここで注意点なのですが、READMEには</p> <blockquote> <p>If you're using rust-analyzer we recommend you set "rust-analyzer.cargo.loadOutDirsFromCheck": true to correctly load the generated code.</p> </blockquote> <p>とあるものの、この通りにやると "unknown configuration setting" とVSCodeに言われてしまいます。<a target="_blank" rel="nofollow noopener" href="https://github.com/rust-analyzer/rust-analyzer/issues/6448#issuecomment-961441435">rust-analyzerのIssue</a>によると、この <code>rust-analyzer.cargo.loadOutDirsFromCheck</code> 設定は <code>rust-analyzer.cargo.runBuildScripts</code> に改名された上に既定でオンになるようになったらしいです。つまり、特に何の設定もしなくてもよいということですね。</p> <h2 id="proto/sinanoki.proto"><a href="#proto%2Fsinanoki.proto">proto/sinanoki.proto</a></h2> <p>protocol bufferでAPI定義を書きます。なお、このsinanokiというのは単なる(私が作ろうとしているものの)プロジェクト名なので意味は無いです。</p> <pre><code>syntax = "proto3"; package sinanoki; service PageManager { rpc GetPage(GetPageRequest) returns (GetPageResponce); } message GetPageRequest { string path = 1; } message GetPageResponce { string markdown = 1; } </code></pre> <h2 id="cargo.toml"><a href="#cargo.toml">cargo.toml</a></h2> <pre><code class="toml">[package] name = "sinanoki-backend" version = "0.1.0" authors = ["suzusime <***@gmail.com>"] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] tonic = "0.6" prost = "0.9" tokio = { version = "1.0", features = ["macros", "rt-multi-thread"] } [build-dependencies] tonic-build = "0.6" </code></pre> <p>言われる通りにdependenciesの設定を追加します。今回はサーバーだけ(複数の実行ファイルはいらない)なので、binまわりの設定は飛ばしてserver.rsの代わりにmain.rsに書くことにします。</p> <h2 id="build.rs"><a href="#build.rs">build.rs</a></h2> <p>ビルドに対して特殊な処理が必要になるので、ルートフォルダにbuild.rsを置きます。</p> <pre><code class="rust">fn main() -> Result<(), Box<dyn std::error::Error>> { tonic_build::compile_protos("./proto/sinanoki.proto")?; Ok(()) } </code></pre> <h2 id="src/main.rs"><a href="#src%2Fmain.rs">src/main.rs</a></h2> <p>今までの設定をして<code>cargo build</code>すると、protocol bufferに書いたものに対応したサーバー/クライアントの雛型となるRustのコードがtarget以下に生成されるようになります。これを利用してサーバーを実装します。</p> <p>例えば私の場合は <code>target/debug/build/sinanoki-backend-1664b6d9540c1c9f/out/sinanoki.rs</code> に雛型コードが吐かれました。この雛型コードの中に</p> <pre><code class="rust"> #[async_trait] pub trait PageManager: Send + Sync + 'static { async fn get_page( &self, request: tonic::Request<super::GetPageRequest>, ) -> Result<tonic::Response<super::GetPageResponce>, tonic::Status>; } </code></pre> <p>というのがあるので、これをコピペしたものを使い、以下のようなコードを<code>src/main.rs</code>に書きます。</p> <pre><code class="rust">use sinanoki::page_manager_server::{PageManager, PageManagerServer}; use sinanoki::{GetPageRequest, GetPageResponce}; use tonic::{transport::Server, Request, Response, Status}; pub mod sinanoki { tonic::include_proto!("sinanoki"); // The string specified here must match the proto package name } #[derive(Debug, Default)] pub struct MyPageManager {} #[tonic::async_trait] impl PageManager for MyPageManager { async fn get_page( &self, request: Request<GetPageRequest>, ) -> Result<Response<GetPageResponce>, Status> { println!("Got a request: {:?}", request); let reply = GetPageResponce { markdown: format!("you requested a file of {}", request.into_inner().path).into(), // We must use .into_inner() as the fields of gRPC requests and responses are private }; Ok(Response::new(reply)) } } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { let addr = "[::1]:50051".parse()?; let manager = MyPageManager::default(); Server::builder() .add_service(PageManagerServer::new(manager)) .serve(addr) .await?; Ok(()) } </code></pre> <h2 id="実行"><a href="#%E5%AE%9F%E8%A1%8C">実行</a></h2> <p>あとは</p> <pre><code>$ cargo run </code></pre> <p>して、</p> <pre><code>$ grpcurl -plaintext -import-path ./proto -proto ./proto/sinanoki.proto -d '{"path": "/index.md"}' localhost:50051 sinanoki.PageManager/GetPage { "markdown": "you requested a file of /index.md" } </code></pre> <p>で動作が確認できました。</p> <h1 id="所感"><a href="#%E6%89%80%E6%84%9F">所感</a></h1> <p>どうせ何かハマるだろうと思っていましたが、すんなり動きました。</p> <p>困ったところを挙げるならば、(tonicに限らず)Rustの補完があまり気持ちよく動いてくれないことでしょうか。単にバグっぽいところ(implしようとしたときに勝手に出してくれるコードが明らかにおかしい)を除いても適切な補完候補がなかなか出てくれなくて、少し厳しいです。おそらく私の設定が悪いんだろうなと思っているのですが……。</p> すずしめ tag:crieit.net,2005:PublicArticle/17135 2021-05-16T21:45:14+09:00 2021-05-16T22:32:39+09:00 https://crieit.net/posts/M1-Mac-Rust M1 Mac上のRustでクロスコンパイルする <p>(自ブログの転載です)</p> <p>最近RustでCGIを書くなんてことをしていたのですが、諸事情でサーバー側にRustのコンパイラを入れられず、別のマシンでビルドしたバイナリをコピーして動かしました。その時はLinuxの別マシンでビルドしたのですが、開発機であるM1 Macでビルドできた方が楽なので、その方法を調べました。</p> <h2 id="クロスコンパイラをインストールする"><a href="#%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9%E3%82%92%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%99%E3%82%8B">クロスコンパイラをインストールする</a></h2> <p>まずは別のCPUを対象としてコンパイルするためのコンパイラ(クロスコンパイラ)をインストールする必要があります。今回は <a target="_blank" rel="nofollow noopener" href="https://github.com/messense/homebrew-macos-cross-toolchains">messense/homebrew-macos-cross-toolchains</a> を使わせてもらいます。Homebrewでもインストールできるようですが、<a target="_blank" rel="nofollow noopener" href="https://github.com/messense/homebrew-macos-cross-toolchains/releases/">Release</a> にあるビルド済みバイナリを用いるとお手軽です<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>。</p> <p>Releaseのページにはたくさんのtar.gzが並んでいるので、目的にあったバイナリをダウンロードします。私の場合は「M1 Mac (aarch64-darwin)上で」「x86_64のLinuxで動く、CライブラリとしてMUSLを使ったバイナリ(x86_64-unknown-linux-musl)を作る」ため、<code>x86_64-unknown-linux-musl-aarch64-darwin.tar.gz</code>をダウンロードしました<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>。</p> <p>そして、これを解凍し、中の<code>bin</code>ディレクトリにパスを通します。</p> <p>現時点(2021/05/16)での最新版(v10.3.0)を使う場合、以上のことをするコマンドは次の通りです。</p> <pre><code class="sh">$ mkdir -p $HOME/tools && cd $HOME/tools # 適当なディレクトリ $ wget https://github.com/messense/homebrew-macos-cross-toolchains/releases/download/v10.3.0/x86_64-unknown-linux-musl-aarch64-darwin.tar.gz $ tar xf x86_64-unknown-linux-musl-aarch64-darwin.tar.gz # 解凍 $ export PATH="$HOME/tools/x86_64-unknown-linux-musl/bin:$PATH" # パスを通す $ x86_64-unknown-linux-musl-gcc # パスが通っているか確かめる x86_64-unknown-linux-musl-gcc: fatal error: no input files compilation terminated. </code></pre> <p>なお、永続的に設定したい場合は<code>export</code>は<code>.zshrc</code>なりのシェル設定ファイルに書いて下さい(以下同じ)。</p> <h2 id="Rustでクロスコンパイルできるようにする"><a href="#Rust%E3%81%A7%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB%E3%81%A7%E3%81%8D%E3%82%8B%E3%82%88%E3%81%86%E3%81%AB%E3%81%99%E3%82%8B">Rustでクロスコンパイルできるようにする</a></h2> <p>まず、Rust側でターゲットを追加します。</p> <pre><code class="sh">$ rustup target add x86_64-unknown-linux-musl </code></pre> <p>これだけではRustがクロスコンパイラを見つけてくれないため、<a target="_blank" rel="nofollow noopener" href="https://github.com/messense/homebrew-macos-cross-toolchains">messense/homebrew-macos-cross-toolchains</a>のREADMEに従って次のように環境変数を設定します。</p> <pre><code class="sh">$ export CC_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-gcc $ export CXX_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-g++ $ export AR_x86_64_unknown_linux_musl=x86_64-unknown-linux-musl-ar $ export CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=x86_64-unknown-linux-musl-gcc </code></pre> <h2 id="クロスコンパイルしてみる"><a href="#%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%AB%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">クロスコンパイルしてみる</a></h2> <p>以上で設定ができたはずなので、Hello worldプロジェクトで確かめてみます。</p> <pre><code class="sh">$ cargo new etude-cross $ cd etude-cross $ cargo build --target x86_64-unknown-linux-musl --release # クロスコンパイル $ file target/x86_64-unknown-linux-musl/release/etude-cross # ファイル形式を確かめる target/x86_64-unknown-linux-musl/release/etude-cross: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, with debug_info, not stripped </code></pre> <p>確かにELF 64-bitのバイナリができあがりました。</p> <h2 id="参考文献"><a href="#%E5%8F%82%E8%80%83%E6%96%87%E7%8C%AE">参考文献</a></h2> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://omarkhawaja.com/cross-compiling-rust-from-macos-to-linux/">Cross-Compiling Rust from macOS To Linux</a> <ul> <li>この記事では <a target="_blank" rel="nofollow noopener" href="https://github.com/FiloSottile/homebrew-musl-cross">FiloSottile/homebrew-musl-cross</a> を使っていますが、これはM1 Macでは動かなかったので(<a target="_blank" rel="nofollow noopener" href="https://github.com/FiloSottile/homebrew-musl-cross/issues/23">Apple silicon support? · Issue #23</a>)、代わりに <a target="_blank" rel="nofollow noopener" href="https://github.com/messense/homebrew-macos-cross-toolchains">messense/homebrew-macos-cross-toolchains</a> を使いました。</li> </ul></li> <li><a target="_blank" rel="nofollow noopener" href="https://rust-lang.github.io/rustup/cross-compilation.html">Cross-compilation - The rustup book</a></li> </ul> <div class="footnotes" role="doc-endnotes"> <hr /> <ol> <li id="fn:1" role="doc-endnote"> <p>変なバイナリである可能性を排除できないのでセキュリティ的にはよろしくないかもしれませんが。 <a href="#fnref:1" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> <li id="fn:2" role="doc-endnote"> <p>例えばラズパイで動かすバイナリが欲しいなど、別のアーキテクチャ用のバイナリが欲しい場合は以下の文章の<code>x86_64-unknown-linux-musl</code>の部分を適宜読み替えてください。 <a href="#fnref:2" class="footnote-backref" role="doc-backlink">↩︎</a></p> </li> </ol> </div> すずしめ tag:crieit.net,2005:PublicArticle/16829 2021-04-10T06:46:21+09:00 2021-04-10T06:47:07+09:00 https://crieit.net/posts/Rust-6070cb2d112f0 素朴な自作言語のコンパイラをRustに移植した <p><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/08/30/132314">移植一覧に戻る</a></p> <hr /> <p>やっつけなので汚いです。ライフゲームのコンパイルが通ったのでヨシ、というレベルの雑なものです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2-rust">https://github.com/sonota88/vm2gol-v2-rust</a></p> <h1 id="移植元"><a href="#%E7%A7%BB%E6%A4%8D%E5%85%83">移植元</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2">https://github.com/sonota88/vm2gol-v2</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/05/04/155425">Rubyで素朴な自作言語のコンパイラを作った</a></li> </ul> <p>ベースになっているバージョン: <a target="_blank" rel="nofollow noopener" href="https://github.com/sonota88/vm2gol-v2/tree/56">tag:56</a> のあたり</p> <h1 id="メモ"><a href="#%E3%83%A1%E3%83%A2">メモ</a></h1> <ul> <li>理解は後回しにして、かっこ悪い書き方でもいいのでとにかく完成まで持って行く……といういつも通りの方針で、時間をかけすぎないように。理解は後でゆっくり。まずは手を動かして慣れる。</li> <li><a target="_blank" rel="nofollow noopener" href="https://tourofrust.com/">Tour of Rust</a> <ul> <li>情報量が多すぎずよく整理されていて、一番最初にこれを読めばよかった</li> </ul></li> <li>借用とかムーブとか <ul> <li>このくらいのプログラムを書いて動かせる程度には分かってきた、はず。<br /> でもけっこう怪しい。</li> </ul></li> <li>ライフタイム <ul> <li>まだよく分かっていなくて、コンパイルが通らなかったらライフタイムが絡まない書き方にして回避したりしている</li> </ul></li> <li>連結リストが難しそうだったので<br /> <code>Vec<NodeId></code> で管理する方式にしてとりあえず回避</li> <li>レキサの入力文字列は最初に <code>Vec<char></code> にして使い回し <ul> <li><a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2020/09/06/043607">C版</a> や <a target="_blank" rel="nofollow noopener" href="https://memo88.hatenablog.com/entry/2021/01/07/235019">Zig版</a> のように単純なバイト列として扱ってもよかったが、<br /> せっかくなので UTF-8 文字列として扱ってみた</li> </ul></li> </ul> <h1 id="TODO"><a href="#TODO">TODO</a></h1> <p>気が向いたらあとで</p> <ul> <li>List にノードID ではなくノードを直接持たせる</li> <li>List のイテレータ対応</li> </ul> sonota486 tag:crieit.net,2005:PublicArticle/16645 2021-01-23T21:12:04+09:00 2021-01-23T21:12:04+09:00 https://crieit.net/posts/aws-lightsail-containers-rust-actix-web AWS Lightsail Containers に Actix web をデプロイする <h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/actix/actix-web">Actix web</a> で Web アプリケーションを作ったのですが、技術勉強も兼ねていたので、デプロイ先も今まで試したことがないものを試そうとしていました。そこで、日頃業務でも AWS を利用しているということもあり、去年末に発表された <a target="_blank" rel="nofollow noopener" href="https://aws.amazon.com/jp/about-aws/whats-new/2020/11/announcing-amazon-lightsail-containers/">AWS Lightsail Containers</a> をデプロイ先に採用しました。</p> <p>AWS Lightsail Containers へのデプロイ自体は非常に簡単でした。また、デプロイにあたり Rust の Docker イメージ作成のやり方も学べました。今回はそのあたりの手順をまとめる形で記事として書き残しておくことにしました。</p> <h1 id="Actix web の Docker イメージを作成する"><a href="#Actix+web+%E3%81%AE+Docker+%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">Actix web の Docker イメージを作成する</a></h1> <p>開発したアプリケーションでは React でフロントエンド開発をしていて、ビルドしたものを Actix web の public フォルダに配置する形で公開しています。そのため、下記の Dockerfile ではマルチステージビルドを利用しておりますが、本質的には <code>FROM rust:1.49</code> 以降の記述が Actix web に関するものとなります。</p> <pre><code class="Dockerfile"># React ビルド用のイメージ FROM node:14.15.4-alpine3.10 as client_builder ARG REACT_APP_API_URL ARG REACT_APP_GYAZO_AUTH_URL ARG REACT_APP_GA_UNIVERSAL_ID WORKDIR /client COPY ./client/package*.json . RUN yarn install ADD ./client . RUN yarn build # Actix web ビルド用のイメージ FROM rust:1.49 # Actix web にアクセスするためのポートを公開する EXPOSE 8080 # Actix web プロジェクトのフォルダをイメージに追加する WORKDIR /server ADD ./server . # プロジェクトフォルダ内で `cargo install` してビルドを生成する RUN cargo install --path . # 不要になったファイル群を削除する RUN ls | grep -v -E 'templates' | xargs rm -r # React ビルド用のイメージでビルドした内容を Actix web ビルド用イメージに追加する COPY --from=client_builder /client/build ./build RUN mkdir tmp # `cargo install` コマンドで生成したビルドを実行して Actix web を起動する # 下記のコマンド名称は Cargo.toml 内の [package.name] に準ずる CMD ["bloggimg-server"] </code></pre> <p>また、<strong>Docker ビルド時のオプション管理を楽にするため、Docker Compose を利用しました。単一の Docker イメージをビルドする際にも利用しておくことで、後々コンテナを追加して連携させたいときにも即座に対応できたりでオススメです。</strong></p> <pre><code class="yml"># docker-compose.yml # context に Actix web プロジェクトのパスを指定する # args に Docker ビルド時に利用したい ARGS の値を環境変数で設定する # image に Docker の &lt;イメージ名:タグ名&gt; を指定する (今回は Docker Hub にデプロイする想定) # env_file に開発/動作検証時に利用したい dotenv ファイルを指定する # ports にポートマッピングの設定を書く version: '3.8' services: app: build: context: ./ args: - REACT_APP_API_URL=${REACT_APP_API_URL} - REACT_APP_GYAZO_AUTH_URL=${REACT_APP_GYAZO_AUTH_URL} - REACT_APP_GA_UNIVERSAL_ID=${REACT_APP_GA_UNIVERSAL_ID} image: n1kaera/bloggimg:v1.0.0 env_file: - ./server/.env ports: - 8080:8080 </code></pre> <p>上記を自分の Actix web プロジェクトに応じて改変し <code>docker-compose up</code> して動作検証します。動作検証ができ次第、<code>docker-compose build</code> を実行して Docker イメージをビルドします。ビルドに成功したら次は Docker Hub にイメージを push します。</p> <h1 id="Docker Hub にビルドしたイメージを push する"><a href="#Docker+Hub+%E3%81%AB%E3%83%93%E3%83%AB%E3%83%89%E3%81%97%E3%81%9F%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8%E3%82%92+push+%E3%81%99%E3%82%8B">Docker Hub にビルドしたイメージを push する</a></h1> <p>今回は AWS Lightsail Containers で使用するイメージの管理に <a target="_blank" rel="nofollow noopener" href="https://hub.docker.com/">Docker Hub</a> を利用します。Docker Hub へ push する前に <code>docker login --username=<Docker Hub のユーザ名></code> コマンドで Docker Hub へのログインを済ませておきます。</p> <p>その後 <code>docker-compose push</code> コマンドで Docker イメージを Docker Hub に push します。</p> <p><img src="https://i.gyazo.com/06ce4ca43a26c73c227b9eb768f65685.png" alt="スクリーンショット 2021-01-23 20.37.39.png" /><br /> <strong>Docker Hub のページから、正常に Docker イメージが push できていそうか確認する</strong></p> <p>Docker イメージの push が成功していることを確認できたら、残りは AWS Console 上での作業になります。</p> <h1 id="AWS Console から Lightsail Containers Service を作成する"><a href="#AWS+Console+%E3%81%8B%E3%82%89+Lightsail+Containers+Service+%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B">AWS Console から Lightsail Containers Service を作成する</a></h1> <p>AWS Console にログイン後、<a target="_blank" rel="nofollow noopener" href="https://lightsail.aws.amazon.com/ls/webapp/home/containers">Lightsail サービス</a> を選択して Lightsail サービスのトップページへ遷移します。遷移したら Containers タブを選択し、<code>Create container services</code> ボタンから Container Service を作成します。</p> <p><img src="https://i.gyazo.com/42a8e6fa6224a6a52212cdb7461a8515.png" alt="スクリーンショット 2021-01-23 18.55.16.png" /><br /> <strong>AWS Console へログイン後 Lightsail のページに遷移して、Containers タブを選択する</strong></p> <p><img src="https://i.gyazo.com/25c1b55863c4d77249d3105ba7a97afd.png" alt="スクリーンショット 2021-01-23 18.57.06.png" /><br /> <strong>Containers タブを選択すると出てくる、<code>Create container services</code> ボタンをクリックする</strong></p> <p><code>Create container services</code> ボタンをクリックした遷移先の画面で、リージョンやキャパシティ (Micro であれば 3ヶ月間のみ無料で利用可能) 等を選択して、名称を入力します。今回は最初にデプロイのための準備をすでに済ませているので、Container Service を作成するついでにデプロイ設定も行います。</p> <p>デプロイ設定は <code>Set up your first deployment</code> の項目から行うことが可能です。</p> <p><img src="https://i.gyazo.com/e8e4ab4e3b29afaf6298f7eb75355c34.png" alt="スクリーンショット 2021-01-23 19.07.53.png" /><br /> <strong><code>Set up deployment</code> の部分をクリックして、デプロイの設定項目を表示する</strong></p> <p><img src="https://i.gyazo.com/da0fd3ce440f441082a367a0dfe350b6.png" alt="スクリーンショット 2021-01-23 19.12.14.png" /><br /> <strong>Docker Hub イメージを利用してデプロイする際に必要な設定項目を入力する</strong></p> <p><img src="https://i.gyazo.com/eefec162ec66adab937d5139f70763cc.png" alt="スクリーンショット 2021-01-23 19.16.16.png" /><br /> <strong>コンテナのヘルスチェックのための情報を入力する</strong></p> <p><img src="https://i.gyazo.com/0c532eb818754d554a61028f6a177dde.png" alt="スクリーンショット 2021-01-23 19.19.17.png" /><br /> <strong>すべての情報入力が完了したら <code>Create container services</code> ボタンをクリックする</strong></p> <p><img src="https://i.gyazo.com/102978a025c8babbba40886e1b551ae6.png" alt="スクリーンショット 2021-01-23 19.22.48.png" /><br /> <strong>遷移後の画面下部の <code>Deployment versions</code> からデプロイ状況の確認が行える</strong></p> <p><img src="https://i.gyazo.com/eaaf0b1f1d2b2d37807d49764cafa681.png" alt="スクリーンショット 2021-01-23 19.39.55.png" /><br /> <strong>正常にデプロイできていれば <code>Deployment versions</code> の項目が Active になる</strong></p> <p>デプロイが完了したら <code>Public domain</code> が発行されているはずなので、正常にアクセスして Web アプリケーションが利用できそうか確認します。<code>Public domain</code> は該当する Container Service のトップページから確認できます。</p> <p><img src="https://i.gyazo.com/9ee36e7f1018c47a9c12e51ebe6df6d0.png" alt="スクリーンショット 2021-01-23 19.48.23.png" /><br /> <strong>AWS Lightsail Containers のトップページにある <code>Public domain</code> から動作検証する</strong></p> <p><img src="https://i.gyazo.com/a381f52745c002ce47addf7721b7ec0b.png" alt="スクリーンショット 2021-01-23 19.51.27.png" /><br /> <strong>一通りの動作検証を行い、正常にデプロイできていそうか確認する</strong></p> <p>これで作業は完了です。新しい Docker イメージでデプロイし直したい場合は、<code>Deployments</code> タブの <code>Modify your deployment</code> リンクをクリックすれば可能です。</p> <h1 id="(おまけ) 独自ドメインで Container Service へアクセス可能にする"><a href="#%28%E3%81%8A%E3%81%BE%E3%81%91%29+%E7%8B%AC%E8%87%AA%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%81%A7+Container+Service+%E3%81%B8%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E5%8F%AF%E8%83%BD%E3%81%AB%E3%81%99%E3%82%8B">(おまけ) 独自ドメインで Container Service へアクセス可能にする</a></h1> <p>AWS Lightsail Containers では独自ドメインの紐付け及び、HTTPS 化も簡単に設定できます。<code>Custom domains</code> タブを選択した後、画面下部にある <code>Create certificate</code> リンクをクリックすることで設定画面を表示します。</p> <p><img src="https://i.gyazo.com/93019714418209872be82c396931beee.png" alt="スクリーンショット 2021-01-23 20.07.22.png" /><br /> <strong><code>Custom domain</code> タブをクリックしてから、<code>Create certificate</code> リンクをクリックする</strong></p> <p><img src="https://i.gyazo.com/b6cc81261f5d3bec4b236e04c0409372.png" alt="スクリーンショット 2021-01-23 20.10.22.png" /><br /> <strong>各種設定項目の入力が完了したら <code>Create</code> ボタンをクリックする</strong></p> <p><img src="https://i.gyazo.com/d8075a0e1eedeb1e518e7f0ae22554a1.png" alt="スクリーンショット 2021-01-23 20.16.02.png" /><br /> <strong>ドメイン検証のために、CNAME レコードの設定を求められるので各自で設定作業を行う</strong></p> <p><img src="https://i.gyazo.com/3aa4608c22ab83193d75b3e968d47b77.png" alt="スクリーンショット 2021-01-23 20.20.26.png" /><br /> <strong>正常に CNAME レコードを設定した後、しばらく経つと Status が Valid になる</strong></p> <p>上記まで確認したら、<code>Create certificate</code> で設定したドメインの CNAME レコードに <code>Public domain</code> の値を設定しておきます。設定内容が反映され次第、独自ドメインへアクセスすることで HTTPS 経由で Container Service へアクセスできるようになります。</p> <p><img src="https://i.gyazo.com/21d69cbc2370b588e01cfae43afa50ca.png" alt="スクリーンショット 2021-01-23 20.27.26.png" /><br /> <strong><code>Custom domains</code> で Container Service で起動しているサービスにアクセスできることを確認する</strong></p> <h1 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h1> <p>AWS Lightsail Containers を利用して Actix web プロジェクトをデプロイする手順について簡単にまとめてみました。便利ではあるものの、個人開発で利用する分には価格面及び性能面で Lightsail Instance のほうが良いなと現時点では感じてしまいました。</p> <p>しかし、日本リージョンが用意されていたりロードバランサーを備えていたり、簡単にスケールさせやすくかつ定額で利用可能なサービスであるというメリットを活かせる場面があれば有効活用できそうだなと感じました。</p> <h1 id="参考リンク"><a href="#%E5%8F%82%E8%80%83%E3%83%AA%E3%83%B3%E3%82%AF">参考リンク</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://aws.amazon.com/jp/blogs/news/lightsail-containers-an-easy-way-to-run-your-containers-in-the-cloud/">Lightsail コンテナ: クラウドでコンテナを実行する簡単な方法 | Amazon Web Services ブログ</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://hub.docker.com/_/rust">rust - Docker Hub</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://lightsail.aws.amazon.com/ls/docs/en_us/articles/amazon-lightsail-enabling-distribution-custom-domains">Enabling custom domains for your Amazon Lightsail distributions | Lightsail Documentation</a></li> </ul> nikaera tag:crieit.net,2005:PublicArticle/16644 2021-01-23T14:33:35+09:00 2021-01-23T14:33:35+09:00 https://crieit.net/posts/cookie-rust-actix-web Actix web で HttpOnly な Cookie を設定する <h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1> <p>最近 Rust を勉強するため、<a target="_blank" rel="nofollow noopener" href="https://github.com/actix/actix-web">Actix web</a> で <a target="_blank" rel="nofollow noopener" href="https://github.com/nikaera/bloggimg">Bloggimg</a> という Web アプリケーションを作りました。その際、セッション管理のために Cookie を利用したのですが、その際の手順及び設定方法についてまとめておきます。</p> <p>本記事では Rust や Actix web のインストール方法については説明しません。Mac であれば <code>brew install rustup</code> して <code>rustup-init</code> した後、<code>PATH</code> に <code>$HOME/.cargo/bin</code> を追加するだけで大丈夫なはずです。詳細なインストール手順については <a target="_blank" rel="nofollow noopener" href="https://www.rust-lang.org/tools/install">公式サイト</a> をご参照ください。</p> <p>開発環境については <a target="_blank" rel="nofollow noopener" href="https://marketplace.visualstudio.com/items?itemName=rust-lang.rust">VSCode の Rust Plugin</a> がオススメです。Rustup で Rust をインストールしている場合、設定から Rustup の PATH を <code>$HOME/.cargo/bin/rustup</code> にするだけで利用可能です。設定手順の詳細は<a target="_blank" rel="nofollow noopener" href="https://takoyaking.hatenablog.com/entry/2020/01/05/180000">こちら</a>をご参照ください。</p> <h1 id="動作環境"><a href="#%E5%8B%95%E4%BD%9C%E7%92%B0%E5%A2%83">動作環境</a></h1> <ul> <li>Mac mini (M1, 2020) <ul> <li>Rust 1.49</li> <li>Actix web 3</li> </ul></li> </ul> <h1 id="Actix web で Cookie をセットする"><a href="#Actix+web+%E3%81%A7+Cookie+%E3%82%92%E3%82%BB%E3%83%83%E3%83%88%E3%81%99%E3%82%8B">Actix web で Cookie をセットする</a></h1> <p>サーバー側で Cookie を設定するため、HTTP レスポンスヘッダーに <a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie">Set-Cookie</a> を含める形でセッション情報をクライアントへ渡します。その際、最低でも Cookie の属性に <code>HttpOnly</code> と <code>Secure</code>、<code>SameSite=Strict</code> は設定します。実際の Cookie を設定するための Actix web でのサンプルコードは下記になります。</p> <pre><code class="rust">use std::env; use actix_web::{App, HttpServer}; use actix_web::cookie::{Cookie, SameSite}; use actix_web::{get, web, Error, HttpRequest, HttpResponse}; use serde::{Deserialize}; /// Cookie に設定するキー /// 今回は cookie_test をキーとして使用する /// const KEY: &str = "cookie_test"; /// 存在していれば、HTTP Request ヘッダーから Cookie 文字列を取得する関数 /// /// # Arguments /// * `req` - actix_web::HttpRequest /// /// # Return value /// * Option<String> - key=value; key1=value1;~ のような Cookie の文字列 /// fn get_cookie_string_from_header(req: HttpRequest) -> Option<String> { let cookie_header = req.headers().get("cookie"); if let Some(v) = cookie_header { let cookie_string = v.to_str().unwrap(); return Some(String::from(cookie_string)); } return None; } /// 存在していれば、特定のキーで Cookie に設定された値を取得するための関数 /// /// # Arguments /// * `key` - Cookie から取り出したい値のキー /// * `cookie_string` - get_cookie_string_from_header 関数で取得した Cookie の文字列 /// /// # Return value /// * Option<String> - Cookie に設定されている値を取得する /// fn get_cookie_value(key: &str, cookie_string: String) -> Option<String> { // 取得した Cookie 文字列を ; で分割してループで回す let kv: Vec<&str> = cookie_string.split(';').collect(); for c in kv { // Cookie 文字列をパースして key で指定した値とマッチしたキーが存在するかチェックする match Cookie::parse(c) { Ok(kv) => { if key == kv.name() { // key で指定した値とマッチしたキーが存在していたら、その値を取得する return Some(String::from(kv.value())); } } Err(e) => { println!("cookie parse error. -> {}", e); } } } return None; } /// 特定のキーで環境変数から値を取得するための関数 /// /// # Arguments /// * `key` - 環境変数から取り出したい値のキー /// /// # Return value /// * String - 環境変数の値を文字列として取得する /// fn get_env(key: &str) -> String { match env::var(key) { Ok(value) => return value, Err(e) => println!("ENV: ERR {:?}", e), } return String::new(); } /// 環境変数に設定された HTTPS の値が 1 か判定する /// Cookie の属性に Secure を付与するか判定するのに使用する /// /// # Return value /// * bool - Secure 属性を付与するか判定するための真偽値 /// fn is_https() -> bool { return get_env("HTTPS") == "1"; } /// Cookie に設定する値を扱う HTTP Query の定義 #[derive(Deserialize)] pub struct CookieQuery { pub value: String, } /// Cookie を設定するために用意したルート /// /// # Example /// /// 例えば GET /cookie?value=test にアクセスした場合、 /// Cookie に cookie_test=test が設定されるようになる /// #[get("/cookie")] async fn set_cookie(query: web::Query<CookieQuery>) -> Result<HttpResponse, Error> { // 設定したい Cookie を作成する // その際に Secure, HttpOnly, SameSite=Strict 属性を付与する let cookie = Cookie::build(KEY, &query.value) .secure(is_https()) .http_only(true) .same_site(SameSite::Strict) .finish(); // 作成した Cookie を HTTP Response の Set-Cookie ヘッダーに含めることで、 // HTTP Response を受け取ったクライアントに Cookie をセットさせる return Ok(HttpResponse::Ok() .header("Set-Cookie", cookie.to_string()) .body("")); } /// KEY で指定した Cookie が存在すれば、その値を返却する /// KEY で指定した Cookie が存在しなければ、空の文字列を返却する #[get("/")] async fn index(req: HttpRequest) -> Result<HttpResponse, Error> { let cookie_string = get_cookie_string_from_header(req); if let Some(s) = cookie_string { if let Some(v) = get_cookie_value(KEY, s) { return Ok(HttpResponse::Ok().body(v)); } } return Ok(HttpResponse::Ok().body("")); } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .service(set_cookie) .service(index) }) .bind("0.0.0.0:8080")? .run() .await } </code></pre> <p>ザッとインラインコメントで説明していますが、<br /> 最も重要な <code>set_cookie</code> 関数について簡単に説明します。</p> <p>Actix web には <a target="_blank" rel="nofollow noopener" href="https://docs.rs/actix-web/3.3.2/actix_web/http/struct.Cookie.html"><code>Cookie</code> クラス</a>が存在します。この <code>Cookie</code> クラスは Cookie 文字列を生成したり、パースしたりするのに役立ちます。<code>set_cookie</code> 関数では、Cookie を生成するための関数 <a target="_blank" rel="nofollow noopener" href="https://docs.rs/actix-web/3.3.2/actix_web/http/struct.Cookie.html#method.build"><code>Cookie::build</code></a> を利用しています。</p> <p><code>Cookie::build</code> 関数を利用することで、メソッドチェインで Cookie の値や属性を設定できます。<strong>作成した Cookie は <code>to_string</code> 関数を使用することで文字列として出力できます。出力した Cookie 文字列を HTTP レスポンスヘッダーに <code>Set-Cookie</code> として設定すれば Cookie を設定できます。</strong></p> <h1 id="動作検証"><a href="#%E5%8B%95%E4%BD%9C%E6%A4%9C%E8%A8%BC">動作検証</a></h1> <p>今回用意した Actix web のサンプルコードには 2つのエンドポイントを用意しました。</p> <div class="table-responsive"><table> <thead> <tr> <th>URI</th> <th>説明</th> </tr> </thead> <tbody> <tr> <td><code>GET /cookie</code></td> <td><code>value</code> クエリで HttpOnly な Cookie を設定する</td> </tr> <tr> <td><code>GET /</code></td> <td><code>GET /cookie</code> で設定した Cookie を確認する</td> </tr> </tbody> </table></div> <p><code>cargo run</code> で Actix web のサンプルを起動した後に、ブラウザで <code>http://localhost:8080/cookie?value=sample</code> にアクセスしてみます。またその際に HTTP レスポンスヘッダーを確認したいため、<a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Learn/Common_questions/What_are_browser_developer_tools">開発者ツール</a>を開いておきます。</p> <p><img src="https://i.gyazo.com/9a1cb0cf73aa001b9ad3fdf7d8ae9966.png" alt="スクリーンショット 2021-01-23 13.12.27.png" /><br /> <strong>HTTP レスポンスヘッダーに Set-Cookie が含まれていることを確認する</strong></p> <p><code>Set-Cookie</code> が含まれていることが確認できたら正常に Cookie が設定されているか確認します。</p> <p><img src="https://i.gyazo.com/a6963e1d7ec82c57b3ae8d4978e1c116.png" alt="スクリーンショット 2021-01-23 13.26.52.png" /><br /> <strong>HTTP リクエストヘッダーの Cookie に <code>cookie_test=sample</code> が存在していることを確認する</strong></p> <p><img src="https://i.gyazo.com/282473c4c235d92233929f95c25ca899.png" alt="スクリーンショット 2021-01-23 13.32.00.png" /><br /> <strong>実際にブラウザーにも Cookie が正しく設定されているか、開発者ツールで確認する</strong></p> <p>正常に Cookie がセットされていることが確認できれば作業完了です。Cookie の属性に <code>Secure</code> を設定した場合の動作検証は、環境変数に <code>HTTPS=1</code> をセットして <code>cargo run</code> で可能です。</p> <h1 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h1> <p>Actix web で割と汎用的に使えそうな知識として Cookie の設定方法について、メモ的な記事を書いてみました。引き続き、Rust への理解を深めるために <a target="_blank" rel="nofollow noopener" href="https://github.com/nikaera/bloggimg">Bloggimg</a> の開発を進めながら学習を進めていきます 🧑‍🎓</p> <p><em>本記事の内容がセキュリティの観点から適切でない場合等はコメントでご指摘いただけますと幸いです。</em></p> <h1 id="参考リンク"><a href="#%E5%8F%82%E8%80%83%E3%83%AA%E3%83%B3%E3%82%AF">参考リンク</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.rust-lang.org/tools/install">Install Rust - Rust Programming Language</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://marketplace.visualstudio.com/items?itemName=rust-lang.rust">Rust - Visual Studio Marketplace</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://takoyaking.hatenablog.com/entry/2020/01/05/180000">VSCodeでRustインストールしたのに「Rustup not available」が出るとき (備忘録) - TAKOYAKING’s blog</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/HTTP/Headers/Set-Cookie">Set-Cookie - HTTP | MDN</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/actix/actix-web">actix/actix-web: Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust.</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.rs/actix-web/3.3.2/actix_web/http/struct.Cookie.html">actix_web::http::Cookie - Rust</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Learn/Common_questions/What_are_browser_developer_tools">ブラウザー開発者ツールとは? - ウェブ開発を学ぶ | MDN</a></li> </ul> nikaera tag:crieit.net,2005:PublicArticle/16612 2021-01-18T02:08:16+09:00 2021-01-18T02:08:16+09:00 https://crieit.net/posts/bloggimg-first-release 📔 ブログを書く用途に特化した Gyazo のツールを開発してみた <p><img src="https://i.gyazo.com/263820f97c341755faf69d9269471bf8.png" alt="Gyazo を技術記事を書く用途で使っているので専用の便利ツールを作ってみた" /><br /> <strong>Gyazo を技術記事を書く用途で使っているので専用の便利ツールを作ってみた</strong></p> <h1 id="はじめに"><a href="#%E3%81%AF%E3%81%98%E3%82%81%E3%81%AB">はじめに</a></h1> <p>いつもブログ記事に載せるキャプチャ画像の編集 & アップロード先として <a target="_blank" rel="nofollow noopener" href="https://gyazo.com/">Gyazo</a> を利用させていただいているのですが、日々使っている中で不満に感じる点もちょくちょく出てくるようになってきました。</p> <p>そのため、3連休を用いて <a target="_blank" rel="nofollow noopener" href="https://www.rust-lang.org/ja">Rust</a> の勉強がてら <a target="_blank" rel="nofollow noopener" href="https://www.bloggimg.net/">Bloggimg</a> というウェブアプリケーションを作ってみました。ソースコードは MIT ライセンスで <a target="_blank" rel="nofollow noopener" href="https://github.com/nikaera/bloggimg">GitHub のリポジトリ</a>にアップしております。<em>ちなみに最初は <code>Gyazo for Blog</code> という名称で開発をしていたため、本記事内のスクショには <code>Gyazo for Blog</code> という文字列が出てきますが、現在は <code>Bloggimg</code> という名称になっております。。</em></p> <p><strong><code>Bloggimg</code> を開発したのは、ブログ記事を書く際に利用する画像のアップロードから加工、マークダウンとして利用するまでのフローを最適化したかったからです。</strong> ブログ記事を書く際に、記事内で用いるスクショ画像の加工や、そのアップロードにすごく時間を取られてしまうなーと日頃から感じていたのでそれを解決したかったのです。✅</p> <p>開発中に得た知見等については別途技術記事として書いて残す予定です。</p> <h1 id="考えていたこと"><a href="#%E8%80%83%E3%81%88%E3%81%A6%E3%81%84%E3%81%9F%E3%81%93%E3%81%A8">考えていたこと</a></h1> <p>今回 Bloggimg の開発を行うに当たり、考えていた点は下記になります。</p> <ul> <li>画像の編集ツールは引き続き Gyazo に用意されているものを使う <ul> <li>既に最高に使いやすい 👑</li> </ul></li> <li>キャプチャ画像をアップロードする際に、<strong>自動的に特定のコレクションに紐付けるようにする</strong> <ul> <li>技術記事毎にコレクションを分けて管理しているため、技術記事を書いている最中にアップするキャプチャ画像は全て特定のコレクションにまとまっていて欲しい</li> </ul></li> <li>ワークスペースのようなツールを目指し、ブログを書く時だけに使える機能を開発する <ul> <li>例えば、ワンクリックで画像マークダウンの記述がコピーできたり、画像のアップロードをし直しやすくするため画像削除がお手軽に出来るよう削除ボタンに即アクセス出来るようにしたり...</li> </ul></li> </ul> <p>特にアップした画像を <strong>自動的に特定のコレクションに紐付けるようにする</strong> については本記事で紹介しているウェブアプリケーションを作成するキッカケとなった点なので外せない点でした。</p> <h1 id="使い方"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9">使い方</a></h1> <p>Bloggimg の使い方についてご紹介いたします。</p> <h2 id="ログインする"><a href="#%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E3%81%99%E3%82%8B">ログインする</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://www.bloggimg.net/">Bloggimg</a> を利用するためには、まず Gyazo アカウントでログインして頂く必要がございます。トップページの右上にあるログインボタンから Gyazo アカウントでログインします。</p> <p><img src="https://i.gyazo.com/b0e863464696ce778ca853d7fac56ab9.png" alt="スクリーンショット 2021-01-11 15.31.27.png" /><br /> <strong>1. トップページ右上に配置されたログインボタンから Gyazo アカウント認証を行う</strong></p> <p><img src="https://i.gyazo.com/1bd6c3212c8fa95196ec0eaaef94d4d7.png" alt="スクリーンショット 2021-01-11 15.35.59.png" /><br /> <strong>2. Gyazo アカウント認証が正常に完了したら、再度トップページを開く</strong></p> <p><img src="https://i.gyazo.com/110fe178f5f158153640ab25271d90a2.png" alt="スクリーンショット 2021-01-11 15.42.21.png" /><br /> <strong>3. トップページを開いた時に Gyazo にアップした直近の画像が確認できるはずです</strong></p> <h2 id="ログアウトする"><a href="#%E3%83%AD%E3%82%B0%E3%82%A2%E3%82%A6%E3%83%88%E3%81%99%E3%82%8B">ログアウトする</a></h2> <p>ウェブアプリケーションからログアウトするには、ログイン後にトップページ右上に表示される <code>ログアウト</code> ボタンをクリックすることでログアウトできます。</p> <p><img src="https://i.gyazo.com/e7391a84be6e2e22845e975383dd78b0.png" alt="スクリーンショット 2021-01-11 16.00.02.png" /><br /> <strong>ログイン後にトップページ右上に表示される <code>ログアウト</code> ボタンをクリックしてログアウトする</strong></p> <h2 id="画像ファイルをアップロードする"><a href="#%E7%94%BB%E5%83%8F%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E3%81%99%E3%82%8B">画像ファイルをアップロードする</a></h2> <p>画像は一枚でも複数枚でもアップロードすることが可能です。画像アップロードの方法としてドラッグ & ドロップとファイル選択ダイアログを用意しております。</p> <p><img src="https://i.gyazo.com/4e559abbc492b82b58349cd511f7987c.png" alt="スクリーンショット 2021-01-11 16.04.31.png" /><br /> <strong>画面中央の点線枠内に画像ファイルをドラッグ & ドロップするか、点線枠内をクリックしてファイル選択ダイアログから選択することで画像をアップロードできる</strong></p> <h2 id="画像ファイルをアップロードする際に自動でコレクションを紐付ける"><a href="#%E7%94%BB%E5%83%8F%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E3%81%99%E3%82%8B%E9%9A%9B%E3%81%AB%E8%87%AA%E5%8B%95%E3%81%A7%E3%82%B3%E3%83%AC%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E7%B4%90%E4%BB%98%E3%81%91%E3%82%8B">画像ファイルをアップロードする際に自動でコレクションを紐付ける</a></h2> <p>Gyazo トップページ左端にコレクションリストが表示されているので、画像を紐づけたいコレクションを選択します。新たにコレクションを作成する場合はコレクションリスト上部にある <code>コレクションを作成</code> ボタンをクリックします。</p> <p><img src="https://i.gyazo.com/b06be51cd5cde0e09733325e696c655a.png" alt="スクリーンショット 2021-01-11 16.37.30.png" /><br /> <strong>1. コレクションリストの中から画像を紐づけたいコレクションを選択する</strong></p> <p><img src="https://i.gyazo.com/7d3dec131a20b1a7f6d24d10bbe8c39e.png" alt="スクリーンショット 2021-01-11 16.42.16.png" /><br /> <strong>2. コレクションを選択後に遷移した先の URL 末尾のコレクション ID をコピーする</strong></p> <p><img src="https://i.gyazo.com/eb052b62aec6500c030e390c85172de8.png" alt="スクリーンショット 2021-01-11 16.45.46.png" /><br /> <strong>3. トップページの最上部に 2. で控えていたコレクション ID をペーストする</strong></p> <p>上記までのステップが完了し、正しくコレクション ID が入力できていれば、次回以降のファイルアップロード時に自動で指定したコレクションに画像が紐づくようになります。</p> <h2 id="アップロードした画像ファイルを編集する"><a href="#%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E3%81%97%E3%81%9F%E7%94%BB%E5%83%8F%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E7%B7%A8%E9%9B%86%E3%81%99%E3%82%8B">アップロードした画像ファイルを編集する</a></h2> <p>画像ファイルのアップロード時や <code>画像の再読み込み</code> ボタンをクリックすることで、最新 20件の画像リストを画面最下部にロードされます。画像リストの各項目ではプレビュー、編集、削除、マークダウンのコピーを行うことが可能です。</p> <p><img src="https://i.gyazo.com/1226a831420cd25992d8de1f4446c5ed.png" alt="スクリーンショット 2021-01-11 16.58.33.png" /><br /> <strong>画像リストの各項目の機能概要図</strong></p> <h3 id="プレビュー"><a href="#%E3%83%97%E3%83%AC%E3%83%93%E3%83%A5%E3%83%BC">プレビュー</a></h3> <p>サムネ画像をクリックすることで、Gyazo にアップした元画像をプレビューすることが可能です。サムネ画像では画像の判別がしにくい場合に詳細を確認するための機能となります。</p> <p><img src="https://i.gyazo.com/e8d3c1b2e3c4247fdcde156dfa18e343.png" alt="スクリーンショット 2021-01-11 17.04.28.png" /><br /> <strong>アップした画像の詳細を確認するためにプレビュー機能を利用する</strong></p> <h3 id="編集"><a href="#%E7%B7%A8%E9%9B%86">編集</a></h3> <p>編集は該当画像の Gyazo ページにて行えるように、タイトルをクリックすることで Gyazo ページを別タブで開きます。</p> <p><img src="https://i.gyazo.com/83ab2ec4c4c4e1251302aea840377870.png" alt="スクリーンショット 2021-01-11 17.07.37.png" /><br /> <strong>別タブで開いた Gyazo ページから画像の編集作業を行う</strong></p> <h3 id="削除"><a href="#%E5%89%8A%E9%99%A4">削除</a></h3> <p>画像の削除は <code>画像の削除</code> ボタンをクリックすることで、削除を行うための画面に遷移します。削除しようとしている画像で間違いないか確認後、削除を行うという手順になっております。</p> <p><img src="https://i.gyazo.com/a3e219b5efb8494103432b369ee99534.png" alt="スクリーンショット 2021-01-11 17.10.11.png" /><br /> <strong>Gyazo から選択した画像を削除する</strong></p> <h3 id="マークダウンのコピー"><a href="#%E3%83%9E%E3%83%BC%E3%82%AF%E3%83%80%E3%82%A6%E3%83%B3%E3%81%AE%E3%82%B3%E3%83%94%E3%83%BC">マークダウンのコピー</a></h3> <p><code>マークダウンをコピー</code> ボタンをクリックすることで、クリップボードにマークダウン形式で該当画像を表示するための記述をコピーすることができます。具体的には下記のような記述がコピーされます。</p> <p>ブログを書く先がマークダウン形式での記述に対応していれば、そのままペーストするだけで画像を表示することが可能です。</p> <pre><code class="md">![スクリーンショット 2021-01-11 17.10.11.png](https://i.gyazo.com/a3e219b5efb8494103432b369ee99534.png) </code></pre> <h1 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h1> <p>この記事を書くのにも実際に Bloggimg を用いましたが、個人的に今までよりも Gyazo でブログ記事内で利用する画像に関する作業効率は上がったように感じました。ブログを書くという用途に Gyazo を利用されている方のお役に立てれば幸いです。</p> <p>また、今後は下記の機能実装を進めていく予定です。</p> <ul> <li>画像アップ時に自動でアスペクト比を維持した状態で画像のリサイズを自動で行う機能</li> <li>画像アップ時のタイトルの接頭辞が指定できるようにする機能</li> <li>編集した画像が自動的にコレクションに紐づく機能 <ul> <li>心残りな点として編集した画像をコレクションに紐付ける機能は API でできなかったため、現在手動で行う必要があります。。Gyazo の API がコレクションの紐づけにも対応したら対応したいと考えています ✅</li> </ul></li> </ul> nikaera tag:crieit.net,2005:PublicArticle/15890 2020-05-08T23:54:31+09:00 2020-05-08T23:54:31+09:00 https://crieit.net/posts/Serde-1-Derive Serde入門 (1) Derive属性の裏側をちょっと覗く <h1 id="Serde入門"><a href="#Serde%E5%85%A5%E9%96%80">Serde入門</a></h1> <h2 id="趣旨"><a href="#%E8%B6%A3%E6%97%A8">趣旨</a></h2> <p>Bayardという全文検索エンジンがあり保存されているドキュメントはJSON形式で取得できる. serde_jsonを使えば良いのだが, serde_jsonではhierarchical_facetというデータが上手く変換できない.</p> <pre><code class="rust">use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize, Deserialize)] enum Episode { Jo, Ha, Q, } #[derive(Debug, Serialize, Deserialize)] struct Human { id: String, name: String, appears_in: Vec<Episode>, } fn main() { let ikari_shinji = Human { id: "1".to_owned(), name: "Ikari Shinji".to_owned(), appears_in: vec![Episode::Jo, Episode::Ha, Episode::Q], }; let ikari_shinji_str = serde_json::to_string(&ikari_shinji).expect("cant't convert into string"); println!("{}", ikari_shinji_str); // {"id":"1","name":"Ikari Shinji","appears_in":["Jo","Ha","Q"]} let ayanami_rei_str = " { \"id\": \"2\", \"name\": \"Ayanami Rei\", \"appears_in\": [\"/episode/Jo\", \"/episode/Ha\", \"/episode/Q\"] }"; let anayami_rei: Human = serde_json::from_str(&ayanami_rei_str).unwrap(); println!("{:?}", anayami_rei); } </code></pre> <p>のayanami_rei_strをdeserializeしたい. 実行するとパニックが起きる.</p> <pre><code class="bash">thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Error("unknown variant `/episode/Jo`, expected one of `Jo`, `Ha`, `Q`", line: 5, column: 36)', src/main.rs:34:30 </code></pre> <p>実はすごく簡単にこのエラーを消せるのですが, 今回はとりあえずserdeを概観し, Derive属性の裏側を覗き見てみましょう.</p> <h2 id="Serdeとは?"><a href="#Serde%E3%81%A8%E3%81%AF%3F">Serdeとは?</a></h2> <blockquote> <p>Serde is a framework for <strong>ser</strong>ializing and <strong>de</strong>serializing Rust data structures efficiently and generically.</p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/">Serde - Docs.rs</a></p> <h3 id="シリアライズとは?"><a href="#%E3%82%B7%E3%83%AA%E3%82%A2%E3%83%A9%E3%82%A4%E3%82%BA%E3%81%A8%E3%81%AF%3F">シリアライズとは?</a></h3> <p>直列化とか訳されたりします. 一般にデータの直列化とはバイト列や文字列への変換を指します. 通常Rustの構造体などはメモリ上に散らばって存在し, これらを参照することで一つの実体のように見せています. 対してバイト列や文字列はメモリ上に直線的な列を作って保存されています. また文字列にしておけばファイルに保存することができます. デシリアライズはその逆に直列化したデータを復元する作業です. こうすることで特定のデータをRustで読み込んで操作できるようになります.</p> <p>ここではRustのデータ構造を別のより一般的なデータ形式に変換する操作をシリアライズと定義することにします. デシリアライズはその逆です.</p> <h3 id="登場人物"><a href="#%E7%99%BB%E5%A0%B4%E4%BA%BA%E7%89%A9">登場人物</a></h3> <ul> <li>Rustデータ構造 (以下データ構造)</li> <li>Serdeデータ・モデル (以下データ・モデル)</li> <li>データ形式</li> </ul> <p>データ構造をデータ形式へと変換するのがSerdeの目的でした. 両者を仲介するAPIをデータ・モデルと呼んでいます.</p> <blockquote> <p>The Serde data model is the API by which data structures and data formats interact.</p> </blockquote> <p>つまりSerdeは二段階の処理を踏むわけです.</p> <p>データ構造 ↔️ データ・モデル ↔️ データ形式</p> <p>こんな感じで説明されるわけですがよく分かりませんよね.</p> <h3 id="データ形式"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E5%BD%A2%E5%BC%8F">データ形式</a></h3> <p>現在対応しているデータ形式は<a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/#data-formats">Data formats -Docs.rs</a>を参照してください.</p> <h2 id="Serdeの基本"><a href="#Serde%E3%81%AE%E5%9F%BA%E6%9C%AC">Serdeの基本</a></h2> <h3 id="Derive属性"><a href="#Derive%E5%B1%9E%E6%80%A7">Derive属性</a></h3> <p>基本的な使い方は簡単です. Derive属性の引数にトレイト指定することでSerialize/Deserializeトレイトをコンパイル時に実装してくれます.</p> <pre><code class="rust">use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize, Debug)] struct Point { x: i32, y: i32, } fn main() { let point = Point { x: 1, y: 2 }; let serialized = serde_json::to_string(&point).unwrap(); println!("serialized = {}", serialized); let deserialized: Point = serde_json::from_str(&serialized).unwrap(); println!("deserialized = {:?}", deserialized); } </code></pre> <p>この際Cargo.tomlでは,</p> <pre><code class="toml">[dependencies] serde = { version = "1.0", features = ["derive"] } </code></pre> <p>のようにserdeの追加とfeaturesでderiveを指定しておきます.</p> <h3 id="Serialize/Deserializeトレイト"><a href="#Serialize%2FDeserialize%E3%83%88%E3%83%AC%E3%82%A4%E3%83%88">Serialize/Deserializeトレイト</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://serde.rs/derive.html">Using Derive - Serde</a></p> <p>Serialize/Deserializeトレイトで実装すべき振る舞いはserializeとdeserializeだけです.</p> <pre><code class="rust">pub trait Serialize { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer; } pub trait Deserialize<'de>: Sized { fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> where D: Deserializer<'de>; } </code></pre> <p>SerializeとDeserializeでは微妙にシグニチャが違いますが, 引数にSerializer/Deserializerという如何にもな名前の型を取ります.</p> <p>どうやら具体的な変換方法はこれらに委譲されるようです. また具体的な変換処理はデータ形式で違ってきますので, Serializer/Deserializerトレイトを実装した型が必要になるわけです.</p> <p>プリミティブ型には全てデフォルトのSerialize/Deserialize実装が存在します.</p> <blockquote> <p>Serde provides such impls for all of Rust's primitive types so you are not responsible for implementing them yourself, ...</p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://serde.rs/impl-serialize.html#serializing-a-primitive">Serializing a primitive - Serde</a><br /> <a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/ser/trait.Serialize.html?search=#foreign-impls">Implementations on Foreign Types - Serialize</a><br /> <a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/de/trait.Deserialize.html#foreign-impls">Implementations on Foreign Types - Deserialize</a></p> <p>例えばstr型に対しては以下のようなトレイト実装が与えられます.</p> <pre><code class="rust">impl Serialize for str { #[inline] fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { serializer.serialize_str(self) } } </code></pre> <p>要するに以下のようなコードがあった時に,</p> <pre><code class="rust">#[derive(Serialize)] struct Human { name: String, age: u8, appears_in: Vec<String>, } </code></pre> <p>次のように展開されることになります.</p> <pre><code class="rust">use serde::ser::{Serialize, SerializeStruct, Serializer}; struct Human { name: String, age: u8, appears_in: Vec<String>, } // This is what #[derive(Serialize)] would generate. impl Serialize for Human { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let mut s = serializer.serialize_struct("Person", 3)?; s.serialize_field("name", &self.name)?; s.serialize_field("age", &self.age)?; s.serialize_field("phones", &self.appears_in)?; s.end() } } </code></pre> <p>これを実際にシリアライズすること考えてみましょう. Human構造体はSerializeトレイトを実装しているのでserializeメソッドが自動導出されています.</p> <pre><code class="rust">fn main() { let JO = String::from("Jo"); let HA = String::from("Ha"); let Q = String::from("Q"); let ikari_shinji = Human { name: "Ikari Shinji".to_owned(), age: 14, appears_in: vec![JO, HA, Q], }; // ikari_shinji.serialize(??); } </code></pre> <p>これでシリアライズはできるようになりましたが, 具体的な方法はserializerに委ねられます. この時点ではどのようなフォーマットで出力するのか定まっていないので当然と言えば当然ですが, 適切なSerializer型を定義することでikari_shinjiインスタンスをシリアライズできます.</p> <p><a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/ser/trait.Serialize.html?search=#required-methods">Required methods - serde::ser::Serialize</a></p> <h3 id="Serializerトレイト"><a href="#Serializer%E3%83%88%E3%83%AC%E3%82%A4%E3%83%88">Serializerトレイト</a></h3> <p>serializerはシリアライズ用のデータ・モデルでデータのシリアライズ用のAPIを提供します. 型に応じて<a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/trait.Serializer.html#required-methods">メソッド</a>が宣言されています. 例えば構造体なら<a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/ser/trait.Serialize.html#tymethod.serialize">serialize_struct</a>というメソッドをシリアライズの時に呼び出しています.</p> <pre><code class="rust">use serde::ser::{Serialize, SerializeStruct, Serializer}; struct Person { name: String, age: u8, phones: Vec<String>, } // This is what #[derive(Serialize)] would generate. impl Serialize for Person { fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer, { let mut s: SerializeStruct = serializer.serialize_struct("Person", 3)?; s.serialize_field("name", &self.name)?; s.serialize_field("age", &self.age)?; s.serialize_field("phones", &self.phones)?; s.end() } } </code></pre> <p>名前はSerde型の前に慣例的にserialize_というプレフィックスがつけられます.</p> <h3 id="Deserializerトレイト/Visitorトレイト"><a href="#Deserializer%E3%83%88%E3%83%AC%E3%82%A4%E3%83%88%2FVisitor%E3%83%88%E3%83%AC%E3%82%A4%E3%83%88">Deserializerトレイト/Visitorトレイト</a></h3> <p>DeserializeプロセスはSerializeとは少し違います. 単純にいろんな入力が想定される点が違います. そのためDeserializerは更に処理をVisitorに委譲します.</p> <p><a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/de/trait.Visitor.html">serde::de::Visitor</a></p> <p>説明が難しいので具体例としてTomlの場合は見てみましょう. <a target="_blank" rel="nofollow noopener" href="https://docs.rs/toml/0.5.6/toml/de/fn.from_str.html">toml::de::from_str</a>には以下のような例が載っています.</p> <pre><code class="rust">use serde_derive::Deserialize; #[derive(Deserialize)] struct Config { title: String, owner: Owner, } #[derive(Deserialize)] struct Owner { name: String, } fn main() { let config: Config = toml::from_str(r#" title = 'TOML Example' [owner] name = 'Lisa' "#).unwrap(); assert_eq!(config.title, "TOML Example"); assert_eq!(config.owner.name, "Lisa"); } </code></pre> <p>from_strはこの入力文字列をDeserializerに渡して, deserialize関数内部で処理を実行します.</p> <pre><code class="rust">pub fn from_str<'de, T>(s: &'de str) -> Result<T, Error> where T: de::Deserialize<'de>, { let mut d = Deserializer::new(s); let ret = T::deserialize(&mut d)?; d.end()?; Ok(ret) } </code></pre> <p>さて元の例をcargo-expandで展開してみましょう. いろいろコードが増えますが, 実際の呼び出し場所は以下のようになります.</p> <pre><code class="rust">_serde::Deserializer::deserialize_struct( __deserializer, "Config", FIELDS, __Visitor { marker: _serde::export::PhantomData::<Config>, lifetime: _serde::export::PhantomData, }, ) </code></pre> <p>Config構造体に対応して<a target="_blank" rel="nofollow noopener" href="https://github.com/alexcrichton/toml-rs/blob/master/src/de.rs#L290">deserialize_struct</a>が呼び出されています. __Visitorというマクロ展開によって生成されたインスタンスも渡されています. このインスタンスが実際の処理担うわけです. ここからが実際には大変なところで, 興味がある人は処理を追ってみてください.</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>だいぶ内容がSerializerに偏ってしまいました. Serializeは比較的分かりやすいです. 例えばRustのコードをJSON化する場合どのような対応関係なのかはある程度予想がつきます. 少なくともRust側データ構造は決まっているからです. しかしDeserializeは比較的自由なJSONを型のきっちり決まったRustのデータ構造に仕立て直す必要があり, 自然と複雑化します. 例外はserde_jsonでシリアライズした者を復元する場合だけです.</p> <p>いずれにしても特殊なデータ形式に対応したいという開発者以外が独自にSerializeとかDeserializeは不要です. 多くの場合は属性を付与することでSerialize/Deserializeの挙動をカスタマイズできます. 次はそれを</p> <h2 id="課題"><a href="#%E8%AA%B2%E9%A1%8C">課題</a></h2> <p>BayardのJSONのデータをDeserializeする</p> <p>[Need help with #<a target="_blank" rel="nofollow noopener" href="https://users.rust-lang.org/t/need-help-with-serde-deserialize-with/18374">serde(deserialize_with)</a></p> <h2 id="補足"><a href="#%E8%A3%9C%E8%B6%B3">補足</a></h2> <p>補足では本論とは関係ないけど気になったことをメモしておきます. 良かったら読んでみてください.</p> <h3 id="Serdeの設計とSOLID原則"><a href="#Serde%E3%81%AE%E8%A8%AD%E8%A8%88%E3%81%A8SOLID%E5%8E%9F%E5%89%87">Serdeの設計とSOLID原則</a></h3> <p>SOLID原則とは,</p> <ul> <li>Single Responsibility Principle (SRP)</li> <li>Open Closed Principle (OCP)</li> <li>Liskov Substitution Principle (LSP)</li> <li>Interface Segregation Principle (ISP)</li> <li>Dependency Inversion Principle (DIP)</li> </ul> <p>の5つの原則の頭文字をとったものです.</p> <p>LSPにつてはRustには継承を言語レベルで採用していません. ただこの原則が型の互換性(多態性)に関する原則だと見るとトレイト境界で強制できます. serializeメソッドはトレイト境界でSerializerトレイトを実装したSerializer型を引数に取るという条件を課すことでLSPを満たしているとも言えます. そもそも継承だけでインターフェースとかがない頃にメソッドのシグニチャを揃えようというような話だったのかなと想像します. 多態性を実現する手段が継承しかない場合はこのような原則で縛りを設ける必要はありそうです. Rustの場合</p> <blockquote> <p>(あらゆる型の)スーパークラスであるTと境界を設定したサブクラスは代替可能である</p> </blockquote> <p>と表現すればそのまま当てはまりそうです.</p> <p>さてserializeメソッドはシリアライズの実行だけを担い, データの変換はSerializer型が提供するAPIが担いました. 一見すると分けすぎな気もしますが, 様々なデータ形式に対応する時にserialzierを別にしておくことで拡張性が保たれることが分かります. 社会でも指示を出す人と実行する人の役割は違うようにserializeの指示を出すのと実際のデータの変換という役割を適切に分離していると考えることもできます.</p> <p>ここでSerializerは依存性として外側から引数として渡されるような設計になっています. いわゆる依存性注入(DI)です. すでに述べましたがこの依存性はSerializerトレイトを実装している型であれば互換性があります. そもそもSerialize自体もトレイトであり抽象に依存する良い設計と言えそうです(DIP). Serde可能なデータ形式を追加したい開発者はこの関係に従って(縛られて?)ひたすら実装を行うだけです.</p> <p>逆にISPは満たしていないように見えます. <a target="_blank" rel="nofollow noopener" href="https://serde.rs/impl-serializer.html#implementing-a-serializer">Implementing a Serializer</a>にも</p> <blockquote> <p>The Serializer trait has a lot of methods ...</p> </blockquote> <p>とあります. トレイトの粒度としては荒いと言えます. これは不要なメソッドの実装も取り敢えずしなくてはいけないことになります. <del>ser.rsの実装を見ると萎えます</del> ではカスタマイズが必要な場合独自のSerializer型を実装する必要があるのかというと<a target="_blank" rel="nofollow noopener" href="https://serde.rs/attributes.html">serde属性</a>を使うことで特定の要素だけをカスタマイズできるようになっています. 例えばデシリアライズでデフォルト値を設定したい場合は</p> <pre><code class="rust">#[serde(default)] </code></pre> <p>を付与することで可能です.</p> <p>最後はOCPです. この原則は少し分かりにくいですが以下のように表現されます.</p> <blockquote> <p>the open/closed principle states "software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification"</p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://en.wikipedia.org/wiki/Open–closed_principle">Open–closed principle - Wikipedia</a></p> <p>(機能の追加自体がmodificationじゃないのかという感じはしますが)要するに他のコードへの変更なしに新しい機能を追加できるかということです. <a target="_blank" rel="nofollow noopener" href="https://serde.rs/impl-serializer.html">Implementing a Serializer</a>のser.rsを見てみましょう. データ構造, Serialize/Serializerトレイトがエンティティで機能の呼び出し側はto_stringという関数です.</p> <pre><code class="rust">pub fn to_string<T>(value: &T) -> Result<String> where T: Serialize, { let mut serializer = Serializer { output: String::new(), }; value.serialize(&mut serializer)?; Ok(serializer.output) } </code></pre> <p>OCPに適うということは, ある機能を拡張した場合に, このロジックに変更がなければ良いわけです. 今オレオレデータ形式がserde可能だとして考えましょう(oreクレートとでも呼びましょうか). ここでserializerに新しい型が対応したとしましょう. 例えばRustがxxx型に対応したとします(例えばf128型とか?あるいはクラスが追加されたとか?). 慣例に倣ってserialize_xxxが必要になりますが, to_stringのロジックに変更は必要ありません. OCPも満たしているようです.</p> <p>Sedeの面白いところは, Derive属性やserde属性を使うことでエンド・ユーザーはこうした実装を何も考慮しなくてもデータの変換が行えるようにしていることです. 反面黒魔術を多用しているためコードを読むのは難しいですが.</p> <h3 id="Required methodsとProvided methods"><a href="#Required+methods%E3%81%A8Provided+methods">Required methodsとProvided methods</a></h3> <p>Docs.rsにはこのような分類があるのですがRustでメソッドや関数に実装をオプションにするような構文があるという話は知りません. 私の勉強不足かとも思いましたが, 単純にデフォルトの実装の有無で見分けているようです.</p> <p><a target="_blank" rel="nofollow noopener" href="https://stackoverflow.com/questions/44642662/how-does-rust-know-which-trait-methods-are-required-or-provided">How does Rust know which trait methods are required or provided?</a></p> <p>実際に<a target="_blank" rel="nofollow noopener" href="https://github.com/serde-rs/serde/blob/33438850a6a8b0a3550619a60885cfc6f224e53f/serde/src/de/mod.rs#L1263">serde::de::Visitor</a>を見てみると<a target="_blank" rel="nofollow noopener" href="https://github.com/serde-rs/serde/blob/33438850a6a8b0a3550619a60885cfc6f224e53f/serde/src/de/mod.rs#L1289">expectingメソッド</a>はシグニチャの宣言だけです.</p> <pre><code class="rust">fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result; </code></pre> <p>一方<a target="_blank" rel="nofollow noopener" href="https://github.com/serde-rs/serde/blob/33438850a6a8b0a3550619a60885cfc6f224e53f/serde/src/de/mod.rs#L1294">visit_boolメソッド</a>はエラーを返すような仕様になっています.</p> <pre><code class="rust">fn visit_bool<E>(self, v: bool) -> Result<Self::Value, E> whereE: Error, { Err(Error::invalid_type(Unexpected::Bool(v), &self)) } </code></pre> <h3 id="matchガード"><a href="#match%E3%82%AC%E3%83%BC%E3%83%89">matchガード</a></h3> <p>Derive属性で自動導出した場合どのようなSerializeトレイトが実装されるのでしょうか.</p> <pre><code class="rust">use serde::{Deserialize, Serialize}; #[derive(Serialize)] struct Human { name: String, age: u8, appears_in: Vec<String>, } fn main() { println!("cargo expand") } </code></pre> <p>これを<a target="_blank" rel="nofollow noopener" href="https://github.com/dtolnay/cargo-expand">cargo-expand</a>で展開すると以下のようなSerializeトレイトが実装されます.</p> <pre><code class="rust">let mut __serde_state = match _serde::Serializer::serialize_struct( __serializer, "Human", false as usize + 1 + 1 + 1, ) { _serde::export::Ok(__val) => __val, _serde::export::Err(__err) => { return _serde::export::Err(__err); } }; </code></pre> <p>適切なSerializerメソッドが呼び出されているのが分かります. <a target="_blank" rel="nofollow noopener" href="https://doc.rust-lang.org/reference/expressions/match-expr.html#match-expressions">Match式</a>は式を後置できるのでmathから波括弧までの間が式で, その戻り値でOkかErrかで判断されます. こういうのは<a target="_blank" rel="nofollow noopener" href="https://doc.rust-lang.org/reference/expressions/match-expr.html">Match guards</a>というらしいです. 何らかの形でMatch式が使われていることは予想していたのですが実際のコードを見てみると面白いですね.</p> <h3 id="triマクロ"><a href="#tri%E3%83%9E%E3%82%AF%E3%83%AD">triマクロ</a></h3> <p>例でserde_jsonではなくtomlを使ったのはsede_jsonはserialize/deserializeを呼び出さないことと, triマクロを使っているので説明に適さないと思ったからです.</p> <pre><code class="rust">macro_rules! tri { ($e:expr) => { match $e { crate::lib::Result::Ok(val) => val, crate::lib::Result::Err(err) => return crate::lib::Result::Err(err), } }; } </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/serde-rs/json/blob/9354bec7ddf0733bae7666e64f0078d9d5f029d9/src/lib.rs#L419">lib.rs - serde-rs /json</a></p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://serde.rs/">The Serde Book</a><br /> <a target="_blank" rel="nofollow noopener" href="https://docs.serde.rs/serde/index.html">serde - Docs.rs</a><br /> <a target="_blank" rel="nofollow noopener" href="https://docs.rs/toml/0.5.6/toml/index.html">toml - Docs.rs</a></p> ブレイン tag:crieit.net,2005:PublicArticle/15870 2020-04-26T21:58:35+09:00 2020-06-28T13:07:35+09:00 https://crieit.net/posts/cargo-make cargo-makeによるプロジェクト・ビルド入門 <h1 id="cargo-makeによるプロジェクト・ビルド"><a href="#cargo-make%E3%81%AB%E3%82%88%E3%82%8B%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%83%BB%E3%83%93%E3%83%AB%E3%83%89">cargo-makeによるプロジェクト・ビルド</a></h1> <h2 id="モチベーション"><a href="#%E3%83%A2%E3%83%81%E3%83%99%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3">モチベーション</a></h2> <p>cargoはrustのパッケージ管理ツール兼ビルドツールである.これい自体非常に便利なのだが, Web標準は無視できない. 特にSeedのようなWebフロントエンド・フレームワークによる開発ではWeb標準に合わせる必要も出てくる. 単純なケースでは<a target="_blank" rel="nofollow noopener" href="https://seed-rs.org/guide/quickstart">Quickstart</a>に従ってindex.htmlに直接wasmモジュールを導入すれば良いのですが, 複雑なアプリなどはwebpackなどが使えた方が便利だと思います. 今回のケースではTailwindの導入などがそれに当たります.</p> <h2 id="目標"><a href="#%E7%9B%AE%E6%A8%99">目標</a></h2> <p>CSSフレームワークであるtailwindcssをSeedプロジェクトで利用する.</p> <h2 id="前提条件"><a href="#%E5%89%8D%E6%8F%90%E6%9D%A1%E4%BB%B6">前提条件</a></h2> <ul> <li>rustupの導入</li> </ul> <p>とりあえずこれを導入しておけば諸々の開発環境の導入・管理が行えるようになります.</p> <h2 id="NPM vs Cargo"><a href="#NPM+vs+Cargo">NPM vs Cargo</a></h2> <div class="table-responsive"><table> <thead> <tr> <th>機能</th> <th>NPM</th> <th>Cargo</th> <th>備考</th> </tr> </thead> <tbody> <tr> <td>パッケージ管理</td> <td>⭕️</td> <td>⭕️</td> <td>パッケージのインスタール・公開などができる</td> </tr> <tr> <td>依存性管理</td> <td>⭕️</td> <td>⭕️</td> <td>lockファイルがある点など共通点が多い</td> </tr> <tr> <td>タスク・ランナー</td> <td>⭕️</td> <td>❌</td> <td>Cargoではカスタム・コマンドが開発できる(はず)</td> </tr> <tr> <td>ビルド</td> <td>❌</td> <td>⭕️</td> <td>NPMでは代わりにタスク・ランナーを使う</td> </tr> <tr> <td>コマンド拡張</td> <td>❌</td> <td>⭕️</td> <td>タスク・ランナーから呼び出せば良い</td> </tr> </tbody> </table></div> <p>Cargoには<a target="_blank" rel="nofollow noopener" href="https://docs.npmjs.com/misc/scripts">npm-scripts</a>のようなタスク・ランナーがありませんが, <a target="_blank" rel="nofollow noopener" href="https://doc.rust-lang.org/cargo/reference/external-tools.html#custom-subcommands">カスタム・コマンド</a>で機能を拡張することができます. その一つが<a target="_blank" rel="nofollow noopener" href="https://github.com/sagiegurari/cargo-make">cargo-make</a>です.</p> <h2 id="cargo-makeによるプロジェクト管理"><a href="#cargo-make%E3%81%AB%E3%82%88%E3%82%8B%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E7%AE%A1%E7%90%86">cargo-makeによるプロジェクト管理</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/seed-rs/seed-quickstart">seed-quickstart</a>のMakefile.toml内容を解説する感じです. wachモードなどは<a target="_blank" rel="nofollow noopener" href="https://github.com/brainvader/EvaQL/blob/tags/v0.1/ui/Makefile.toml#L73">EvaQL/ui/Makefile.toml</a>を参照してください.</p> <h3 id="cargo-makeの導入"><a href="#cargo-make%E3%81%AE%E5%B0%8E%E5%85%A5">cargo-makeの導入</a></h3> <p>cargo-makeの実行にはcargo-makeバイナリが必要になるのでインストールしておきます.</p> <pre><code class="bash">cargo install --force cargo-make </code></pre> <p>次にプロジェクトを作成します. これもコマンド一つでできます. 今回はwasmモジュールとして読み込まれるので--libオプションをつけます.</p> <pre><code class="bash">cargo new --lib project-name </code></pre> <p>できたらプロジェクト・ルートに移動し実行してみましょう.</p> <pre><code class="bash">cago make </code></pre> <p>この時点では<a target="_blank" rel="nofollow noopener" href="https://github.com/sagiegurari/cargo-make#default-tasks-and-extending">デフォルトのtomファイル</a>が参照されます. 次にMakefile.tomlファイルを作成します.</p> <pre><code class="bash">cd project-name touch Makefile.toml </code></pre> <p>同様に実行すると今度はMakefile.tomlをもとに実行が行われます. 任意のmakefileを指定するには--makefileオプションを使います.</p> <pre><code class="bash">cargo make --makefile ./my_build.toml test </code></pre> <h3 id="Makefile.tomlの書き方"><a href="#Makefile.toml%E3%81%AE%E6%9B%B8%E3%81%8D%E6%96%B9">Makefile.tomlの書き方</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/seed-rs/seed-quickstart/blob/master/Makefile.toml">seed-quickstart/Makefile.toml</a>の解説です. 必要ない方は飛ばしましょう.</p> <h4 id="タスク"><a href="#%E3%82%BF%E3%82%B9%E3%82%AF">タスク</a></h4> <p>実行するコマンドはタスクという単位で管理します. 何もしないタスクは以下のようになります.</p> <pre><code class="toml">[tasks.do_nothing] # do nothing </code></pre> <h4 id="基本"><a href="#%E5%9F%BA%E6%9C%AC">基本</a></h4> <p>cargoのbuildサブコマンドを呼び出してみましょう.</p> <pre><code class="toml"># cargo make compile [tasks.compile] description = "Build" workspace = false command = "cargo" args = ["build"] </code></pre> <p>それぞれの意味は以下のようになります.</p> <div class="table-responsive"><table> <thead> <tr> <th>セクション</th> <th>意味</th> </tr> </thead> <tbody> <tr> <td>description</td> <td>このタスクの内容</td> </tr> <tr> <td>workspace</td> <td>workspaceでタスクを実行するかどうか</td> </tr> <tr> <td>command</td> <td>実行するメイン・コマンド</td> </tr> <tr> <td>args</td> <td>引数の指定</td> </tr> </tbody> </table></div> <h4 id="依存タスク"><a href="#%E4%BE%9D%E5%AD%98%E3%82%BF%E3%82%B9%E3%82%AF">依存タスク</a></h4> <p>dependencies属性を指定するとコマンドの依存性を指定できます. 要するに呼び出し順序です.</p> <pre><code class="toml"># cargo make start [tasks.start] description = "Combine the build and serve tasks" workspace = false dependencies = ["build"] </code></pre> <p>これでcargo startを実行するとbuildタスクが実行されます.</p> <h4 id="開発サーバーと環境変数"><a href="#%E9%96%8B%E7%99%BA%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%A8%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0">開発サーバーと環境変数</a></h4> <p>開発サーバーとしてmicroserverというクレートを使います. 簡単なWeb UIの開発には便利そうなのとクレート導入の例として紹介しておきます. 開発の趣旨を開発者の人がブログに書いています.</p> <p><a target="_blank" rel="nofollow noopener" href="https://robertohuertas.com/2018/11/01/microserver/">Microserver: local http server with SPA support</a></p> <p>今回のようにあるタスクの前提となるバイナリ・クレートのインストールも記述できます.</p> <pre><code class="toml">[tasks.serve] description = "Start server" install_crate = { crate_name = "microserver", binary = "microserver", test_arg = "-h" } workspace = false command = "microserver" args = ["--port", "${PORT}"] </code></pre> <p>サーバーということでポートの指定もしています. 環境変数もenvセクションで指定できます.</p> <pre><code class="toml">[env] PORT = "8000" </code></pre> <p>別ファイルに指定して読み込むこともできます.</p> <pre><code class="toml">[env] env_files = [ "./my_env.env", ] </code></pre> <h4 id="conditionによる条件設"><a href="#condition%E3%81%AB%E3%82%88%E3%82%8B%E6%9D%A1%E4%BB%B6%E8%A8%AD">conditionによる条件設</a></h4> <p>ある条件を満たすときにタスクを実行することもできます. 環境変数がきちんと指定されている場合だけ実行するという条件ならconditionセクションをタスクに追加します.</p> <pre><code class="toml">[tasks.start] condition = { env_set = [ "PORT" ] } </code></pre> <p>あるいは特定の環境変数を条件にして新しい変数を定義することができる.</p> <pre><code class="toml">[env] PORT_EXISTING = { value = "true", condtion = { env_set = ["PORT"] } } PORT = { value = "8000", condition = { env_not_set = ["PORT"] } } </code></pre> <p>条件によって読みやすい環境変数に変換したり, 環境変数が定義されていない場合に設定したりということができそうです.</p> <h4 id="profileによるモードの切り替え"><a href="#profile%E3%81%AB%E3%82%88%E3%82%8B%E3%83%A2%E3%83%BC%E3%83%89%E3%81%AE%E5%88%87%E3%82%8A%E6%9B%BF%E3%81%88">profileによるモードの切り替え</a></h4> <p>webpackのモードの指定ののようなこともできます.</p> <pre><code class="toml">[env] env_files = [ { path = "./development.env", profile = development }, { path = "./production.env", profile = "production } ] </code></pre> <pre><code class="bash">cargo make --profile production some_task </code></pre> <p>developmentはデフォルト値なので指定する必要はないです.</p> <h4 id="タスクの拡張とリリース・ビルド"><a href="#%E3%82%BF%E3%82%B9%E3%82%AF%E3%81%AE%E6%8B%A1%E5%BC%B5%E3%81%A8%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E3%83%BB%E3%83%93%E3%83%AB%E3%83%89">タスクの拡張とリリース・ビルド</a></h4> <p>タスク名をextend属性で指定するとタスクを拡張できます. 例えばcompileタスクをリリース・モードでビルドするように拡張すると以下のようになります.</p> <pre><code class="toml">[tasks.compile_release] description = "Release Build " extend = "compile" args = ["build", "--release"] </code></pre> <p>プラットフォームごとの拡張も簡単にできます.</p> <pre><code class="toml">[tasks.hello-world] script = [ "echo \"Hello World From Unknown\"" ] [tasks.hello-world.linux] script = [ "echo \"Hello World From Linux\"" ] [tasks.hello-world.mac] script = [ "echo \"Hello World From macOS\"" ] </code></pre> <h4 id="スクリプティング"><a href="#%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0">スクリプティング</a></h4> <p>シェルスクリプトを指定して実行することもできます.</p> <pre><code class="toml">[tasks.echo] script = [ "echo hello world" ] </code></pre> <p>script_runner属性を指定することでpythonなどスクリプトのランナーを指定できます.</p> <pre><code class="toml">[tasks.python] script_runner = "python" script_extension = "py" script = [ ''' print("Hello, World!") ''' ] </code></pre> <p>またファイルを指定して実行することもできます.</p> <pre><code class="toml">[tasks.run_from_script] script = { file = "hello.py" } </code></pre> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sagiegurari/cargo-make#rust-code">@rust</a>の指定でRustを実行することもできます.</p> <h4 id="run_tasks"><a href="#run_tasks">run_tasks</a></h4> <p>実行するタスクを指定します. dependenciesで指定したタスクは事前に実行されますが, run_task属性で指定したタスクは事後に実行されます.</p> <pre><code class="toml">[tasks.pre_task] script = [ "echo pre task"] [tasks.post_task] script = [ "echo post task"] [tasks.do_something] dependencies = ["pre_task"] run_task = "post_task" </code></pre> <p>この例の場合pre_task -> do_something -> post_taskの順で実行されます. 並列実行やフォークなど細かいタスクのフローを設定することできます. 詳しくは<a target="_blank" rel="nofollow noopener" href="https://github.com/sagiegurari/cargo-make#sub-task">Sub Task</a>を参照してください.</p> <h4 id="エイリアス"><a href="#%E3%82%A8%E3%82%A4%E3%83%AA%E3%82%A2%E3%82%B9">エイリアス</a></h4> <p>タスクを別名で参照できます.</p> <pre><code class="toml">[tasks.build] alias = "default_build" </code></pre> <p>Seedの<a target="_blank" rel="nofollow noopener" href="https://github.com/seed-rs/seed/tree/master/examples">examples</a>フォルダは複数のクレートが含まれており, そこにはMakefile.tomlが存在します. それぞれのクレートではプロジェクト・ルートのMakefile.tomlを拡張する形でルートのタスクを参照しています. つまり実行する処理は同じといことです.</p> <h4 id="条件付き実行"><a href="#%E6%9D%A1%E4%BB%B6%E4%BB%98%E3%81%8D%E5%AE%9F%E8%A1%8C">条件付き実行</a></h4> <p>条件を満たした場合にタスクが実行されます.</p> <pre><code class="toml">[tasks.test-condition] condition = { platforms = ["windows", "linux"], channels = ["beta", "nightly"], profiles = ["development", "production"], rust_version = { min = "1.39.0", max = "1.42.0" } } script = [ "echo \"condition was met\"" ] </code></pre> <p>このタスクはmacでstableチャネルを利用している人は実行されません. またscriptの代わりにrun_taskで他のタスクを条件を満たした時だけ実行するということもできます.</p> <h4 id="watchモード"><a href="#watch%E3%83%A2%E3%83%BC%E3%83%89">watchモード</a></h4> <p>watch属性をつけるとwatchモードで実行できます.</p> <pre><code class="toml">[tasks.run_from_script] script = { file = "hello.py" } watch = true </code></pre> <p>監視対象からの除外のような設定もできます.</p> <pre><code class="toml">[tasks.watch] description = "Start building project in watch mode" workspace = false dependencies = ["build", "build_wasm"] watch = { ignore_pattern="pkg/*" } </code></pre> <p>watchモードでサーバーを起動することはできません. この場合run_taskのparallelを使うとファイルの変更を監視しながら配信もで可能です.</p> <pre><code class="toml">[tasks.dev] description = "Build in watch mode while serving file" run_task = [ { name = ["watch", "serve"], parallel = true } ] </code></pre> <h3 id="tailwindcssの導入"><a href="#tailwindcss%E3%81%AE%E5%B0%8E%E5%85%A5">tailwindcssの導入</a></h3> <p>npmを使います.</p> <pre><code class="bash">npm init # if needed npm install tailwindcss </code></pre> <p>これでtailwindというコマンドがパッケージ上で使えるようになります. cssフォルダを作成して以下の内容をstyle.cssファイルを新規作成します.</p> <pre><code class="css">@tailwind base; @tailwind components; @tailwind utilities; </code></pre> <p>これをビルドして利用します. publicフォルダを同じ階層に作っておいて, css用のフォルダを作ります. carg-makeのタスクを追加しましょう.</p> <pre><code class="toml"># cargo make tailwind [tasks.tailwind] script = [ "npx tailwind build ./css/style.css -o ./public/css/style.css", ] </code></pre> <p>style.cssからstyle.cssが出力されますが中身を見ると見れ慣れたCSSファイルです. 出力されたファイルをpublic/index.htmlに読み込めばtailwindが提供するユーティリティ・クラスを利用できます.</p> <p>Seedでtailwindを使ってみましょう. Seedの説明はしませんがRustのマクロを使って要素を記述できます. 注目するのはclassマクロです. ここに指定された文字列がtailwindcssのユーティリティ・クラス名です.</p> <pre><code class="rust">fn view(model: &Model) -> impl View<Msg> { let button_class = class!["bg-gray-400", "px-8", "py-4"]; div![ class![ "flex", "flex-col", "justify-center", "items-center", "h-screen", "text-gray-600" ], button![ button_class, simple_ev(Ev::Click, Msg::Increment), format!("Click Me!") ], div![ class!["w-56", "text-center", "mt-2"], format!("Click {} times", model.counter) ] ] } </code></pre> <p>こんな感じの表示になりちゃんと表示されました(クリック時にカウント数を表示するラベルが動くバグがありますが・・・)</p> <p><a href="https://crieit.now.sh/upload_images/06db047230e8021afca815dae9b4f1595ea58291385eb.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/06db047230e8021afca815dae9b4f1595ea58291385eb.png?mw=700" alt="Seed with tailwindcss" /></a></p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>もうちょっとまとまりがあれば良いと思ったのですが, 意外と機能が多く詳細は公式の<a target="_blank" rel="nofollow noopener" href="https://github.com/sagiegurari/cargo-make">README.md</a>を読むのがいいと思います. <a target="_blank" rel="nofollow noopener" href="https://github.com/sagiegurari/cargo-make/tree/master/examples">examplesフォルダ</a>に例が豊富なので参考になると思います.</p> <p>tailwindcssはかなり使いやすいですしSeedもいい感じです(ただビルドが遅いですが・・・).</p> <h2 id="補足"><a href="#%E8%A3%9C%E8%B6%B3">補足</a></h2> <h3 id="WorkspaceとWorkspace Flow"><a href="#Workspace%E3%81%A8Workspace+Flow">WorkspaceとWorkspace Flow</a></h3> <p>タスクにworkspace属性を指定できました. Workspaceとは何でしょうか?</p> <blockquote> <p>A workspace is a set of packages that share the same Cargo.lock and output directory.</p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://doc.rust-lang.org/book/ch14-03-cargo-workspaces.html#cargo-workspaces">Cargo Workspaces - The Rust Programming Language</a></p> <p>要するに複数のパッケージを一つにまとめたものです. ただし単一のプロジェクトとして管理される前提なので最終生成物やCargo.lockなどで全体のクレートのバージョンなどは共通化されています. 実態は以下のような内容のCargo.tomlとmembers属性で指定されたメンバーとなるパッケージが存在するフォルダです.</p> <pre><code class="toml">[workspace] members = [ "client", "server", ] </code></pre> <p>こうした構成はSeedの<a target="_blank" rel="nofollow noopener" href="https://github.com/seed-rs/seed/tree/master/examples/server_integration">server_integration</a>という例が参考になると思います. 例えば適当なウォークスペースにmakefileを作りworkspace属性を指定します.</p> <pre><code class="toml">[tasks.do_something] workspace = false </code></pre> <p>なぜこのような設定が必要なのでしょうか. 通常cargo-makeのタスクはworkspace直下では実行されません. タスクの要求はメンバー・クレートで実行されます(workspace flow). このおかげでウォークスペースで実行したビルド処理が各クレートで実行されることになり, 共通の処理をウォークスペースにまとめられるので構成ファイルを小さくできます.</p> <p>しかしウォークスペースで実行したい場合もあるでしょう. その場合にこの機能をオフにするのがworkspace属性の意味です. この値はデフォルトでtrueになっています.</p> <pre><code class="toml">[config] default_to_workspace = false </code></pre> <p>のようにも指定するとデフォルト値をfalseに上書きできます.</p> <p>あるいはコマンド実行時にオプションとして渡すこともできます.</p> <pre><code class="bash">cargo make --no-workspace mytask </code></pre> <h3 id="CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILEフラグ"><a href="#CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE%E3%83%95%E3%83%A9%E3%82%B0">CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILEフラグ</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/sagiegurari/cargo-make#automatically-extend-workspace-makefile">CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILEフラグ</a>はウォークスペース直下のmakefileに指定します. そうすると自動的に個々のメンバー・クーレトが持つmakefileはルートのmakefileをextendで読み込み参照できるようになるようです.</p> <h3 id="WASM in A Nutshell"><a href="#WASM+in+A+Nutshell">WASM in A Nutshell</a></h3> <p>WebAssemblyという技術の略称がWASMでコードの拡張子にもなっている. 通常ブラウザはJavaScriptのランタイムを備えており(V8やスパイダーモンキー)JavaScriptのみを実行できる. JIT(Just In Time)コンパイラによる最適化など高速化されたが, 原理的にはランタイムはJavaScriptを逐次解釈してマシンコードに翻訳しそれを実行するために遅い. このプロセスを飛ばせれば, ネットーワークにるRTT(Round-Trip Time)を無視すればネイティブ並みに高速化できるわけです. これはPythonやRubyなどのインタプリタ言語がC/C++やRustなどの言語より遅い事と基本的には同じ関係と言えそうです.</p> <p>そこでWeb版のアセンブラを導入しようという話になるわけです. 通常アセンブリ言語はマシン語と1対1に対応するニーモニックを用いて表現されますが, WASMがターゲットとするのは複数のマシンを抽象化したマシンになります.</p> <blockquote> <p>So WebAssembly is a little bit different than other kinds of assembly. It’s a machine language for a conceptual machine, not an actual, physical machine.</p> </blockquote> <p><img src="https://2r4s9p1yi1fa2jd7j43zph8r-wpengine.netdna-ssl.com/files/2017/02/04-03-toolchain07.png" alt="image" /></p> <p><a target="_blank" rel="nofollow noopener" href="https://hacks.mozilla.org/2017/02/creating-and-working-with-webassembly-modules/">Creating and working with WebAssembly modules</a></p> <p>この説明を聞くとJavaに近い感じを受ける. 実際に(この比較はおかしいけど)WASIとJavaの類似性を指摘した記事なんかもある.</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.infoq.com/jp/news/2019/05/wasi-wasm-system-interface/">MozillaがWASIイニシアティブを発表、WebAssemblyをすべてのデバイス、コンピュータ、オペレーティングシステムで動作可能に</a></p> <p>また公式ではWASMを以下のように定義している.</p> <blockquote> <p>WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine.</p> </blockquote> <p><a target="_blank" rel="nofollow noopener" href="https://webassembly.org/">WebAssembly</a></p> <p>スタック・マシンは良くわからないけどWikipediaによると<a target="_blank" rel="nofollow noopener" href="https://ja.wikipedia.org/wiki/Java仮想マシン">Java仮想マシン</a>も似たような定義で紹介されている.</p> <blockquote> <p>Java仮想マシン (Java virtual machine、Java VM、JVM) は、Javaバイトコードとして定義された命令セットを実行するスタック型の仮想マシン。</p> </blockquote> <h2 id="Reference"><a href="#Reference">Reference</a></h2> <ol> <li><a target="_blank" rel="nofollow noopener" href="https://doc.rust-lang.org/cargo/">The Cargo Book</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/sagiegurari/cargo-make#usage-watch">cargo-make</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/seed-rs/seed-quickstart/blob/master/Makefile.toml">Makefile.toml - seed-quickstart</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://seed-rs.org/guide/view">View - Seed</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://tailwindcss.com/docs/installation">Installation - tailwindcss</a></li> </ol> <h2 id="例題"><a href="#%E4%BE%8B%E9%A1%8C">例題</a></h2> <p>タスクの依存関係, watchモードやcrateの導入などcargo-makeの基本的な使い方を学べる.</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/seed-rs/seed-quickstart">seed-quickstart</a></p> <p>examplesフォルダからルート・フォルダにあるMakefile.tomlの参照法などが参考になりました.</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/seed-rs/seed/tree/master/examples">examples -seed</a></p> <h2 id="課題"><a href="#%E8%AA%B2%E9%A1%8C">課題</a></h2> <p>SeedのようなWebフロントエンドの開発では, プロジェクトをcargoパッケージとしてマインに構成するのかnpmパッケージとしてメインに構成するのかが問題になる. cargo-makeがない場合はnpmパッケージ以下にcargoパッケージを作らないとビルド・プロセスが自動にできない. seed-quickstart-webpackもwebpackを使ってrustライブラリのビルドからwasmモジュールの読み込みなどを行なっている. これをcargo-makeベースに置き換えたい. npm-scriptでコマンド化しておけば, cargo-makeから呼び出せる.</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/seed-rs/seed-quickstart-webpack">seed-quickstart-webpack</a></p> <h2 id="Further Reading"><a href="#Further+Reading">Further Reading</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://hacks.mozilla.org/2017/02/a-crash-course-in-assembly/">A crash course in assembly</a><br /> <a target="_blank" rel="nofollow noopener" href="https://hacks.mozilla.org/2017/02/a-cartoon-intro-to-webassembly/">A cartoon intro to WebAssembly</a><br /> <a target="_blank" rel="nofollow noopener" href="https://hacks.mozilla.org/2017/02/what-makes-webassembly-fast/">What makes WebAssembly fast?</a></p> ブレイン tag:crieit.net,2005:PublicArticle/15524 2019-11-01T20:31:16+09:00 2019-11-01T20:34:07+09:00 https://crieit.net/posts/crate-feature 依存先のcrateのfeatureを指定する方法 <h1 id="依存先のcrateのfeatureを有効にする方法"><a href="#%E4%BE%9D%E5%AD%98%E5%85%88%E3%81%AEcrate%E3%81%AEfeature%E3%82%92%E6%9C%89%E5%8A%B9%E3%81%AB%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95">依存先のcrateのfeatureを有効にする方法</a></h1> <h2 id="結論"><a href="#%E7%B5%90%E8%AB%96">結論</a></h2> <p><code>cargo build --features="grpcio/openssl"</code>で<code>grpcio</code>の<code>openssl</code>を有効にできる。</p> <hr /> <p>Cargo.tomlが下記のようになっていれば<br /> <code>cargo build --features="openssl"</code>で同じことができる。</p> <pre><code class="toml">[features] openssl = ["grpcio/openssl"] [dependencies] grpcio = "0.4.4" </code></pre> <h2 id="やりたかったこと"><a href="#%E3%82%84%E3%82%8A%E3%81%9F%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8">やりたかったこと</a></h2> <p><code>grpcio</code>というcrateで<code>featueres = ["openssl"]</code> を指定してやることができる。</p> <p><code>cargo build</code>のようにfeaturesを指定しなかったときは<code>grpcio</code>のfeaturesを有効にしたくない。<br /> <code>cargo build --features=openssl</code>などしたときだけ<code>grpcio</code>の<code>openssl</code>を有効にしたい。</p> <h2 id="ドキュメントに書いてあった"><a href="#%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88%E3%81%AB%E6%9B%B8%E3%81%84%E3%81%A6%E3%81%82%E3%81%A3%E3%81%9F">ドキュメントに書いてあった</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section">https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section</a></p> <p>以下引用</p> <pre><code># Features can be used to reexport features of other packages. The `session` # feature of package `awesome` will ensure that the `session` feature of the # package `cookie` is also enabled. session = ["cookie/session"] </code></pre> <p>ここに気づかずに数時間無駄にした。</p> block tag:crieit.net,2005:PublicArticle/15390 2019-09-12T20:14:47+09:00 2019-09-20T03:43:34+09:00 https://crieit.net/posts/bbb87e9c13cbe3749e91325f25878c5d コンピューター囲碁を作ろうぜ☆(^~^)?<書きかけ> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 コンピューター囲碁を作るかだぜ☆<br /> ↓ググれば なんでも載ってるだろ……☆」</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/7ma7X/items/479ad9025a3368c2348f">Rust で TCP/IP ソケット通信をする際のモデル</a></p> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 コンピューター囲碁のポート番号は <code>9696</code> だったかだぜ☆?」</p> <p><code>C:\Users\むずでょ\source\repos\kifuwarabe-air2019</code></p> <p><a href="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif?mw=700" alt="OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif" /></a><br /> 「 ↑ソース・レポジトリ作っておくわよ」</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/muzudho/kifuwarabe-air2019">kifuwarabe-air2019</a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑ギットハブも用意しておいたぜ☆」</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.rust-lang.org/">Rust</a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑近年 Rustは仕様が変わったみたいだし見ておくかだぜ☆」</p> <p><a target="_blank" rel="nofollow noopener" href="https://doc.rust-lang.org/book/">book</a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑説明書はこれかだぜ☆」</p> <p><a target="_blank" rel="nofollow noopener" href="https://doc.rust-lang.org/book/ch01-01-installation.html">installation</a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑Rustのインストールには <code>rustup</code> を使えと書いてあるぜ☆<br /> わたしは既に古いのが入っているから アップグレードだな☆」</p> <p><a href="https://crieit.now.sh/upload_images/266b9eb99210fc692c6ab854a5760a7d5d7a2b6074953.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/266b9eb99210fc692c6ab854a5760a7d5d7a2b6074953.png?mw=700" alt="20190912igo1.png" /></a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑Visual Studio Code には Terminal も付いている☆ Powershell でやってしまおうぜ☆」</p> <p><a href="https://crieit.now.sh/upload_images/ef988f3aa6a596db4f611b502ae1f3f25d7a2c47ca567.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/ef988f3aa6a596db4f611b502ae1f3f25d7a2c47ca567.png?mw=700" alt="20190912igo2b1.png" /></a></p> <pre><code>rustc --version </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑よし、最新版になったぜ☆」</p> <p><a target="_blank" rel="nofollow noopener" href="https://doc.rust-lang.org/book/ch01-03-hello-cargo.html">Hello, Cargo!</a></p> <pre><code>cargo --version </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑まあ 基本 <code>cargo</code> を使うんだけど☆」</p> <p><a href="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif?mw=700" alt="OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif" /></a><br /> 「 きふわらべちゃんのプロジェクトを作りましょう!」</p> <p><a href="https://crieit.now.sh/upload_images/c06a850ba77777a6edc2f2c66941f1865d7a2e7e467aa.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/c06a850ba77777a6edc2f2c66941f1865d7a2e7e467aa.png?mw=700" alt="20190912igo3b1.png" /></a></p> <pre><code>cargo new kifuwarabe-air2019 </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑はい☆」</p> <p><a href="https://crieit.now.sh/upload_images/d12c09ebbae51b21ba9f52d54d3004835d7a2f66509e9.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d12c09ebbae51b21ba9f52d54d3004835d7a2f66509e9.png?mw=700" alt="20190912igo4b1.png" /></a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑ <code>Cargo.toml</code> の内容は何もいじらなくても書かれていたぜ☆ 完璧だぜ☆」</p> <p><a href="https://crieit.now.sh/upload_images/5bf80ffcac3937e2e1981d0d3f3e0f035d7a30bdd0bbe.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5bf80ffcac3937e2e1981d0d3f3e0f035d7a30bdd0bbe.png?mw=700" alt="20190912igo5b1.png" /></a></p> <pre><code>cargo build </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑ <code>.exe</code> ファイルも作られているな☆」</p> <p><a href="https://crieit.now.sh/upload_images/74c863e13c721ab5d00cba6f23bb484c5d7a319d35da5.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/74c863e13c721ab5d00cba6f23bb484c5d7a319d35da5.png?mw=700" alt="20190912igo6b1.png" /></a></p> <pre><code>cargo run </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑ ちゃんと実行もされるぜ☆」</p> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 Visual Studio Code で十分だな☆」</p> <p><a href="https://crieit.now.sh/upload_images/e7aae78662c0a167bd541eed76be6efc5d7a32b7800df.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e7aae78662c0a167bd541eed76be6efc5d7a32b7800df.png?mw=700" alt="20190912igo7.png" /></a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑ しかし ソース・レポジトリのコードを GitHub の ローカル・レポジトリにコピーするのは めんどくさいよな☆」</p> <p>go-to-git.ps1</p> <pre><code># + # | UTF-8 with BOM (Powershell) # | # | Folder copy. # + # + # | フォルダをゴミ箱に移動する # + function Remove-FolderToRecycleBin($dir) { if (Test-Path $dir -PathType Container) { $fullpath = (Get-Item $dir).FullName # + # | こんな変なパスぜったい間違う☆(^~^)チェックだぜ☆(^~^) # + if ($fullpath -cmatch ".*\\kifuwarabe-air2019\\kifuwarabe-air2019") { Write-Host "Remove | [$fullpath] directory." Remove-Item $fullpath -Recurse } else { Write-Host "Ignore | [$fullpath] directory." } } else { Write-Host "Ignore | [$dir] is not directory or not found." } } $sr = "C:\Users\むずでょ\source\repos\kifuwarabe-air2019\kifuwarabe-air2019" $ds = "C:\Users\むずでょ\Documents\GitHub\kifuwarabe-air2019\kifuwarabe-air2019" Remove-FolderToRecycleBin($ds) Write-Host "Copy | [$sr] --to--> [$ds]." Copy-Item $sr -destination $ds -recurse </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑ 必死こいて Powershell 書く☆」</p> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 <code>powershell ./go-to-git.ps1</code> と打鍵するのが うんこ じゃないか☆?」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 Visual studio code の Explorer で右クリックして File explorer 開いて .ps1 ファイルをダブルクリックするからOK☆」</p> <p><a href="https://crieit.now.sh/upload_images/fff364242c609825f2eda573b64207fb5d7a3cd785fed.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/fff364242c609825f2eda573b64207fb5d7a3cd785fed.png?mw=700" alt="20190912igo8.png" /></a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑ あとは Git hub desktop でなんとかする☆」</p> <pre><code>cargo check </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑ コーディングのチェックをしてくれたりするのだろうか☆? あとで使おう☆」</p> <pre><code>cargo build --release </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑ デバッグを外して .exe を作るには <code>--release</code> を付ければいいらしい☆ これ1つ忘れるだけで大会では クソとノーマルの差が分かれる☆」</p> <h1 id="ロガーを用意しろだぜ☆(^~^)"><a href="#%E3%83%AD%E3%82%AC%E3%83%BC%E3%82%92%E7%94%A8%E6%84%8F%E3%81%97%E3%82%8D%E3%81%A0%E3%81%9C%E2%98%86%EF%BC%88%EF%BC%BE%EF%BD%9E%EF%BC%BE%EF%BC%89">ロガーを用意しろだぜ☆(^~^)</a></h1> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ロギングできるかどうかで プログラマーは クソとノーマルに大きく分かれるのに 公式のマニュアルに書いてないな☆」</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/fujitayy/items/590145c0f4b4e7d06de7">Rust:logでログ出力を行う</a><br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/Dsuke-K/items/163a312bdd2b8a260615">Rustでのロギング</a><br /> <a target="_blank" rel="nofollow noopener" href="https://docs.rs/log/0.4.8/log/">Crate log</a><br /> <a target="_blank" rel="nofollow noopener" href="https://rust-lang-nursery.github.io/rust-cookbook/development_tools/debugging/config_log.html">Configure Logging</a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ここらへんの連中は ログをファイルに書き込まないのだろうか☆?<br /> <code>stderr</code> へのストリーム出力から ファイルへリダイレクトが基本かだぜ☆?」</p> <p><a href="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif?mw=700" alt="OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif" /></a><br /> 「 遅~いロガーを掴んだら コンピューターは弱くなるわよ?」</p> <p><a target="_blank" rel="nofollow noopener" href="https://crates.io/crates/env_logger">env_logger</a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 じゃあ <code>env_logger</code> 一択な気がするな……☆ 使ってみるかだぜ☆?」</p> <p><a href="https://crieit.now.sh/upload_images/3bdfbe3e6daffc2a78092c28b9d1dbae5d7a46c045e8d.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3bdfbe3e6daffc2a78092c28b9d1dbae5d7a46c045e8d.png?mw=700" alt="20190912igo9b1.png" /></a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 こんな風に 真似して <code>Cargo.toml</code> を書いていけばいい☆<br /> 1990年代のジャパンなら 友だちの友だちをたどっていけば パソコンオタクの眼鏡のお兄さんがどこかに居て<br /> 家までやってきて パソコンのコマンドの叩き方とか教えてくれたが、<br /> 2019年頃のジャパンでは 学校のパソコン部に入ったのに何もせずに卒業して ブログのエントリを書いて<br /> いいね! が付くのが流行りみたいだぜ☆」</p> <p><a href="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif?mw=700" alt="OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif" /></a><br /> 「 何も いいね! ではないわよね。 何が いいの かしら?」</p> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 承認欲求が満たされたのだろう☆」</p> <p>main.rs</p> <pre><code>#[macro_use] extern crate log; extern crate env_logger; fn main() { env_logger::init(); println!("Hello, println!"); trace!("Hello, trace!"); debug!("Hello, debug!"); info!("Hello, info!"); warn!("Hello, warn!"); error!("Hello, error!"); } </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑こういうコードがあるとして……☆」</p> <p><a href="https://crieit.now.sh/upload_images/8520b184eb63c30e1f12da236531104f5d7a4dde37cd4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/8520b184eb63c30e1f12da236531104f5d7a4dde37cd4.png?mw=700" alt="20190912igo10.png" /></a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑環境変数を使って これだけ使い分けれれば十分だろう……☆<br /> なんか <code>Hello, println!</code> の出るタイミングがバラバラだな……、非同期か☆」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 で、 PowerShell って どうやって ストリームをリダイレクトするんだぜ☆?」</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/toshihirock/items/936b33f0c15723565dce">PowerShellでのエラーハンドリングについて</a></p> <pre><code>command 2>&1 >> ./kifuwarabe-air2019.log </code></pre> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 ↑で いいのでは☆?」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ほんとか☆?」</p> <p><a href="https://crieit.now.sh/upload_images/3c4bf99b25ffc6a706d6db8d966061075d7a4ff216272.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3c4bf99b25ffc6a706d6db8d966061075d7a4ff216272.png?mw=700" alt="20190912igo11.png" /></a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑コマンドの一覧が ログ・ファイルに書き出された……☆ ということは☆」</p> <pre><code>cargo run 2>&1 >> ./kifuwarabe-air2019.log </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑こうかだぜ☆?」</p> <p><a href="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif?mw=700" alt="OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif" /></a><br /> 「 それだと cargo の引数になるんじゃない?」</p> <p><a href="https://crieit.now.sh/upload_images/7a53a989b0834f8e755346390740dae85d7a51538b796.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7a53a989b0834f8e755346390740dae85d7a51538b796.png?mw=700" alt="20190912igo12.png" /></a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 一応 <code>>></code> が利いたのか ファイルに 追加書き込みされているが、標準出力に何もでなくなったぜ☆」</p> <pre><code>cargo run 2>> ./kifuwarabe-air2019.log </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 だったら単に 標準エラーの2 を追加書き込みのリダイレクト <code>>></code> でファイルに送る指定だけする☆<br /> これで平和だぜ☆」</p> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 それだと 標準出力にエラーが出てこない☆」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 リダイレクト先を2つに増やすことはできないんじゃないか☆?<br /> とりあえず ログを取るときは しばらくこれで☆」</p> <p><a href="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif?mw=700" alt="OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif" /></a><br /> 「 コマンドの打鍵がめんどくさくない?」</p> <pre><code>/* # - Log level. #$env:RUST_LOG = "trace" #$env:RUST_LOG = "debug" $env:RUST_LOG = "info" #$env:RUST_LOG = "warn" #$env:RUST_LOG = "error" # - Log redirect. cargo run 2>> ./kifuwarabe-air2019.log */ </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 main.rs の冒頭にコメントを書いておいて これを貼り付ければいいだろ……☆」</p> <h1 id="TCP/IP 通信"><a href="#TCP%2FIP+%E9%80%9A%E4%BF%A1">TCP/IP 通信</a></h1> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 次は通信書くか……☆」</p> <p><a target="_blank" rel="nofollow noopener" href="https://rohki.hatenablog.com/entry/2018/08/03/200805">Rust のお試しコードを実行する: cargo run --example</a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑rust には サンプル・プログラムの書き方がある☆」</p> <p><a href="https://crieit.now.sh/upload_images/25adb0101818f1baee11a624e666bce75d7bf4644db04.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/25adb0101818f1baee11a624e666bce75d7bf4644db04.png?mw=700" alt="20190914igo13b1.png" /></a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 <code>examples</code> ディレクトリの下にあるファイルは <code>cargo run --example</code> を使って実行できる☆」</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/7ma7X/items/479ad9025a3368c2348f">Rust で TCP/IP ソケット通信をする際のモデル</a><br /> <a target="_blank" rel="nofollow noopener" href="https://cha-shu00.hatenablog.com/entry/2019/03/02/174532">Rustにっき/8日目・TCPサーバ</a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 接続に失敗するな……、ポート番号調べてみるか☆」</p> <pre><code>### Linux lsof -i:3000 ### Windows netstat -aon | findstr 3000 </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 何も出てこないな☆ 3000番ポートは空いてるはず☆」</p> <p><a href="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif?mw=700" alt="OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif" /></a><br /> 「 接続される側で 待ち受けているサーバーが無いのだから、接続できるわけないんじゃないの?」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 だったら そういうサンプルだと説明が欲しいぜ☆<br /> サーバー書くか……☆」</p> <h2 id="クライアントとサーバー"><a href="#%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%A2%E3%83%B3%E3%83%88%E3%81%A8%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC">クライアントとサーバー</a></h2> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 接続するだけなら 次のように書けばいい☆」</p> <p>examples/server.rs</p> <pre><code>/* # Current directory # cd ./kifuwarabe-air2019 cd C:\Users\むずでょ\source\repos\kifuwarabe-air2019\kifuwarabe-air2019 cargo run --example server */ use std::net::{TcpListener, TcpStream, ToSocketAddrs}; use std::io::{BufRead, BufReader, BufWriter, Error, Read, Write}; use std::thread; /** * See: [Rustにっき/8日目・TCPサーバ](https://cha-shu00.hatenablog.com/entry/2019/03/02/174532) */ fn main(){ let host = "localhost"; let port = 9696; let url = format!("{}:{}", host, port); let mut addrs = url.to_socket_addrs().unwrap(); // Change to ip v4. if let Some(addr) = addrs.find(|x| (*x).is_ipv4()) { // Success addr:127.0.0.1:9696 println!("Success sever-addr:{}", addr); // Wait for connection. let listener = TcpListener::bind(addr).expect("Error. failed to bind."); for streams in listener.incoming() { match streams { Err(e) => { eprintln!("error: {}", e)}, Ok(stream) => { println!("Create the thread.") /* // TODO: Create the thread. thread::spawn(move || { handler(stream).unwrap_or_else(|error| eprintln!("{:?}", error)); }); */ } } } } else { eprintln!("Invalid Host:Port Number"); } } </code></pre> <p>examples/client.rs</p> <pre><code>/* # Current directory # cd ./kifuwarabe-air2019 cd C:\Users\むずでょ\source\repos\kifuwarabe-air2019\kifuwarabe-air2019 cargo run --example client */ use std::net::{TcpStream, ToSocketAddrs}; use std::io::{BufRead, BufReader, BufWriter, Write}; /** * See: [Rust で TCP/IP ソケット通信をする際のモデル](https://qiita.com/7ma7X/items/479ad9025a3368c2348f) */ fn main(){ let host = "localhost"; let port = 9696; let url = format!("{}:{}", host, port); let mut addrs = url.to_socket_addrs().unwrap(); // Change to ip v4. if let Some(addr) = addrs.find(|x| (*x).is_ipv4()) { // Success addr:127.0.0.1:3000 println!("Success client-addr:{}", addr); match TcpStream::connect(addr) { Err(_) => { println!("Connection NG."); } Ok(stream) => { println!("Connection Ok."); // Buffering. let mut reader = BufReader::new(&stream); let mut writer = BufWriter::new(&stream); read_something(&mut reader); write_something(&mut writer, "hoge"); } } } else { eprintln!("Invalid Host:Port Number"); } } fn read_something (reader: &mut BufReader<&TcpStream>) { let mut msg = String::new(); reader.read_line(&mut msg).expect("RECEIVE FAILURE!!!"); // read_line は改行文字まで読む。 // 他のread系のメソッドもある (https://doc.rust-lang.org/std/io/trait.BufRead.html) println!("{}", msg); } fn write_something (writer: &mut BufWriter<&TcpStream>, comment: &str) { let msg = format!("MESSAGE: {}\n", comment); // 送る側もたぶん改行文字を付けたほうがよいでしょう。 writer.write(msg.as_bytes()).expect("SEND FAILURE!!!"); writer.flush().unwrap(); } </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 で、使うには server.rs を実行して待機させておいてから、 client.rs で接続する☆<br /> Visual studio code は Terminal ビューを1つしか出せないかもしれないので、<br /> Visual studio code の2つ目を起動して Terminal ビューを使う☆」</p> <p><a href="https://crieit.now.sh/upload_images/04e91cac753dd345ba90087c43d5ed1c5d7f3bf379721.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/04e91cac753dd345ba90087c43d5ed1c5d7f3bf379721.png?mw=700" alt="20190916igo14a1b1.png" /></a></p> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 そんな使い方で 何かが 混線しないか 知らんけどな☆」</p> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 コンピューター囲碁の通信プログラムって どんなもんだぜ☆?」</p> <h1 id="GTPではなく、nngs通信プロトコル"><a href="#GTP%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%8F%E3%80%81nngs%E9%80%9A%E4%BF%A1%E3%83%97%E3%83%AD%E3%83%88%E3%82%B3%E3%83%AB">GTPではなく、nngs通信プロトコル</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://www.igoshogi.net/ai_ryusei/01/protocols.html">通信対局規約</a><br /> <a target="_blank" rel="nofollow noopener" href="https://www.gnu.org/software/gnugo/gnugo_19.html">19. The Go Text Protocol</a><br /> <a target="_blank" rel="nofollow noopener" href="http://www.lysator.liu.se/~gunnar/gtp/">GTP - Go Text Protocol</a><br /> <a target="_blank" rel="nofollow noopener" href="https://senseis.xmp.net/?GoTextProtocol">Go Text Protocol</a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 Go text protocol だろう☆ 大会で使うコマンドは6つしかない☆」</p> <p><a target="_blank" rel="nofollow noopener" href="http://www.yss-aya.com/cgfgoban.html">CgfGoBan and Nngs_try</a><br /> <a target="_blank" rel="nofollow noopener" href="http://www9.plala.or.jp/sgwr-t/c_sub/ascii.html">ASCIIコード表</a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑例えば Cgf Go Ban の cgf_pipe.cpp というファイルに実装が書いてあるぜ☆<br /> ASCIIコード表は助けになるだろう☆」</p> <ul> <li>コマンドは Ascii文字。</li> <li>コマンドは 2048文字未満。これを超えたらエラー。</li> <li>コマンドは <code>\n</code> (Asciiコード 10)で終了。</li> <li><code>\n</code> を除く 32未満のAsciiコードはすべて無視。例えば <code>\r</code> (Asciiコード 13) などを無視。</li> <li><code>\t</code> (水平タブ; Asciiコード 9) を 半角スペース (Asciiコード 32) に置換しているコードがあるが、これは働いてないと思う。</li> <li>コマンド先頭が <code>#</code> ならコメント行だから無視。まだコマンドは送られてくる。</li> <li>空行(<code>\n</code> だけの行)は無視。まだコマンドは送られてくる。</li> </ul> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 あと、プロトコルの通信ログを見ないと分からないところだが☆、」</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.gnu.org/software/gnugo/gnugo_19.html">19. The Go Text Protocol</a></p> <pre><code>virihaure 462% ./gnugo --mode gtp 1 boardsize 7 =1 2 clear_board =2 3 play black D5 =3 4 genmove white =4 C3 5 play black C3 ?5 illegal move 6 play black E3 =6 7 showboard =7 A B C D E F G 7 . . . . . . . 7 6 . . . . . . . 6 5 . . + X + . . 5 4 . . . + . . . 4 3 . . O . X . . 3 2 . . . . . . . 2 WHITE (O) has captured 0 stones 1 . . . . . . . 1 BLACK (X) has captured 0 stones A B C D E F G 8 quit =8 </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 サーバーは イコールを付けて ひと桁の数を返してくる☆<br /> イコールと数の2文字だけが飛んで来たら無視しろだぜ☆ 3文字以上なら何か命令かもしれない☆」</p> <ul> <li>4文字目以降を move とする。例えば <code>=4 C3</code> なら <code>C3</code> が move。</li> <li>パスは move が <code>pass</code>。 GnuGoは"PASS"と送ってくるから大文字・小文字を区別しないようにする。</li> <li>投了は move が <code>resign</code>。 SGMPは非対応。nngsは相手が落ちるかも?</li> <li>move の1文字目はアルファベット。筋(列)に対応。<code>I</code> は無いから詰めること。</li> <li>move の2文字目以降は段(行)数。</li> </ul> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 Go text protocol は いろいろコマンドがあるんだが 大会では 5つのコマンドしか送んな、ということだし<br /> それだけ 実装すればいいんだが、一応 Cgp Go Ban にあるコマンドを見ておこう☆」</p> <ul> <li><code>boardsize %d\n</code> - ban_size 何路盤。</li> <li><code>clear_board\n</code> - 盤面を初期化</li> <li><code>komi %.1f\n</code> - komi, - コミを設定</li> <li><code>name\n</code> - 名前を要求。</li> <li><code>version\n</code> - バージョンを要求。</li> <li><code>genmove black\n</code> - GnuGo に黒番打たせる。</li> <li><code>genmove white\n</code> - GnuGo に白番打たせる。</li> <li><code>play black %s\n</code> - GnuGo の盤面に黒石を置く。符号は <code>Q4</code> みたいなやつ。</li> <li><code>play white %s\n</code> - GnuGo の盤面に白石を置く。</li> <li><code>final_status %s\n</code> - GnuGo に死活判定を聞く。符号は <code>Q4</code> みたいなやつ。結果は次の6つ。 <ul> <li><code>alive</code></li> <li><code>dead</code></li> <li><code>seki</code></li> <li><code>white_territory</code></li> <li><code>black_territory</code></li> <li><code>dame</code></li> </ul></li> </ul> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 どれも大会で使わなさそう☆」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 こっちから 通信で送るのは cgf_win.cpp の <code>SendUpdateSetKifu</code> 関数から辿っていけば 見つかるだろう……☆」</p> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 cgf_term.cpp にいろいろ書いてあるが、 <code>SGMP</code> 使ってそう☆」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 大会は <code>NNGS 1.1.22 を拡張したプロトコル</code> だから、 NNGS 1.1.22 が何を使ってるか調べるか……☆」</p> <p><a target="_blank" rel="nofollow noopener" href="http://www.computer-go.jp/gifu2005/regulations/communication.html">通信対戦について</a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 <code>GTP</code> とは また違うのか☆ うーむ☆」</p> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 <code>cgf_wsk.cpp</code> に、nngs への接続プログラムが書かれているぜ☆」</p> <p><a target="_blank" rel="nofollow noopener" href="http://www.computer-go.jp/gifu2005/regulations/nngs/playera.html">playera側の記録</a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 通信ログが上記のようなものだとして……☆」</p> <p>Send:</p> <pre><code>telnet nngs.computer-go.jp 9696 </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 テルネットで接続すればいいらしい☆」</p> <p>Send:</p> <pre><code>playera </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 そのあと有無を言わさず 自分の名前を送るらしい☆」</p> <p>Receive:</p> <pre><code>No Name Go Server (NNGS) version 1.1.14 </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 次はこんな行が送られてくるから、<br /> <code>No Name Go Server (NNGS) version</code> という文字列を当てに行く☆ 当たれば☆、」</p> <p>Send:</p> <pre><code>set client TRUE\n </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 こんな文字列を 有無を言わさず送る☆<br /> 大会では <code>"set client FALSE"</code> と書いてあって どうすればいいのか☆」</p> <p>Receive:</p> <pre><code>Set client to be True </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 よく分からんが client モードが True になった、と受信すれば……☆」</p> <pre><code>match %s B %d %d 0\n",nngs_player_name[1-fTurn],ban_size,nngs_minutes </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 matchコマンドで黒番に対局を申し込むのか☆ わたしは白番か☆」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 対局を申し込まれた <code>playerb</code> の方は☆、」</p> <pre><code>Match [19x19] in 40 minutes requested with playera as Black. </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑みたいな文字列を受け取る☆<br /> <code>"Match [%dx%d]",ban_size,ban_size</code> みたいな感じで当てに行く☆」</p> <pre><code>match playerb B 19 40 0 </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 そして<br /> <code>"match %s W %d %d 0\n",nngs_player_name[1-fTurn],ban_size,nngs_minutes</code><br /> みたいな感じの文字列を 有無を言わさず送る☆<br /> わたしのプレイヤー名は <code>playerb</code>、 黒番で 19路、 持ち時間は 40分とか そんな感じだろうか☆」</p> <p>Received:</p> <pre><code>accepted. </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 黒番のときは <code>accepted.</code> を受信するとのことだぜ☆ この直後から初手を送れるとのことだぜ☆」</p> <pre><code>Illegal </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 エラーがあった場合、 <code>illegal</code> という文字が含まれているらしいぜ☆ こうなりゃ終了☆」</p> <pre><code>sprintf(tmp,"(%s): ",stone_str[1-fTurn]); </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 これはただの表示かだぜ☆」</p> <pre><code>Pass </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 これを受け取ったら、相手はパス☆<br /> 送るのは <code>pass</code> なのに、受け取るのは <code>Pass</code> なのか☆」</p> <pre><code>T10 </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 よく分からんが 指し手 は そのまんま飛んでくるのか? よく分からん☆」</p> <pre><code>14 |. # # O O # # . # O # # O O O # O O #| 14 13 |# # O # # # . # O O O # # # O O O . O| 13 Last Move: J1 12 |# O O # O . . # # O . O O O O . O O .| 12 #216 O (White) </code></pre> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 盤の右横に指し手が書いてるんじゃないか☆?」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 なんで そんなところに……☆ よく誤検知しないものだぜ☆ A1 とか B10 とか☆」</p> <p>Received:</p> <pre><code>You can check your score </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 自分が <code>pass</code> したあとに相手が <code>pass</code> すると、 <code>Pass</code> は受信せず上のメッセージを受信するみたいだぜ☆」</p> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a></p> <p>「 これは プロトコル なのかだぜ☆?!」</p> <p>Send:</p> <pre><code>done </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 地を計算するんだが めんどくさいんで <code>done\n</code> を送る☆ すると☆、」</p> <p>Received:</p> <pre><code>resigns. </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 というメッセージを受信して 通信完了だぜ☆ 棋譜はサーバーに保存されているはずだぜ☆」</p> <p>Received:</p> <pre><code>9 {Game 1: test2 vs test1 : Black resigns. W 10.5 B 6.0} </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 対局が終わると 上記のようなメッセージを受信するので……☆」</p> <pre><code>strstr(str,"9 {Game") && strstr(str,"resigns.") </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 <code>9 {Game</code> と <code>resigns.</code> を含む行を当てに行き……☆」</p> <pre><code>sprintf(tmp,"%s vs %s",nngs_player_name[1],nngs_player_name[0]); </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 <code>playera vs playerb</code> みたいな文字列を作って また当てに行き、当たったら 対局終了だぜ☆」</p> <p><a href="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif?mw=700" alt="OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif" /></a><br /> 「 JSONでプロトコルを書き直しましょうよ!」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 大会のサーバーと通信することが優先だぜ☆」</p> <p>Received:</p> <pre><code>{%s has disconnected} </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 対戦相手が接続を切ったときは 上のメッセージが飛んでくる☆ <code>%s</code> は相手プレイヤー名だぜ☆」</p> <p>Received:</p> <pre><code>forfeits on time </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 どちらかの持ち時間が切れた場合は、上のメッセージと同じ行に 両プレイヤー名が載っているはずだぜ☆」</p> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 こんなけ分かれば 疑似サーバー を作れるだろ☆」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 要点を まとめないとな☆」</p> <p><a href="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif?mw=700" alt="OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif" /></a><br /> 「 ユーザー名と パスワードの送信って どうやんの?」</p> <p><a target="_blank" rel="nofollow noopener" href="http://www.wing.gr.jp/explore_b.html">WINGの歩き方 Ver 1.10</a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 もっと古い時代に NNGS を使っていた Wing のサイトも見てみるかだぜ☆」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 パスワードを変える方法はあっても、パスワードを入力する説明は見当たらないな☆」</p> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 CgfGoBan でパスワードを入れたことは無いだろ☆ パスワード無しでやろうぜ☆」</p> <ul> <li>Send: <code>telnet nngs.computer-go.jp 9696</code></li> <li>Send: <code>playera</code></li> <li>Received: <code>No Name Go Server (NNGS) version</code></li> <li>Send: <code>set client TRUE</code></li> <li>Received: <code>Set client to be True</code></li> <li>Send: <code>match %s B %d %d 0\n",nngs_player_name[1-fTurn],ban_size,nngs_minutes</code></li> <li>Received: <code>Match [19x19]</code></li> <li>Send: <code>match %s W %d %d 0\n",nngs_player_name[1-fTurn],ban_size,nngs_minutes</code></li> <li>Received: <code>accepted.</code></li> <li>Received: <code>Illegal</code></li> <li>Received: <code>Pass</code></li> <li>Send: <code>T10</code></li> <li>Send: <code>pass</code></li> <li>Received: <code>You can check your score</code></li> <li>Send: <code>done</code></li> <li>Received: <code>9 {Game 1: test2 vs test1 : Black resigns. W 10.5 B 6.0}</code></li> <li>Received: <code>{%s has disconnected}</code></li> <li>Received: <code>forfeits on time</code></li> </ul> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 まだ マッチングのところが よく分からんな……☆」</p> <h2 id="マッチング"><a href="#%E3%83%9E%E3%83%83%E3%83%81%E3%83%B3%E3%82%B0">マッチング</a></h2> <p><a target="_blank" rel="nofollow noopener" href="http://www.computer-go.jp/gifu2005/regulations/communication.html">通信対戦について</a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑説明を1つ1つ読んでいくかだぜ☆」</p> <ul> <li>プレイヤがサーバにログイン</li> </ul> <p>Send:</p> <pre><code>telnet nngs.computer-go.jp 9696 </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ログインは 通信プロトコルと関係ないよな☆ 接続を確立するまでは TCP/IP だぜ☆」</p> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 ここから先は とにかく2人以上が サーバーにぶら下がっているという前提みたいだぜ☆」</p> <ul> <li>一方(例えばplayeraという名前でログインしている)が相手に「match」コマンドで対局を申し込む。playerbという名前のプレイヤに、19路盤、自分が黒番、持ち時間は40分、秒読みなしという条件で申し込む場合には、「match playerb B 19 40 0」というコマンドをサーバに送信する。</li> </ul> <p>Send:</p> <pre><code>match playerb B 19 40 0 </code></pre> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 相手のプレイヤー名も知っているという前提だな☆ 大会でなら知ってるしな☆」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 少しずつ 分かってきた感じがするな☆」</p> <ul> <li>相手が同じく「match」コマンドで対局を受けたら対局開始(match playera W 19 40 0)</li> </ul> <p>Send (Opponent):</p> <pre><code>match playera W 19 40 0 </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 サーバー側で シェイクハンド を監視しないといけないな☆」</p> <ul> <li>対局開始</li> </ul> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 具体的なことが分からん☆」</p> <ul> <li>互いに自分の手を送信していく(「d3」というような座標を送信する)</li> </ul> <p>Send:</p> <pre><code>d3 </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 どっちの手番か分かるのかだぜ☆?」</p> <ul> <li>一方のプレイヤが「pass」を送信</li> <li>もう一方のプレイヤが「pass」を送信</li> </ul> <p>Send (Self/Opponent):</p> <pre><code>pass </code></pre> <p>Send (Opponent/Self):</p> <pre><code>pass </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 これは棋譜の上で2者が続けて <code>pass</code> を送信するということだな☆」</p> <ul> <li>双方が「done」コマンドを送信(死に石の情報を送信する必要はありません。また、サーバからのメッセージは無視してください)</li> </ul> <p>Send (Self/Opponent):</p> <pre><code>done </code></pre> <p>Send (Opponent/Self):</p> <pre><code>done </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 これは本当は 郵便囲碁でお互いが 地計算して どれが生き石、どれが死に石 と言い合うところなんだが、<br /> 大会では めんどくさいんで スキップしようぜ、という意味合いだぜ☆」</p> <ul> <li>それぞれのプログラムはそのコンピュータのモニタ上に、盤面情報、死に石の情報、地の計算結果を表示します</li> <li>プログラムの操作者がお互いに双方のプログラムの表示している結果を比較してください(これまでのRS232C接続の際の終局時と同様です)</li> <li>双方のプログラムの結果が一致しない時には、審判が不一致部分の判定を行ない、結果を確定します</li> </ul> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 これだけ実装すれば 疑似サーバーは作れそうだな☆」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 しかし、白番が先にログインして 黒番は後から入れ、とか 何か しきたり がなかったかだぜ☆?」</p> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 Cgf Go Ban のしきたりなんじゃないか☆?」</p> <p><a href="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif?mw=700" alt="OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif" /></a><br /> 「 2人しか接続してこないという前提で プログラム書けば 早くできるんじゃないの?」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 そのあたりの仕様を もう少し詰めようぜ☆」</p> <h1 id="TCP/IP通信その2"><a href="#TCP%2FIP%E9%80%9A%E4%BF%A1%E3%81%9D%E3%81%AE%EF%BC%92">TCP/IP通信その2</a></h1> <pre><code> // Create the thread. thread::spawn(move || { handler(stream).unwrap_or_else(|error| eprintln!("{:?}", error)); }); </code></pre> <p><a href="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/5ac9fa3b390b658160717a7c1ef5008a5d7a26458e30d.gif?mw=700" alt="KIFUWARABE_80x100x8_01_Futu.gif" /></a><br /> 「 スポーンしたあとは どうすんだぜ☆?」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 Rust でどう書けばいいのか分からん☆ とりあえず <code>quit</code> を送ったらループから抜けるようにするかだぜ☆」</p> <p><a target="_blank" rel="nofollow noopener" href="https://doc.rust-lang.org/1.2.0/std/io/struct.BufStream.html">Struct std::io::BufStream</a><br /> <a target="_blank" rel="nofollow noopener" href="https://crates.io/crates/bufstream">bufstream 0.1.4</a></p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑Read も Write もしたいバッファー・ストリームは どう書けばいいんだぜ☆?」</p> <pre><code>bufstream = "0.1.4" </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑これを <code>Cargo.toml</code> に書いて☆、」</p> <pre><code>extern crate bufstream; </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑これを main.rs の冒頭に書いておくかだぜ☆」</p> <pre><code> // Buffering. let mut reader = BufReader::new(&stream); let mut writer = BufWriter::new(&stream); </code></pre> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑いや、そんなことをしなくても これでいけたのか……☆?」</p> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 ↑ <code>\n</code> を末尾に付けておかないと <code>read_line</code> が いつまで経っても終わらないぜ☆」</p> <h1 id="ソケット間で共有するスコープとかない"><a href="#%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88%E9%96%93%E3%81%A7%E5%85%B1%E6%9C%89%E3%81%99%E3%82%8B%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%97%E3%81%A8%E3%81%8B%E3%81%AA%E3%81%84">ソケット間で共有するスコープとかない</a></h1> <p><a href="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3da2d4690cf2c3f101c5cbc0e48729f55d7a25f728bdc.gif?mw=700" alt="KITASHIRAKAWA_Chiyuri_80x100x8_01_Futu.gif" /></a><br /> 「 そうか、クライアントのスレッド と サーバーのスレッド との間は グローバル変数でやりとりするのではなく、<br /> 通信して やりとりするのかだぜ☆」</p> <p><a href="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/058791c2dd4c1604ce1bd9ec26d490ae5d7a271c2fbbc.gif?mw=700" alt="OKAZAKI_Yumemi_80x80x8_02_Syaberu.gif" /></a><br /> 「 Rust で Postgresql データベースを使えたりしないの?」</p> <p><書きかけ></p> むずでょ tag:crieit.net,2005:PublicArticle/14889 2019-03-28T16:09:17+09:00 2019-04-03T00:17:03+09:00 https://crieit.net/posts/T-From-U-impl-From-Wrapper-U-for-Wrapper-T T: From<U>のときimpl From<Wrapper<U>> for Wrapper<T>ができない <h1 id="T: From&lt;U&gt;のときimpl From&lt;Wrapper&lt;U&gt;&gt; for Wrapper&lt;T&gt;ができない"><a href="#T%3A+From%26lt%3BU%26gt%3B%E3%81%AE%E3%81%A8%E3%81%8Dimpl+From%26lt%3BWrapper%26lt%3BU%26gt%3B%26gt%3B+for+Wrapper%26lt%3BT%26gt%3B%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84">T: From<U>のときimpl From<Wrapper<U>> for Wrapper<T>ができない</a></h1> <p>これができない。</p> <pre><code class="rust">struct Wrapper<T>(T); impl<T, U> From<Wrapper<U>> for Wrapper<T> where T: From<U>, { fn from(p: Wrapper<U>) -> Wrapper<T> { Wrapper(p.0.into()) } } fn main() { let a = Wrapper::<i32>(42); let _: Wrapper<f32> = a.into(); } </code></pre> <h2 id="エラー内容"><a href="#%E3%82%A8%E3%83%A9%E3%83%BC%E5%86%85%E5%AE%B9">エラー内容</a></h2> <pre><code> Compiling playground v0.0.1 (/playground) error[E0119]: conflicting implementations of trait `std::convert::From<Wrapper<_>>` for type `Wrapper<_>`: --> src/main.rs:3:1 | 3 | / impl<T, U> From<Wrapper<U>> for Wrapper<T> 4 | | where 5 | | T: From<U>, 6 | | { ... | 9 | | } 10 | | } | |_^ | = note: conflicting implementation in crate `core`: - impl<T> std::convert::From<T> for T; error: aborting due to previous error For more information about this error, try `rustc --explain E0119`. error: Could not compile `playground`. To learn more, run the command again with --verbose. </code></pre> <hr /> <p><a target="_blank" rel="nofollow noopener" href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=c2684882a6c547090142be06ca64f8ce">実行できるコード</a></p> <p>おそらく同じ型同士のFrom(<code>impl From<T> for T</code>)と衝突している。</p> block tag:crieit.net,2005:PublicArticle/14698 2018-12-27T18:27:22+09:00 2018-12-27T18:27:22+09:00 https://crieit.net/posts/T-T ユーザー定義型 * Tは実装できるがT * ユーザー定義型は実装できない <h1 id="ユーザー定義型 * Tは実装できるがT * ユーザー定義型は実装できない"><a href="#%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E5%AE%9A%E7%BE%A9%E5%9E%8B+%2A+T%E3%81%AF%E5%AE%9F%E8%A3%85%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%8CT+%2A+%E3%83%A6%E3%83%BC%E3%82%B6%E3%83%BC%E5%AE%9A%E7%BE%A9%E5%9E%8B%E3%81%AF%E5%AE%9F%E8%A3%85%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84">ユーザー定義型 * Tは実装できるがT * ユーザー定義型は実装できない</a></h1> <h2 id="例"><a href="#%E4%BE%8B">例</a></h2> <p>任意の型の要素を持つ<code>Vector1<T></code>を定義して<code>T * T</code>が可能であれば<code>Vector1<T> * T</code>と<code>T * Vector1<T></code>ができるようにしたいとします。</p> <pre><code class="rust">struct Vector1<T> { x: T } // T * Tが可能であれば // Vector1<T> * Tと // T * Vector1<T>を可能にしたい </code></pre> <p>しかし<code>Vector1<T> * T</code>は実装できるのですが<code>T * Vector1<T></code>は実装できません。</p> <h2 id="とりあえず実装してみる"><a href="#%E3%81%A8%E3%82%8A%E3%81%82%E3%81%88%E3%81%9A%E5%AE%9F%E8%A3%85%E3%81%97%E3%81%A6%E3%81%BF%E3%82%8B">とりあえず実装してみる</a></h2> <pre><code class="rust">use std::ops; struct Vector1<T> { x: T, } impl<T> ops::Mul<T> for Vector1<T> where T: ops::Mul<T, Output = T>, { type Output = Vector1<T>; fn mul(self, rhs: T) -> Vector1<T> { Vector1 { x: self.x * rhs } } } impl<T> ops::Mul<Vector1<T>> for T where T: ops::Mul<T, Output = T>, { type Output = Vector1<T>; fn mul(self, rhs: Vector1<T>) -> Vector1<T> { Vector1 { x: self * rhs.x } } } fn main() {} </code></pre> <p>エラーが出ました。</p> <pre><code> | 18 | / impl<T> ops::Mul<Vector1<T>> for T 19 | | where 20 | | T: ops::Mul<T, Output = T>, 21 | | { ... | 26 | | } 27 | | } | |_^ type parameter `T` must be used as the type parameter for some local type | = note: only traits defined in the current crate can be implemented for a type parameter </code></pre> <hr /> <p>Rust Playgroundでエラーを確認できます。<br /> <a target="_blank" rel="nofollow noopener" href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7d83e5369d7c227e28e27f408d7d45b7">https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=7d83e5369d7c227e28e27f408d7d45b7</a></p> block