###### 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