#概要
RustでHashMapを用いたPHP風の連想配列を作りました。
ほとんどの記事で、HashMapに特定の型のみを用いた使い方しかしていなかったので(というかそれで十分なのだろうけれど)、どんな型でも入るHashMapを作りました。
###経緯(飛ばしてもOK)
ironライブラリを用いてWebプログラミングの勉強をしていたとき、PHPのセッション配列がほしくなりました。
最初はenumにInteger(i64)
やBool(bool)
といったヴァリアントを持たせてHashMapに入れていたのですが、
if let Some(Value::Integer(value)) = ...;
と書いているとどうしても冗長に感じてきて、別の方法をとることにしました。
###実装
といっても、使うのはAny
トレイトだけです。Box
に包んで使います。
use std::any::Any;
use std::collections::HashMap;
let mut hashmap = HashMap::new();
hashmap.insert(
"key".to_owned(),
Box::new(0) as Box<Any>
);
関数にしていますが、値を取得する際は次のような処理になります。
// (Boxの中身の)値のイミュータブルな参照を返す
fn get_ref<'a, T: 'static>(hashmap: &'a HashMap<String, Box<Any>>, key: &str) -> Option<&'a T> {
if let Some(ref_boxed_value) = hashmap.get(key) {
let ref_value: &Any = ref_boxed_value.as_ref();
if let Some(ref_value) = ref_value.downcast_ref::<T>() {
return Some(ref_value);
}
}
None
}
// (Boxの中身の)値のミュータブルな参照を返す
fn get_mut_ref<'a, T: 'static>(hashmap: &'a mut HashMap<String, Box<Any>>, key: &str) -> Option<&'a mut T> {
if let Some(mut_ref_boxed_value) = hashmap.get_mut(key) {
use std::borrow::BorrowMut;
let mut_ref_value: &mut Any = mut_ref_boxed_value.borrow_mut();
if let Some(mut_ref_value) = mut_ref_value.downcast_mut::<T>() {
return Some(mut_ref_value);
}
}
None
}
// 次の二つはCloneトレイトを実装している必要がある
// (Boxの中身の)値のコピーを返す
fn get<T: 'static + Clone>(hashmap: &HashMap<String, Box<Any>>, key: &str) -> Option<T> {
if let Some(ref_boxed_value) = hashmap.get(key) {
let ref_value: &Any = ref_boxed_value.as_ref();
if let Some(ref_value) = ref_value.downcast_ref::<T>() {
let value = (*ref_value).clone();
return Some(value);
}
}
None
}
// HashMapから値を取り出す
// ※正確には、HashMapからremoveしたBoxの中身の値のコピーを返しています
fn remove<T: 'static + Clone>(hashmap: &mut HashMap<String, Box<Any>>, key: &str) -> Option<T> {
if let Some(boxed_value) = hashmap.remove(key) {
let ref_value: &Any = boxed_value.as_ref();
if let Some(ref_value) = ref_value.downcast_ref::<T>() {
let value = (*ref_value).clone();
return Some(value);
}
}
None
}
Boxの中身を直接取り出す方法がわからなかったので、get()
とremove()
は一度参照を受け取ってからそれをクローンしています。
そのため、独自の構造体を扱う場合は適宜#[derive(Clone)]
してやってください。get_ref()
とget_mut_ref()
だけであれば必要ありません。
###使用例
let mut hashmap = HashMap::new();
hashmap.insert("key".to_owned(), Box::new(0) as Box<Any>);
// 値のイミュータブルな参照を得る(Boxの中身のイミュータブルな参照を得る)
if let Some(ref_value) = get_ref::<u8>(&hashmap, "key") {
assert_eq!(ref_value, &0);
} else {
panic!("Error1");
}
// 値のミュータブルな参照を得る(Boxの中身のミュータブルな参照を得る)
if let Some(mut_ref_value) = get_mut_ref::<u8>(&mut hashmap, "key") {
assert_eq!(mut_ref_value, &mut 0);
*mut_ref_value += 1;
assert_eq!(mut_ref_value, &mut 1);
} else {
panic!("Error2");
}
// 値のコピーを得る(Boxの中身のコピーを得る)
if let Some(value) = get::<u8>(&hashmap, "key") {
assert_eq!(value, 1);
} else {
panic!("Error3");
}
// 値を取り出す(Boxの中身のコピーを得、HashMapから要素はなくなる)
if let Some(value) = remove::<u8>(&mut hashmap, "key") {
assert_eq!(value, 1);
} else {
panic!("Error4");
}
assert!(!hashmap.contains_key("key"));
###制約
Any
トレイトを実装している必要があるので、非'staticな参照もしくはそれを含む型を扱うことはできません。
こちらの記事にて詳しく解説されています。
##まとめ
型に厳しいRustだと連想配列は無理かな?と思っていたけれど、Boxが役立ちました。ただヒープに保存されるので高速な処理には向いていないと思われます。詳しくはないですが...