Rust에서 OnceLock과 LazyLock은 둘 다 "데이터를 딱 한 번만 초기화하고, 이후에는 재사용한다"는 목적을 가진 동시성(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
