OnceLock과 LazyLock 비교 설명

Rust에서 OnceLockLazyLock은 둘 다 "데이터를 딱 한 번만 초기화하고, 이후에는 재사용한다"는 목적을 가진 동시성(Concurrency) 도구입니다.

기존에는 lazy_static이나 once_cell 같은 외부 라이브러리를 써야 했지만, 이제는 Rust 표준 라이브러리(std::sync)에 정식 포함되었습니다.

두 개념이 헷갈리는 이유는 목적이 같기 때문인데요, 핵심 차이는 "초기화 코드를 언제, 어디서 정의하느냐"에 있습니다. 아주 쉽게 비유와 함께 알아볼게요!


1. 한 줄 요약 (비유로 이해하기)

  • OnceLock (수동 초기화 상자): 빈 상자를 먼저 만들어 두고, 나중에 내가 원할 때 코드를 실행해서 상자를 채우는 방식. (주문 제작형)
  • LazyLock (자동 게으른 상자): 상자를 만들 때 '채우는 방법(함수)'을 미리 담아두고, 나중에 누군가 상자를 처음 열어볼 때 자동으로 채워지는 방식. (주문 시 자동 조리형)

2. OnceLock: "데이터는 나중에 내가 직접 넣을게"

OnceLock은 생성할 때 내부에 아무것도 가지고 있지 않습니다. 프로그램이 실행되는 도중에 내가 원하는 시점set()이나 get_or_init()을 호출해서 값을 딱 한 번만 집어넣습니다.

(1) 언제 쓸까?

초기화에 필요한 값이 런타임(프로그램 실행 중)에 결정될 때 사용합니다. 예를 들어 사용자 키보드 입력값, 파일에서 읽어온 설정값, CLI 인자(Arguments) 등을 전역 변수에 한 번만 세팅하고 싶을 때 딱입니다.

(2) 코드 예시

use std::sync::OnceLock;

// 1. 처음엔 비어있는 상자를 전역 변수로 선언합니다.
static CONFIG_DIR: OnceLock<String> = OnceLock::new();

fn main() {
    // 2. 프로그램 실행 중(런타임)에 값을 구합니다.
    let user_input = String::from("/home/user/app_config");

    // 3. 딱 한 번 값을 세팅합니다. (이미 채워져 있다면 실패함)
    CONFIG_DIR.set(user_input).unwrap();

    // 4. 이후 어디서나 안전하게 꺼내 씁니다.
    println!("설정 경로: {}", CONFIG_DIR.get().unwrap());
}

3. LazyLock: "처음 쓸 때 자동으로 계산해줘"

LazyLock은 생성할 때 "값을 어떻게 만들지"에 대한 클로저(초기화 함수)를 미리 등록해 둡니다. 프로그램이 켜지자마자 실행되는 것이 아니라, 누군가 이 변수에 처음 접근하는 순간(Dereference) 코드가 실행되면서 값이 채워집니다.

(1) 언제 쓸까?

초기화하는 데 비용이 많이 드는 작업(예: 정규식 컴파일, 대용량 데이터 로드, 복잡한 그래픽 리소스 생성 등)이 필요하지만, 프로그램 실행 중에 이 변수를 아예 안 쓸 수도 있을 때 유용합니다. 안 쓰면 리소스를 아예 낭비하지 않으니까요(Lazy Evaluation).

(2) 코드 예시

use std::sync::LazyLock;
use std::collections::HashMap;

// 1. 상자를 만들면서 '어떻게 채울지' 미리 정의해 둡니다.
static DICTIONARY: LazyLock<HashMap<&str, &str>> = LazyLock::new(|| {
    println!("딕셔너리 초기화 중... (이 문구는 딱 한 번만 출력됩니다)");
    let mut m = HashMap::new();
    m.insert("apple", "사과");
    m.insert("banana", "바나나");
    m
}); // <--- 이 시점에는 아직 HashMap이 안 만들어졌음!

fn main() {
    println!("프로그램 시작!");
    
    // 2. 처음으로 DICTIONARY에 접근하는 순간 초기화 블록이 실행됩니다.
    println!("apple 뜻: {}", DICTIONARY.get("apple").unwrap());
    
    // 3. 두 번째 접근할 때는 이미 만들어진 값을 바로 가져옵니다.
    println!("banana 뜻: {}", DICTIONARY.get("banana").unwrap());
}

4. 핵심 차이점 비교 table

특징 OnceLock LazyLock
초기화 시점 개발자가 set()이나 get_or_init()명시적으로 호출할 때 변수에 처음 접근해서 쓰려고 할 때 (자동)
초기화 정의 위치 값을 넣는 런타임 코드 중간 어디서나 `LazyLock::new(
실패 가능성 set() 호출 시 이미 값이 있으면 에러(Err) 반환 가능 처음 접근할 때 무조건 성공함 (호출 실패 개념 없음)
주요 용도 외부 입력값, 파일 시스템 경로 등 런타임 데이터 저장 정규식(Regex) 컴파일, 폰트/이미지 로드 등 무거운 고정 데이터

요약하자면

  • "설정 파일 경로처럼 실행해 봐야 알 수 있는 값"을 안전하게 딱 한 번 전역으로 세팅하고 싶다면 : OnceLock
  • "텍스트 파싱용 정규식처럼 만드는 데 비용이 드는데, 필요할 때 알아서 척척 켜지게" 하고 싶다면 : LazyLock