Rust에서 리터럴(literal)은 소스 코드에 직접 값을 표현한 것이며(예: 5, “hello”, true), 변수(variable)는 프로그램 실행 중에 값을 저장하고 변경할 수 있는 이름이 있는 저장 공간입니다(예: let x = 5;).
1. 리터럴과 변수
가. 리터럴(Literals)
프로그래밍에서 리터럴은 변수(variable)나 상수(const)와 같이 특정 데이터 유형의 값을 직접 나타내는 표현 방식입니다. 예를 들어, 숫자 10, 문자 ‘a’, 문자열 “hello” , true, false 등이 모두 리터럴입니다.
- 직접적인 값:리터럴은 프로그램 실행 중에 변경될 수 없는 고정된 값 자체를 의미합니다.
- 불변성: 리터럴은 정의된 이후에 그 값을 변경할 수 없습니다.
- 표현식:때로는 코드에서 특정 값을 계산하는 표현식 자체가 리터럴로 사용되기도 합니다. 예를 들어, (2 + 3)과 같이 계산 결과가 고정된 값으로 나타나는 경우.
나. 변수(Variables)
- 이름이 있는 저장소: 변수는 메모리에 값을 저장하는 이름이 붙은 공간입니다.
- 가변성 또는 불변성: Rust에서는 let x = 5;처럼 불변 변수도 만들 수 있고, let mut x = 5;처럼 가변 변수도 만들 수 있습니다. 가변 변수는 값을 변경할 수 있습니다.
- 모든 타입 가능: 변수는 기본 타입은 물론, 구조체나 참조 등 다양한 데이터 타입의 값을 저장할 수 있습니다.
- 데이터 저장과 조작: 변수는 프로그램에서 필요에 따라 데이터를 저장하거나 변경하는 데 사용됩니다.
다. 비교표
항목 | 리터럴(Literal) | 변수(Variable) |
---|---|---|
표현 방식 | 값을 직접 표현 | 이름이 있는 저장 공간 |
변경 가능 여부 | 변경 불가 (immutable) | 변경 가능 또는 불가능 (mutable / immutable) ※ Rust에서는 기본값이 immutable |
저장 방식 | 변수처럼 메모리에 저장되지 않음 | 메모리에 저장됨 |
사용 목적 | 상수값(변하지 않는 값) 표현 | 데이터 저장 및 조작 |
예시 | 5, “hello”, true | let x = 5;에서 x let mut y = “hello”;에서 y |
라. 예제 1
fn main() {
let x = 10; // 10은 정수 리터럴
let y = "hello"; // "hello"는 문자열 리터럴
println!("x = {}", x);
println!("y = {}", y);
}
여기서:
- 10은 숫자 리터럴 (미리 정의된 값)
- “hello”는 문자열 리터럴 (미리 코드에 써 넣은 값)
즉, 이런 리터럴 값은 코드에서 바로 보이고 바뀌지 않는 상수 같은 값입니다.
마. 예제 2
fn main() {
let a = 5; // 5는 리터럴
let b = a + 3; // 3도 리터럴, b는 변수
println!("b = {}", b); // 출력: b = 8
}
- 여기서 5와 3은 literal 또는 literal value (미리 정해진 값)로 바뀔 수 없는데,
- a와 b는 변수로 나중에 바뀔 수 있습니다. 그러나, Rust에서는 mut가 없으면 불변이 기본임
바. 요약
Rust에서 10, “hello”, true 같은 값들은 literal로서
코드에 직접 써 넣은, 미리 정의된 값들입니다.
2. 리터럴과 값(value)
가. Literal (리터럴)
“소스 코드에 직접 쓰인 값”
즉, 고정된 형태의 값을 코드에 명시한 것입니다.
42 // 정수 리터럴
3.14 // 부동소수점 리터럴
"hello" // 문자열 리터럴
true // 불리언 리터럴
'b' // 문자 리터럴
리터럴은 컴파일 타임에 결정되며 변하지 않습니다.
나. Value (값)
프로그램이 실행될 때 메모리에 존재하는 데이터입니다.
리터럴, 변수, 연산 결과 등 다양한 방식으로 생성될 수 있습니다.
let x = 5; // 리터럴 5를 변수 x에 저장 → value는 5
let y = x + 2; // 연산 결과 7도 value
let s = String::from("hi"); // value는 "hi"라는 힙에 저장된 문자열
value는 runtime의 개념이며, 리터럴은 value를 만들기 위한 한 방법입니다.
다. 비교표
항목 | Literal | Value |
---|---|---|
정의 | 코드에 직접 적힌 고정 값 | 실행 중 사용되는 데이터 |
예시 | “hello”, 3.14, true | 변수에 저장된 값, 함수의 반환값 등 |
생성 시점 | 컴파일 타임 | 런타임 |
변경 가능성 | 불변 | 변경 가능 (가변 변수에 저장된 경우) |
위치 | 코드에 직접 작성됨 | 메모리에 존재 |
라. 관계 요약
- 리터럴은 value를 만드는 수단 중 하나입니다.
- 모든 리터럴은 value지만, 모든 value가 리터럴은 아닙니다.
예:
let a = 1 + 2;
// 1과 2는 리터럴 → 3은 value (리터럴이지만 연산 결과이기도 함)
let b = a * 2;
// b의 값 6은 value지만 literal은 아님 (런타임 연산 결과)
3. literal과 const, static
구분 | 의미 | 특징 |
---|---|---|
literal | 코드에 직접 써 넣은 값 | 미리 정의된 값. 변수나 상수에 저장 가능 |
const | 컴파일 시점 상수 | 항상 값이 고정, 함수 밖/안 모두 사용 가능, 메모리 없음 |
static | 정적 변수 (전역 상수) | 프로그램 전체에서 공유, 고정 메모리 공간 사용 |
가. 예제
const CONST_VALUE: i32 = 10;
static STATIC_VALUE: i32 = 20;
fn main() {
let literal = 5; // 5는 literal
println!("literal: {}", literal);
println!("const: {}", CONST_VALUE);
println!("static: {}", STATIC_VALUE);
}
나. 설명
- 5는리터럴(literal)
→ 그냥 코드에 직접 쓴 고정된 값 - CONST_VALUE는 const
→ 컴파일 시간에 값이 확정됨. 메모리에 저장되지 않음.
→ const는 let처럼 지역적으로도 가능하지만 항상 값이 바뀌지 않음 - STATIC_VALUE는 static
→ 프로그램이 끝날 때까지 살아있는 고정 메모리 주소에 저장됨
→ 전역 변수처럼 사용 가능
→ 다만static mut은 unsafe하게 써야 함
다. 비교표
항목 | 값이 고정됨 | 변경 가능성 | 메모리 위치 | 사용 시기 |
---|---|---|---|---|
literal | ✅ | ❌ | 스택 또는 인라인 | 코드 내 직접 |
const | ✅ | ❌ | 없음 (값 인라인) | 컴파일 시 |
static | ✅ | ❌ (기본) | 전역 메모리 | 런타임 전체 기간 |
라. static mut의 대체 수단
static mut COUNTER: i32 = 0;
fn main() {
unsafe {
COUNTER += 1;
println!("counter: {}", COUNTER);
}
}
- static mut은 동시성 문제 때문에 unsafe로 접근해야 함
Rust의const와 static은 값이 컴파일 타임에 반드시 결정되어야 하는데,
다음처럼 런타임 정보나 복잡한 로직으로 초기화해야 하는 경우에는 문제가 생깁니다:
static MY_STRING: String = String::from("hello"); // ❌ 에러!
🚫String은 컴파일 타임에 생성할 수 없기 때문에 static으로 직접 초기화 불가능
이럴 때 쓰는 게 바로 아래 세 가지입니다:
(1) const fn
- const fn은 컴파일 타임에 실행 가능한 함수를 정의합니다.
- const 상수나 const 표현식에서 사용할 수 있음
const fn square(x: i32) -> i32 {
x * x
}
const RESULT: i32 = square(4); // ✅ OK!
📌 단점: const fn은 제한이 많아서, String, Vec, 파일읽기 등은 못 씀
(2) lazy_static (deprecated 경향 있음)
- 복잡한 값도 런타임 최초 1회 초기화 후 전역처럼 사용 가능
- macro_use가 필요하고, 내부적으로 unsafe와 Mutex를 씀
#[macro_use]
extern crate lazy_static;
use std::collections::HashMap;
lazy_static! {
static ref CONFIG: HashMap<&'static str, i32> = {
let mut m = HashMap::new();
m.insert("threshold", 10);
m
};
}
fn main() {
println!("threshold = {}", CONFIG["threshold"]);
}
✅ 장점: 복잡한 초기화 가능
⚠️ 단점: 오래된 방식이고 macro 기반, 무거움
(3) once_cell (추천)
- 최신 Rust 커뮤니티에서는 lazy_static 대신 once_cell을 많이 씁니다.
- 런타임 최초 1회 초기화, macro 없이, 더 가볍고 안전
use once_cell::sync::Lazy;
use std::collections::HashMap;
static CONFIG: Lazy<HashMap<&'static str, i32>> = Lazy::new(|| {
let mut m = HashMap::new();
m.insert("threshold", 10);
m
});
fn main() {
println!("threshold = {}", CONFIG["threshold"]);
}
✅ 장점: lazy_static보다 깔끔하고 안전
🔒 sync::Lazy: 여러 스레드에서 안전하게 사용 가능
🧵 unsync::Lazy: 단일 스레드용으로 더 빠름
(4) const, static 등 비교표
방식 | 초기화 시점 | 복잡한 타입 가능 | 스레드 안전성 | 비고 |
---|---|---|---|---|
const | 컴파일 타임 | ❌ (기본 타입만) | N/A | 빠르고 간단 |
static | 컴파일 타임 | ❌ (기본 타입만) | 가능 | 전역 메모리 사용 |
const fn | 컴파일 타임 | 제한적 | 가능 | 복잡한 초기화 불가능 |
lazy_static | 런타임 (1회만) | ✅ | ✅ | 무겁고 오래된 방식 |
once_cell | 런타임 (1회만) | ✅ | ✅ or ❌ | 최신 방식, 매크로 없이 가능 |
(5) 요약
- 간단한 상수는 const
- 전역 정적 값은 static
- 복잡한 초기화가 필요한 전역 값은 once_cell::Lazy 추천