###### tags: `redis` `Rust` # Redis in Rust redis-rs クレートを使って redisをrustのプログラム上から操作する https://docs.rs/redis/0.11.0/redis/ # 注意点 いくつかのTraitが、rlsの補完に出てこない - Commands : set, get, mset, lpush など、redisのコマンドに対応するメソッド - PubSubCommands : PubSub機能に関するコマンド extern crate redis; に加えて、 use でこれらのトレイトを有効にして、メソッドが動作するようにする。 redis::Commands のコマンドはマクロを使って実装されているようで、 補完が効かないので使いづらい # FromRedisValue と ToRedisArg - ToRedisArg : redisのコマンドへの引数を実装するトレイト - FromRedisValue : redisからの戻り値の振る舞いを実装するトレイト - RedisResult\<T>: redisの実行結果 Redisは実質的に、データベーススキーマの型が存在しない(int と strぐらい) Rustのデータ型とは一致しないので、それを補完するトレイトとしてこれらが実装されている。 これにより、rustからredisを操作するために、パラメータなどを与える際 - i32 - &str, String - Vec\<T> - Array などなど、様々な値が使える。(ToRedisArgを実装されているため) また、コンパイルするときに、RedisResultの型を指定するように求められる事があるので、 戻り値を使うことがなくても 代入文で戻り値を取得しなければならない。 ```rust= let _: () = con.publish("a", "hello").unwrap(); ``` _ という名前の () ユニット型で、戻り値の型を示しつつ捨てる。 RedisResultを処理しないとコンパイルできない # 基本形 - clientを作成 - connectionを作成 - pubsubの接続を作成 ```rust= extern crate redis; use redis::Commands; fn main() { let client = redis::Client::open("redis://127.0.0.1/").expect("error"); let mut con = client.get_connection().expect("connect error"); let mut pubsub = con.as_pubsub(); } ``` チュートリアルなどでは、 ```rust= let client = redis::Client::open("redis://127.0.0.1/")?; ``` のように、 ? マークが使われている。 これはtry!マクロの短縮形で、もしもredis::RedisResult 型でErrが起きたら Err()をリターンするというもの。 main()では使えないので、代わりに unwrap() や expect() を使う main() では、戻り値を指定しないので、 RedisResult型を戻り地とするような関数で使うと良い Init() とかを作って初期化するといいかも # key-valueのCRUD ```rust= extern crate redis; use redis::{Client, Commands, ControlFlow, PubSubCommands}; fn main() { let client = redis::Client::open("redis://127.0.0.1/").expect("error"); let mut con = client.get_connection().expect("connect error"); // Create let _:() = con.set("myKey", 1).unwrap(); // Read let myKey: i32 = con.get("myKey").unwrap(); println!("myKey: {}", myKey); // Update let _:() = con.set("myKey", 100).unwrap(); // Delete let result: i32 = con.del("myKey").unwrap(); println!("削除結果: 1 => 成功, 0 => 失敗"); println!("del result for myKey: {}", result); } ``` Connection.~~~ で、redisのコマンドそのままを使える。 引数にredisコマンドの引数。 これらのコマンドには必ず戻り値がある(と思う)。 **FromRedisValue** # PubSub ## 受信 ```rust= let mut pubsub = con.as_pubsub(); pubsub.subscribe("a").unwrap(); loop{ let msg = pubsub.get_message().unwrap(); let payload: String = msg.get_payload().unwrap(); println!("channel '{}': {}", msg.get_channel_name(), payload); } ``` con.as_pubsub() の PubSub オブジェクトは、 subscriptionしかできない。 "a" という名前のチャンネルをsubscribbeする。 - pubsub.get_message() 関数は、メッセージが来るまで待機する - ~~Rustは非同期IOのサポートが無いので、timeoutが実装されてない - pubsub.set_read_timeout(During) で読み込みのタイムアウトを設置できる - msgはメッセージの様々な情報をまとめたオブジェクト - メッセージ本体は msg.get_payload() で取得する。 as_pubsub() で pubsubを作成すると、元のconの借用はpubsubに移る ライフタイム'aで管理される ## 送信 ```rust= extern crate redis; use redis::{Client, Commands, ControlFlow, PubSubCommands}; use redis::Cmd; fn main() { let client = redis::Client::open("redis://127.0.0.1/").expect("error"); let mut con = client.get_connection().expect("connect error"); let _: () = con.publish("a", "hello").unwrap(); let _: () = con.publish("a", "world").unwrap(); } ``` ## アンチパターン: pub と sub を同じクライアントでやろうとする ```rust= extern crate redis; use redis::{Client, Commands, ControlFlow, PubSubCommands}; use redis::Cmd; fn main() { // subscription用のpubsub let client = redis::Client::open("redis://127.0.0.1/").expect("error"); let mut con = client.get_connection().expect("connect error"); let mut pubsub = con.as_pubsub(); // publish用のcon let client = redis::Client::open("redis://127.0.0.1/").expect("error"); let mut con = client.get_connection().expect("connect error"); pubsub.subscribe("a").unwrap(); loop{ let msg = pubsub.get_message().unwrap(); let payload: String = msg.get_payload().unwrap(); println!("channel '{}': {}", msg.get_channel_name(), payload); let _: () = con.publish("a", "message").unwrap(); } } ``` 上記のように、 1. メッセージが取得 2. メッセージを表示 -> メッセージを同じチャンネルにpublish すると、2. が無限ループする - 返信は別のチャンネルを使う - 受信のみを処理して送信しない(1方向通信) などで活用する。 ## 参考 https://github.com/tkrs/rust-redis-pubsub-example/blob/master/src/main.rs