출시·고도화 중
Rust 안내서 · 2/6
이 장에서는 변수와 가변성, 함수, 제어 흐름(if, loop, while, for, match), 그리고 섀도잉을 다룹니다. Rust는 C 계열 문법을 닮았지만 "거의 모든 것이 식(expression)"이라는 점이 크게 다릅니다.
let으로 만든 변수는 기본적으로 불변입니다. 값을 바꿀 변수에는 mut를 붙입니다. 이 규칙 덕분에 코드를 읽을 때 어떤 값이 바뀌는지 바로 알 수 있습니다.
fn main() {
let x = 5;
// x = 6; // 컴파일 오류: 불변 변수에 다시 대입할 수 없습니다
let mut count = 0;
count += 1;
count += 1;
// 상수는 타입을 반드시 적고, 이름은 대문자로 씁니다
const MAX_USERS: u32 = 100_000;
println!("x = {x}, count = {count}, max = {MAX_USERS}");
}타입은 대부분 컴파일러가 추론하므로 생략할 수 있습니다. 필요하면 let age: u8 = 30;처럼 콜론 뒤에 적습니다. const는 컴파일 시점에 정해지는 값이며, 함수 밖에서도 선언할 수 있습니다. 숫자 사이의 밑줄(100_000)은 읽기 쉽게 하려는 표기일 뿐 값에는 영향이 없습니다.
같은 이름으로 let을 다시 쓰면 앞의 변수를 가리는 새 변수가 생깁니다. 이를 섀도잉이라고 합니다. mut와 달리 타입을 바꿀 수 있고, 변환이 끝난 뒤에는 다시 불변이 됩니다.
fn main() {
let input = " ";
let input = input.len(); // &str 에서 usize 로 타입이 바뀝니다
println!("공백 개수: {input}");
let x = 5;
{
let x = x * 2; // 이 블록 안에서만 유효합니다
println!("안쪽 x = {x}"); // 10
}
println!("바깥 x = {x}"); // 5
}섀도잉은 문자열을 숫자로 바꾸는 것처럼 "같은 의미의 값을 다른 형태로 바꿀 때" 특히 유용합니다. input_str, input_num처럼 이름을 따로 지을 필요가 없습니다.
함수는 fn으로 정의하고, 매개변수에는 타입을 반드시 적습니다. 반환 타입은 -> 뒤에 씁니다. 함수 본문의 마지막 식이 곧 반환값이며, 이때 세미콜론을 붙이지 않습니다. 세미콜론을 붙이면 문(statement)이 되어 값이 ()로 바뀝니다. 중간에 빠져나갈 때는 return을 씁니다.
fn add(a: i32, b: i32) -> i32 {
a + b // 세미콜론이 없으므로 이 값이 반환됩니다
}
fn sign(n: i32) -> &'static str {
if n < 0 {
return "음수";
}
if n == 0 { "영" } else { "양수" }
}
fn main() {
let sum = add(2, 3);
// 블록도 식이므로 값을 가질 수 있습니다
let doubled_plus_one = {
let t = sum * 2;
t + 1
};
println!("{sum} {doubled_plus_one} {}", sign(-4));
}함수 이름은 snake_case로 짓는 것이 관례입니다. 함수를 정의하는 순서는 상관없으므로, main 아래에 보조 함수를 두어도 됩니다.
if의 조건은 반드시 bool이어야 합니다. C처럼 숫자를 조건으로 쓸 수 없습니다. if도 식이므로 let grade = if score >= 90 { "A" } else { "B" };처럼 값을 바로 받을 수 있으며, 이때 두 갈래의 타입이 같아야 합니다.
반복문은 세 가지입니다.
loop: 명시적으로 break할 때까지 무한 반복하며, break 값으로 결과를 돌려줄 수 있습니다.while: 조건이 참인 동안 반복합니다.for: 범위나 컬렉션을 차례로 순회합니다. 가장 많이 쓰입니다.fn main() {
let mut counter = 0;
let result = loop {
counter += 1;
if counter == 10 {
break counter * 2; // loop 의 값이 됩니다
}
};
println!("result = {result}"); // 20
let mut n = 3;
while n > 0 {
println!("{n}!");
n -= 1;
}
for i in 1..=3 {
println!("i = {i}"); // 1, 2, 3 (..= 은 끝을 포함)
}
let fruits = ["사과", "배", "감"];
for (index, fruit) in fruits.iter().enumerate() {
println!("{index}: {fruit}");
}
// 레이블로 바깥 반복문을 한 번에 빠져나갑니다
'outer: for a in 0..5 {
for b in 0..5 {
if a * b == 6 {
println!("찾음: {a} x {b}");
break 'outer;
}
}
}
}0..5는 5를 포함하지 않고, 1..=3은 3을 포함합니다. 거꾸로 돌려면 (1..=3).rev()를 씁니다.
match는 값을 여러 패턴과 차례로 비교해 처음 맞는 갈래를 실행합니다. 컴파일러가 모든 경우를 다뤘는지 검사하므로 빠뜨린 경우가 있으면 컴파일되지 않습니다. 나머지 전부는 _로 받습니다.
fn classify(n: i32) -> &'static str {
match n {
0 => "영",
1 | 2 | 3 => "작은 수",
4..=9 => "한 자리 수",
x if x < 0 => "음수", // 가드: 추가 조건
_ => "큰 수",
}
}
fn main() {
for n in [0, 2, 7, -1, 42] {
println!("{n}: {}", classify(n));
}
// 한 가지 경우만 볼 때는 if let 이 간결합니다
let maybe: Option<i32> = Some(3);
if let Some(v) = maybe {
println!("값: {v}");
}
}mut를 붙입니다.if와 블록도 값을 가집니다.loop, while, for를 쓰며 for와 범위(.., ..=)가 가장 흔합니다.match는 모든 경우를 다뤄야 하므로 빠뜨린 처리를 컴파일러가 잡아 줍니다.
댓글 0개
로그인 · 로그인하면 댓글을 남길 수 있습니다.
첫 댓글을 남겨 보세요.