정적변수(static)과 상수(const)
정적 변수와 상수는 전역 스코프에서 값을 생성하는 두 가지 방법입니다. 전역 스코프에 생성된 값은 프로그램 수행 도중에 다른 값으로 이동되지 않으며, 메모리 상에서 그 위치가 변하지 않습니다.
상수(const
)
상수는 컴파일 할 때 그 값이 정해집니다. 그리고 그 값은 그 상수가 사용되는 모든 부분에서 인라인 됩니다:
const DIGEST_SIZE: usize = 3; const ZERO: Option<u8> = Some(42); fn compute_digest(text: &str) -> [u8; DIGEST_SIZE] { let mut digest = [ZERO.unwrap_or(0); DIGEST_SIZE]; for (idx, &b) in text.as_bytes().iter().enumerate() { digest[idx % DIGEST_SIZE] = digest[idx % DIGEST_SIZE].wrapping_add(b); } digest } fn main() { let digest = compute_digest("Hello"); println!("Digest: {digest:?}"); }
Rust RFC Book에 따르면 상수는, 그 상수가 사용되는 곳에 인라인 됩니다.
const
값을 생성할 때에는 const
로 마킹된 함수만이 호출 가능하며, 이 함수들은 컴파일 시에 호출이 됩니다. 물론 const
함수들을 런타임에 호출하는 것도 가능합니다.
정적변수(static
)
정적 변수는 프로그램이 수행되는 동안 유지가 됩니다. 그러므로 다른 변수로 이동(move)되지 않습니다:
static BANNER: &str = "Welcome to RustOS 3.14"; fn main() { println!("{BANNER}"); }
Rust RFC Book에서 언급한 바와 같이, 정적 변수는 별도의 메모리 공간을 가지며, 인라인 되지 않습니다. 정적 변수는 안전하지 않은(unsafe) 러스트와 임베디드 시스템용 코드에서 유용합니다. 이들의 수명은 프로그램이 수행되는 전체 시간과 동일합니다. 전역 스코프를 가진 어떤 값이, 메모리 상에 단 하나만 존재해야 한다는 요구조건이 없다면, 정적 변수 대신 const
를 쓰는 것이 옳습니다.
static
변수들은 어떤 스레드에서도 접근 가능하기 때문에, Sync
트레잇을 구현해야 합니다. 이 변수를 읽고 쓰려면 Mutex
로 감싸거나, atomic 연산을 써야 합니다. static
변수를 mutable하게 선언할 수도 있지만, 이 경우 동기화 작업을 수동으로 해 주어야 합니다. 그래서 그러한 변수를 접근하는 코드는unsafe
로 명시적으로 표시가 되어야 합니다. “안전하지 않은 러스트“를 배울 때 mutable statics 부분에서 좀 더 자세히 다루겠습니다.
- 러스트의
const
는 C++의constexpr
과 매우 비슷합니다. - 반면에 러스트의
static
은 C++의const
나 가변 정적 변수(mutable global variable)와 훨씬 더 유사합니다. static
은 객체에 정체성을 부여합니다. 정체정이란 메모리 상에서의 주소, 그리고 내부 상태를 의미합니다.- 프로그램 수행시 그 값이 정해지는 상수가 필요한 경우는 드뭅니다. 그러나 그렇다고 해도, 정적 변수를 사용하는 것 보다는 더 유용하고 안전합니다.
thread_local
데이터는std::thread_local
매크로를 이용하여 생성할 수 있습니다.
속성 비교 테이블:
속성 | 정적(static) 변수 | 상수(constant) |
---|---|---|
메모리 상에 주소가 있는가 | 예 | 아니오(인라인 됨) |
프로그램이 수행되는 동안 계속 살아 있는가 | 예 | 아니오 |
변경 가능한가 | 예 (그러나 안전하지 않음) | 아니오 |
컴파일시 그 값이 결정되는가 | 예 (컴파일시 초기화 됨) | 예 |
사용되는 곳에 인라인 되는가 | 아니오 | 예 |