에러와 메모리 연습 문서의 문제 4 풀이 코드다.

//! 연습 p4: RefCell로 만든 이벤트 버스에서 핸들러가 emit 도중 subscribe를 부르면
//! "RefCell already borrowed"로 panic하는 버그를 재현하고, 핸들러 목록을 복사해 빌림을 먼저 끝내 고친 풀이.
//! 의존성: 없음 (표준 라이브러리만)
//! 실행: python3 "_시스템/Rust 계획/rs_check.py" "Rust/4.연습문제/30_errors-memory-practice_p4.rs"

use std::cell::RefCell;
use std::rc::Rc;

type Handler = Rc<dyn Fn(&Bus, &str)>;

#[derive(Default)]
struct Bus {
    handlers: RefCell<Vec<Handler>>,
    log: RefCell<Vec<String>>,
}

impl Bus {
    fn subscribe(&self, h: Handler) {
        self.handlers.borrow_mut().push(h);
    }

    /// 문제의 코드: 목록을 빌린 채로 핸들러를 부른다.
    fn emit_buggy(&self, ev: &str) {
        for h in self.handlers.borrow().iter() {
            h(self, ev);
        }
    }

    /// 풀이: Rc 복사본 목록을 만들고 빌림을 끝낸 뒤 부른다.
    fn emit(&self, ev: &str) {
        let snapshot: Vec<Handler> = self.handlers.borrow().clone();
        for h in snapshot {
            h(self, ev);
        }
    }
}

fn logger() -> Handler {
    Rc::new(|bus: &Bus, ev: &str| bus.log.borrow_mut().push(format!("log {ev}")))
}

/// "login"을 받으면 새 핸들러를 하나 더 등록한다.
fn auto_subscriber() -> Handler {
    Rc::new(|bus: &Bus, ev: &str| {
        if ev == "login" {
            bus.subscribe(logger());
        }
    })
}

fn main() {
    std::panic::set_hook(Box::new(|_| {}));

    // 버그 재현
    let bus = Bus::default();
    bus.subscribe(logger());
    bus.subscribe(auto_subscriber());
    let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| bus.emit_buggy("login")));
    let p = r.expect_err("panic이 나야 한다");
    let msg = p
        .downcast_ref::<&str>()
        .map(|s| s.to_string())
        .or_else(|| p.downcast_ref::<String>().cloned())
        .unwrap_or_default();
    println!("buggy: panic {msg:?}, log = {:?}", bus.log.borrow());
    assert_eq!(msg, "RefCell already borrowed");
    assert_eq!(*bus.log.borrow(), vec!["log login"]); // 첫 핸들러는 돌았다

    // 풀이
    let bus = Bus::default();
    bus.subscribe(logger());
    bus.subscribe(auto_subscriber());
    bus.emit("login");
    assert_eq!(bus.handlers.borrow().len(), 3);
    assert_eq!(*bus.log.borrow(), vec!["log login"]); // 새 핸들러는 다음 이벤트부터
    bus.emit("click");
    println!("fixed: log = {:?}", bus.log.borrow());
    assert_eq!(
        *bus.log.borrow(),
        vec!["log login", "log click", "log click"]
    );

    // try_borrow_mut로 충돌을 panic 대신 값으로 받기
    let held = bus.handlers.borrow();
    assert!(bus.handlers.try_borrow_mut().is_err());
    drop(held);
    assert!(bus.handlers.try_borrow_mut().is_ok());

    println!("ALL CHECKS PASSED");
}