同じようなコードはマクロを使うことで繰り返し書かずに済むようにできます。
こんかいはlocal_thread_intern_arenaマクロを作って使いまわしできるようにしてみました。
PartialEq の実装をDerivingで自動的に実装できると良いと思うのですがそれはまた別の機会にやってみたいと思います。
定義の仕方は、以下のようにマクロを呼び出します:
thread_local_intern_arena!(ARENA: Arena -> Exp);
こうすることでARENAという名前のローカルスレッドな変数にExp型用のArena型のアリーナが作られます。使い方は以下のようにHash,Eq,PartialEq,AddressEqを実装し、internで同じアドレスを割り当てて使います:
#[derive(Debug,Hash,Eq)]
enum Exp {
Int(i8),
Add(EXP,EXP),
}
type EXP = &'static Exp;// boxのアドレスになってる。
impl AddressEq for Exp {}
impl PartialEq for Exp {
fn eq(&self,other:&Exp) -> bool {
self.addr_eq(other) ||
match (self,other) {
(Exp::Int(i1),Exp::Int(i2)) => *i1==*i2,
(Exp::Add(e1,e2),Exp::Add(e3,e4)) => e1.addr_eq(e3) && e2.addr_eq(e4),
(_, _) => false
}
}
}
thread_local_intern_arena!(ARENA: Arena -> Exp);
fn int(i:i8) -> EXP {
intern(Exp::Int(i))
}
fn add(e1:EXP,e2:EXP) -> EXP {
intern(Exp::Add(e1,e2))
}
fn eval(e:EXP) -> i8{
match e {
Exp::Int(i) => *i,
Exp::Add(e1,e2) => eval(e1)+eval(e2)
}
}
全ソースは以下のようになります:
fn leak<A>(a:A) -> &'static A {
Box::leak(Box::new(a))
}
trait AddressEq {
fn addr_eq(&self, other: &Self) -> bool {
self as *const _ == other as *const _
}
}
macro_rules! thread_local_intern_arena {
($(#[$attr:meta])* $vis:vis $name:ident: $t:ident -> $t2:ty) => {
use rustc_hash::FxHashMap;
use std::cell::RefCell;
struct $t (FxHashMap<&'static $t2, &'static $t2>);
impl $t {
fn new()->$t {
$t(FxHashMap::default())
}
fn intern(&mut self,e:$t2) -> &'static $t2 {
let pe = leak(e);
if let Some(x) = self.0.get(pe) {
unsafe {core::ptr::drop_in_place(pe as *const _ as *mut $t2)}
return *x;
}
self.0.insert(pe, pe);
pe
}
}
impl Drop for $t {
fn drop(&mut self) {
for (e, _) in self.0.iter() {
unsafe {core::ptr::drop_in_place(e as *const _ as *mut $t2)}
}
}
}
fn intern(e:$t2) -> &'static $t2 {
$name.with(|arena|{arena.borrow_mut().intern(e)})
}
thread_local!($(#[$attr])* $vis static $name: RefCell<$t> = RefCell::new($t::new()));
}
}
#[derive(Debug,Hash,Eq)]
enum Exp {
Int(i8),
Add(EXP,EXP),
}
type EXP = &'static Exp;// boxのアドレスになってる。
impl AddressEq for Exp {}
impl PartialEq for Exp {
fn eq(&self,other:&Exp) -> bool {
self.addr_eq(other) ||
match (self,other) {
(Exp::Int(i1),Exp::Int(i2)) => *i1==*i2,
(Exp::Add(e1,e2),Exp::Add(e3,e4)) => e1.addr_eq(e3) && e2.addr_eq(e4),
(_, _) => false
}
}
}
thread_local_intern_arena!(ARENA: Arena -> Exp);
fn int(i:i8) -> EXP {
intern(Exp::Int(i))
}
fn add(e1:EXP,e2:EXP) -> EXP {
intern(Exp::Add(e1,e2))
}
fn eval(e:EXP) -> i8{
match e {
Exp::Int(i) => *i,
Exp::Add(e1,e2) => eval(e1)+eval(e2)
}
}
#[test]
fn testa() {
assert_eq!(3, eval(intern(Exp::Add(intern(Exp::Int(1)),intern(Exp::Int(2))))));
assert_eq!(3, eval(add(int(1),int(2))));
assert_eq!(true, int(1).addr_eq(int(1)));
assert_eq!(true, add(int(1),int(2)).addr_eq(add(int(1),int(2))));
assert_eq!(false, int(2).addr_eq(leak(Exp::Int(2))));
assert_eq!(true, int(1)==int(1));
assert_eq!(true, add(int(1),int(2))==add(int(1),int(2)));
assert_eq!(true, int(2)==leak(Exp::Int(2)));
assert_eq!(false, add(int(1),int(2))==add(int(1),leak(Exp::Int(2))));
}
fn main() {
println!("{:?}", eval(intern(Exp::Add(intern(Exp::Int(1)),intern(Exp::Int(2))))));
println!("{:?}", eval(add(int(1),int(2))));
println!("{:?}", int(1));
println!("{:?}", int(1).addr_eq(int(1)));
println!("{:?}", add(int(1),int(2)).addr_eq(add(int(1),int(2))));
println!("{:?}", int(2).addr_eq(leak(Exp::Int(2))));
}
続き: