Rust 디자인 패턴: 가드(Guard) 패턴과 RAII

· by 박승재

가드(Guard) 패턴은 객체 외부에서 객체 내부로의 접근을 제한하기 위한 디자인 패턴입니다.

만약 내부 값에 접근하기 위해서는, 래퍼 구조체(Wrapper Struct)인 가드(Guard)를 통해야 합니다.

이때, 가드 객체를 일반적인 객체처럼 다루기 위해 DerefDerefMut 트레잇(Trait)을 구현합니다.

Rust에서는 역참조 강제(Deref Coercion)을 통해 &와 *를 생략해도 정상적으로 동작합니다.

참고: Deref 트레잇을 가지고 스마트 포인터를 평범한 참조자와 같이 취급하기

가드 패턴은 주로 Drop 트레잇과 함께 RAII(Resource Acquisition is Initialisation)에 사용합니다.

RAII는 “자원의 획득은 초기화다”라는 뜻으로, 객체의 생성-소멸 수명 주기에 따라 메모리 등의 자원을 할당하고 해제하는 코딩 기법입니다.

참고: C++ RAII

표준 라이브러리에서는 MutexGuard가 대표적인 가드 패턴의 예시입니다.

let mutex = Mutex::new(0);
let guard = mutex.lock().unwrap();
*guard += 1;
drop(guard);

lock 함수는 값에 락(Lock)을 걸고, 값을 가지고 있는 MutexGuard를 반환합니다.

pub struct MutexGuard<'a, T: ?Sized + 'a> {
    lock: &'a Mutex<T>,
    poison: poison::Guard,
}

guardDeref가 구현되어 있기 때문에 *guard내부 값에 접근할 수 있습니다.

impl<T: ?Sized> Deref for MutexGuard<'_, T> {
    type Target = T;

    fn deref(&self) -> &T {
        unsafe { &*self.lock.data.get() }
    }
}

impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
    fn deref_mut(&mut self) -> &mut T {
        unsafe { &mut *self.lock.data.get() }
    }
}

drop이 호출되어 MutexGuard가 소멸되면 Drop 트레잇 구현의 unlock이 호출되어 자동으로 락이 풀리게 됩니다.

impl<T: ?Sized> Drop for MutexGuard<'_, T> {
    #[inline]
    fn drop(&mut self) {
        unsafe {
            self.lock.poison.done(&self.poison);
            self.lock.inner.raw_unlock();
        }
    }
}

가드 패턴과 RAII를 이용하면, 자원의 사용이 끝난 이후에 자원을 해제하지 않아 발생하는 버그를 예방할 수 있습니다.

참고: RAII with guards