[Rust] 전역 불변 객체 할당하기
2023.06.26 작성
2024.07.29 수정
Rust의 전역변수/상수 매커니즘에는 다소 한계가 있는 편이었다.
가변성 변수로서 사용하는 전역 객체에는 제약을 가하는 것이 당연히 맞지만, 불변성을 가지는 객체를 만드는 것까지 어렵게 만들어놨다는게 단점이다.
아래와 같이 한번 만들고 read-only로만 사용할 HashMap 객체를 만들어쓰려고 해도, 이게 생각보다 쉽지가 않다.
일단 전역변수는 반드시 초기화가 되어야하는데, 전역변수의 초기화 식은 const-표현식만 들어갈 수 있어서 HashMap::new 같은 생성 함수 자체를 사용할 수가 없다.
1.70부터는 조금 괜찮은 방법이 생겨서 한결 나아지게 된 상황이다.
1.70.0 이전 버전에서: lazy_static
lazy_static 같이 흑마술을 부려주는 서드파티를 사용하면 복잡한 값들에 대한 전역객체를 적당히 만들 수 있다.
아래는 위의 코드에 lazy_static을 적용해서 전역객체로 만든 예시다.

여기엔 단점이 좀 있는데, 이상한 흑마술로 도배된 구현체라서 저걸로 만들어진 객체는 일부 기능들을 활용하기 어렵다는 것이다.
이를테면, 원래 해시맵에 있는 Debug display 기능이 날라간다던가...
1.70.0 이후: OnceLock
1.70.0부터는 OnceLock을 이용해서 좀더 깔끔한 구조로 전역객체를 정의할 수 있다.
우선, 기존의 객체를 OnceLock으로 감싸서 전역변수로 할당하고, 이후 로직에서 적절히 접근해서 값을 초기화하면 된다.
근데 이것도 영 깔끔하지는 못하다.
더 관리가 용이한 방법은, 전역객체 접근용 함수를 따로 만들어주는 것이다.
아래 코드는 get_or_init으로 최초 접근시에만 값을 초기화하고, 이후에는 저장된 값을 반환토록 한다.

전역변수를 아예 함수 스코프 안에 가두는 것도 좋은 방법이다.

동시성이 필요하지 않다면 OnceCell을 사용할 수 있다.
1.80.0 이후: LazyLock
근데 위의 OnceLock은 쓰는 것 자체에는 문제가 없지만, 사용할때마다 get_or_init 같은걸 써서 초기화까지 직접 책임져야한다는게 상당히 불편했다.
1.80 버전에서는 보다 개선된 형태의 전역변수 처리 매커니즘이 나왔는데, LazyLock이 그것이다.
사용법은 매우 간단하다. 타입은 LazyLock으로 하고, 초기화도 선언 시점에 함께 해줄 수 있다.

쓸때도 그냥 쓰면 된다. 자동으로 역참조되고, 그냥 전역 상수처럼 사용할 수 있다.

동시성이 필요하지 않다면 LazyCell을 사용할 수 있다.
참조
https://www.reddit.com/r/rust/comments/1416qul/how_to_replace_lazy_static_with_the_new_oncecell/