러스트(Rust) 기초 문법 총정리: 변수부터 소유권까지 한 번에
러스트(Rust) 기초 문법 총정리: 변수부터 소유권까지 한 번에
Rust는 “안전성(메모리 안전)”과 “성능(제로 코스트 추상화)”을 동시에 목표로 하는 시스템 프로그래밍 언어입니다. C/C++에서 자주 발생하는 메모리 오류를 컴파일 타임에 강하게 막아주기 때문에, 백엔드/임베디드/CLI/고성능 서버 등에서 점점 더 많이 사용됩니다.
이 글은 러스트를 처음 시작하는 분을 위해, “문법 + 실제 코드 예시” 중심으로 정리합니다. 단순 암기보다 왜 이렇게 쓰는지까지 이해할 수 있게 구성했어요.
1) Rust 프로젝트 시작: Cargo 기본
Rust의 공식 빌드 도구/패키지 매니저는 Cargo입니다. Rust를 한다 = Cargo를 쓴다, 라고 생각해도 됩니다.
프로젝트 생성
cargo new hello_rust
cd hello_rust
cargo run
src/main.rs: 실행 파일 엔트리Cargo.toml: 의존성/프로젝트 설정
Hello World
fn main() {
println!("Hello, Rust!");
}
2) 변수와 상수: let, mut, const
Rust에서 기본 변수는 불변(immutable)입니다. 즉, 실수로 값이 바뀌는 것을 기본적으로 막습니다. 바꾸려면 mut 키워드를 명시해야 합니다.
불변 변수 vs 가변 변수
fn main() {
let x = 10; // 불변
// x = 20; // ❌ 컴파일 에러
let mut y = 10; // 가변
y = 20; // ✅ OK
println!("x={x}, y={y}");
}
상수(const)
상수는 반드시 타입을 명시해야 하고, 컴파일 타임에 결정되는 값이어야 합니다.
const MAX_USERS: u32 = 1000;
fn main() {
println!("MAX_USERS={MAX_USERS}");
}
섀도잉(Shadowing)
Rust는 같은 이름을 let으로 다시 선언해 값을 “덮어쓰는” 섀도잉을 허용합니다. (mut로 바꾸는 것과 느낌이 다릅니다: “새 변수”로 다시 바꿔치기)
fn main() {
let a = "10";
let a = a.parse::().unwrap(); // 타입까지 바꿀 수 있음
println!("a={a}");
}
3) 기본 타입: 숫자, bool, char, 문자열
숫자 타입
- 정수:
i8 i16 i32 i64 i128 isize,u8 u16 u32 u64 u128 usize - 실수:
f32 f64
fn main() {
let n: i32 = -10;
let u: u32 = 10;
let f: f64 = 3.14;
println!("{n}, {u}, {f}");
}
bool / char
fn main() {
let ok: bool = true;
let c: char = '가'; // char는 유니코드 1글자
println!("{ok}, {c}");
}
문자열: &str vs String
Rust 문자열은 입문 난이도가 높은 편입니다. 핵심만 잡으면 됩니다.
&str: 문자열 “슬라이스”, 보통 리터럴(고정) / 참조 형태String: 힙에 저장되는 “가변 문자열”
fn main() {
let s1: &str = "hello"; // 슬라이스(대개 불변)
let mut s2: String = String::from("hi"); // 가변 String
s2.push_str(" rust");
println!("{s1} / {s2}");
}
4) 함수와 반환: fn, return, 표현식
Rust는 마지막 줄에 세미콜론(;)이 없으면 반환값이 됩니다. 이게 Rust 문법의 아주 중요한 감각입니다.
fn add(a: i32, b: i32) -> i32 {
a + b // ✅ 세미콜론 없음 = 반환
}
fn main() {
let r = add(3, 4);
println!("{r}");
}
조기 반환
fn safe_div(a: i32, b: i32) -> i32 {
if b == 0 {
return 0;
}
a / b
}
5) 조건문과 반복문: if, loop, while, for
if는 “표현식”이다
Rust에서 if는 값을 만들 수 있습니다.
fn main() {
let score = 87;
let grade = if score >= 90 { "A" } else { "B" };
println!("grade={grade}");
}
loop / while / for
fn main() {
let mut i = 0;
loop {
i += 1;
if i == 3 { break; }
}
let mut j = 0;
while j < 3 {
j += 1;
}
for k in 0..3 {
println!("k={k}");
}
}
6) 컬렉션: 배열, 튜플, 벡터
배열(Array) - 고정 길이
fn main() {
let arr: [i32; 3] = [1, 2, 3];
println!("{}", arr[0]);
}
튜플(Tuple) - 서로 다른 타입 가능
fn main() {
let t: (i32, &str, bool) = (10, "hi", true);
let (a, b, c) = t; // 구조 분해
println!("{a}, {b}, {c}");
}
벡터(Vec) - 가변 길이
fn main() {
let mut v: Vec<i32> = vec![1, 2, 3];
v.push(4);
println!("{:?}", v);
}
7) 구조체(Struct)와 메서드(impl)
Rust에서 데이터 묶음은 구조체로 많이 다룹니다.
struct User {
name: String,
age: u32,
}
impl User {
fn new(name: &str, age: u32) -> Self {
Self { name: name.to_string(), age }
}
fn greet(&self) {
println!("Hi, I'm {} ({})", self.name, self.age);
}
}
fn main() {
let u = User::new("Hong", 18);
u.greet();
}
8) enum과 match: 러스트의 “필살기”
Rust의 enum은 단순 상수 집합이 아니라, “상태 + 값”을 함께 담는 강력한 타입입니다.
enum ResultState {
Ok(i32),
Err(String),
}
fn main() {
let r = ResultState::Ok(200);
match r {
ResultState::Ok(code) => println!("success: {code}"),
ResultState::Err(msg) => println!("fail: {msg}"),
}
}
Option: null 대신 쓰는 표준 타입
fn get_first(v: &Vec<i32>) -> Option<i32> {
if v.is_empty() { None } else { Some(v[0]) }
}
fn main() {
let v = vec![10, 20];
match get_first(&v) {
Some(x) => println!("first={x}"),
None => println!("empty"),
}
}
9) 소유권(Ownership) 핵심만: 왜 Rust가 안전한가
Rust 입문에서 가장 중요한 개념이 소유권(ownership)입니다. 쉽게 말해 “값의 주인이 누구인지”를 컴파일러가 엄격하게 추적해서, 해제된 메모리를 또 쓰는 문제(use-after-free) 같은 위험을 막습니다.
이동(move)
fn main() {
let s1 = String::from("hello");
let s2 = s1; // s1이 s2로 이동(move)됨
// println!("{}", s1); // ❌ s1 사용 불가
println!("{}", s2);
}
복사(copy) - 간단 타입은 자동 복사
fn main() {
let a = 10;
let b = a; // i32는 Copy
println!("{a}, {b}");
}
빌림(borrow) - 참조(&T)
fn print_len(s: &String) {
println!("len={}", s.len());
}
fn main() {
let s = String::from("hello");
print_len(&s); // 빌려주기
println!("{}", s); // 여전히 사용 가능
}
가변 참조(&mut T) 규칙
Rust는 동시 수정 경쟁을 막기 위해 가변 참조는 동시에 하나만 허용하는 규칙이 있습니다.
fn main() {
let mut s = String::from("hi");
let r = &mut s;
r.push_str(" rust");
println!("{r}");
}
10) 에러 처리: Result와 ? 연산자
Rust는 예외(Exception)를 던지는 대신, 보통 Result<T, E>로 에러를 반환합니다. 그리고 ? 연산자로 “에러면 바로 반환”을 깔끔하게 처리할 수 있습니다.
use std::fs;
fn read_file(path: &str) -> Result<String, std::io::Error> {
let s = fs::read_to_string(path)?; // 에러면 바로 반환
Ok(s)
}
fn main() {
match read_file("Cargo.toml") {
Ok(text) => println!("read ok: {} chars", text.len()),
Err(e) => println!("read fail: {e}"),
}
}
11) 모듈과 공개 범위: mod, pub
Rust는 기본적으로 “숨김(private)”이 기본값입니다. 외부에 노출하려면 pub이 필요합니다.
// lib.rs 예시
mod utils {
pub fn hello() {
println!("hello");
}
fn secret() {
println!("secret");
}
}
fn main() {
utils::hello();
// utils::secret(); // ❌ private
}
12) 입문자가 자주 막히는 포인트 (현실 체크)
- &str vs String: “고정 참조 vs 가변 힙 문자열” 감각을 잡아야 합니다.
- move/borrow: 값이 이동되는 순간과 참조로 빌리는 순간을 구분하세요.
- match: 초반엔 길어 보이지만, 안정성과 가독성을 크게 올려줍니다.
- 컴파일 에러: Rust는 에러 메시지가 친절한 편이라 “에러로 배우는 언어”로 접근하면 빨리 늡니다.
13) 마무리: 다음 단계 로드맵
여기까지가 “Rust 문법 입문”의 뼈대입니다. 다음 단계는 아래 순서로 추천합니다.
- 소유권 심화: 라이프타임(lifetime) 기초 맛보기
- 트레이트(trait): 인터페이스/다형성 감각
- 제네릭(generic): 타입 안전한 재사용 코드
- 비동기(async): tokio 기반 서버/CLI 확장
Rust는 “문법”보다 “규칙(소유권/빌림)”이 핵심입니다.
처음엔 답답해도, 규칙이 몸에 붙으면 코드 품질이 확 달라집니다.
관련 키워드 태그 (10개)
#Rust #러스트 #Rust기초 #Rust문법 #소유권 #Borrow #Cargo #시스템프로그래밍 #백엔드개발 #개발공부
Meta Description (160자 내외)
Rust 기초 문법을 변수/타입/함수/조건문/컬렉션/struct/enum/match부터 소유권·빌림·Result 에러 처리까지 예제로 정리한 입문 가이드.
'it' 카테고리의 다른 글
| 개발 블로그 운영 팁: 꾸준히 성장시키는 실전 운영 가이드 (0) | 2026.02.28 |
|---|---|
| 인공지능 웹사이트 연동 완전 가이드: API 연결부터 운영(보안/비용/성능)까지 (0) | 2026.02.26 |
| Node.js로 챗봇 만들기: 기획부터 배포까지 한 번에 끝내는 실전 가이드 (1) | 2026.02.24 |
| OpenAI API 활용법 (2026 최신 흐름 기준: Responses API 중심) (0) | 2026.02.22 |
| C++ vs Rust 성능 비교: “누가 더 빠르냐”보다 “어떻게 빠르게 쓰냐”가 핵심 (0) | 2026.02.18 |
| Rust 입문 장점: 왜 요즘 ‘안전한 고성능’의 표준이 되었을까 (1) | 2026.02.16 |
| 클라우드 서버 요금 비교 & 웹사이트 유지비용 총정리 (2026 기준) (0) | 2026.02.14 |
| 가비아 Node.js 호스팅 후기: 장점/단점, 배포 흐름, 실전 운영 팁까지 (0) | 2026.02.12 |