본문 바로가기
IT/Language

[2025.08.10] rust 기초 개념

by how-are-you 2025. 8. 10.

불변 참조와 가변 참조.

mut 는 가변이라는 뜻이고, rust 에서는 기본이 불변, mut를 사용해야 값을 변경 가능하다.

C++은 기본이 가변이라는 것에 불변에 사용하는 const와 반대되는 개념이다.

rust에는 불변 참조와 가변 참조가 있다

가변 참조는 내가 다른 곳에서 사용하기 위해 변수를 선언할 때, 해당 값을 가져와서 그 값을 변경할 수 있는 것이고, 불변은 변수 선언 이후 참조 시 참조 값이 바뀔 수 없는 것. 불변 참조는 여럿 선언 가능하지만 가변 참조는 하나만 사용 가능하다. 그리고 불변과 가변 참조를 동시에 가질 수 없다.

참조는 소유권을 가져가지 않고, 원본이 유효한 동안에 사용 가능하다.

변수 lifetime

수명 파라미터는 ‘a 와 같이 유효한 기간을 표시한다.

  • &T → 어떤 데이터의 참조지만, 그 참조가 유효한 기간을 <'a>로 표시 가능
  • 수명 파라미터 'a는 **“이 참조는 최소 'a 동안 유효하다”**라는 의미
  • 함수, 구조체, 메서드 등에서 입력 참조와 출력 참조 사이의 관계를 표현하는 데 사용
// x, y 둘 다 최소 'a 동안 살아있어야 하고
// 반환 참조도 최소 'a 동안 유효
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
    if x.len() > y.len() { x } else { y }
}

b 객체의 <`a> 라는 뜻은 b 객체의 lifetime은 a라는 객체의 lifetime 이내라는 뜻

`static

static 이라는 말과 같이 프로그램 전체 실행 기간 동안 유효한다는 뜻.

C++ 의 static과 같은 내용

let s: &'static str = "hello";

자료형

  • 자료형추가로, 사용자 정의 타입도 존재합니다.
    1. 스칼라 타입 (Scalar Types)분류 예시 설명
    정수형 (Integer) i8, i16, i32, i64, i128, isize / u8, u16, u32, u64, u128, usize i는 부호 있음(signed), u는 부호 없음(unsigned). isize/usize는 플랫폼 포인터 크기와 동일.
    부동소수형 (Floating-point) f32, f64 IEEE-754 표준. f64가 기본.
    불리언 (Boolean) bool (true / false) 조건식, 제어문에 사용
    문자 (Character) char 4바이트 유니코드 스칼라 값(UTF-32). 'a', '한', '\\n' 등

    2. 복합 타입 (Compound Types)분류 예시 설명
    튜플 (Tuple) (i32, f64, char) 서로 다른 타입 묶음. 인덱스로 접근: t.0, t.1
    배열 (Array) [i32; 5] 동일 타입 고정 길이. 길이와 타입이 타입 정의에 포함됨.
    슬라이스 (Slice) &[T] 배열의 연속된 부분 참조. 길이는 런타임에 결정.
    문자열 슬라이스 (String slice) &str UTF-8 문자열의 참조. 리터럴 "hello" 도 &'static str 타입.

    3. 사용자 정의 타입
    • 구조체 (struct)
    • rust 복사편집 struct Point { x: i32, y: i32 }
    • 열거형 (enum)
    • rust 복사편집 enum Direction { North, South, East, West }
    • 형식 별칭 (type alias)
    • rust 복사편집 type Kilometers = i32;
    • 유니언 (union) (unsafe)
    • rust 복사편집 union IntOrFloat { i: i32, f: f32 }

    4. 특이점
    • Rust의 모든 변수는 기본적으로 불변(immutable).
    • 기본 타입 대부분은 Copy 트레이트를 구현하여 값 복사로 이동.
    • 자료형 추론 가능하지만, 모호하면 명시 필요.
    • 문자열은 String(가변, heap)과 &str(불변, 참조) 구분 중요.
  • Rust는 struct, enum, type alias, union 등으로 타입을 만들 수 있습니다.
  • 여러 값을 하나로 묶어 표현하는 타입입니다.
  • 하나의 값만 표현하는 타입입니다.
  • Rust의 자료형(data types)은 크게 스칼라(Scalar) 타입복합(Compound) 타입으로 나뉩니다.

rust 자료형 기본이 불변에 타입 추론이 가능하여 자료형을 안 써도 된다. 다만 상수는 자료형을 명시해야 한다.

match

C++의 switch 문 과 비슷함.

let number = 7;

// if-else
if number < 5 {
    println!("less than 5");
} else {
    println!("greater or equal to 5");
}

// match (switch 비슷)
match number {
    1 => println!("one"),
    2 | 3 | 4 => println!("two to four"),
    5..=10 => println!("five to ten"),
    _ => println!("something else"),
}

반복문

let number = 7;

// if-else
if number < 5 {
    println!("less than 5");
} else {
    println!("greater or equal to 5");
}

// match (switch 비슷)
match number {
    1 => println!("one"),
    2 | 3 | 4 => println!("two to four"),
    5..=10 => println!("five to ten"),
    _ => println!("something else"),
}

Rust match

📌 Rust match와 enum 개념 정리

1. enum과 variant

enum Message {
    Quit,                        // 필드 없음
    Move { x: i32, y: i32 },     // 구조체 형태 (필드 있음)
    Write(String),               // 튜플 형태
}

  • Message자료형(타입)
  • Quit, Move {..}, Write(..) → Message 타입이 가질 수 있는 값의 형태(variant)
  • 각 variant는 데이터를 가질 수도 있고 (예: Move, Write), 안 가질 수도 있음 (예: Quit)

2. match 문과 coverage 규칙

  • Rust의 match는 모든 가능한 variant를 처리해야 함.
  • 일부만 처리하면 컴파일 에러.
  • 나머지를 _로 처리하면 C++의 default:와 같은 역할.
match msg {
    Message::Quit => { ... }
    Message::Move { x, y } => { ... }
    Message::Write(text) => { ... }
}

또는 _ 사용:

match msg {
    Message::Quit => { ... }
    _ => { /* 나머지 전부 처리 */ }
}

 


3. "Quit"은 자료형이 아님

  • Quit은 Message 타입의 하나의 값일 뿐.
  • 예:
let m1 = Message::Quit;                   // 타입: Message
let m2 = Message::Move { x: 5, y: 10 };   // 타입: Message

  • 자료형(타입) = Message
  • variant = Quit, Move, Write

4. C++와 비교

  • C++의 enum은 값만 가지지만,
  • Rust enum은 variant마다 데이터를 포함할 수 있음.
  • 예:
// C++에서 비슷하게 표현하려면 enum + struct 조합 필요
enum class MessageType { Quit, Move, Write };
struct MoveData { int x, y; };

  • Rust의 Message::Move { x, y }는 enum과 구조체의 기능을 한 번에 가짐.

결론

  • 타입: Message
  • 값의 형태: Quit, Move {..}, Write(..)
  • match: variant에 따라 분기 처리, 모든 경우를 커버해야 함 (_ 사용 가능)
  • Rust enum 장점: 각 variant에 개별 데이터 저장 가능 → C++보다 표현력 강력.

rust 범위 확인

#[derive(Debug)]
struct Percentage(u8);

impl Percentage {
    fn new(value: u8) -> Option<Self> {
        if value <= 100 {
            Some(Percentage(value))
        } else {
            None
        }
    }
}

fn main() {
    let p = Percentage::new(85).unwrap();
    println!("{:?}", p); // Percentage(85)

    // let p2 = Percentage::new(150).unwrap(); // 런타임에서 panic
}

컴파일 타임에 범위를 못 벗어나게 수정

좋아요. “컴파일 타임에 범위를 못 벗어나게”를 두 가지 층위로 보여줄게요.

  1. 리터럴처럼 고정된 값은 진짜로 컴파일 에러로 막기
  2. 런타임에서 오는 값은 동일 타입으로 받되, const 문맥에서는 컴파일 에러, 그 외엔 Result로 안전 처리

impl

Rust에서 **impl**은

“어떤 타입에 대해 메서드나 관련 함수를 구현(implement)하는 블록”

이라고 보면 돼요.

C++에서의 member function 정의 + static method 정의를 합쳐 놓은 개념입니다.


1. 기본 구조

struct Point {
    x: i32,
    y: i32,
}

// Point 타입에 메서드/함수를 붙인다
impl Point {
    // self를 받는 메서드 → 인스턴스에서 호출 가능
    fn move_by(&mut self, dx: i32, dy: i32) {
        self.x += dx;
        self.y += dy;
    }

    // self를 안 받는 함수 → "연관 함수"(associated function)
    fn origin() -> Point {
        Point { x: 0, y: 0 }
    }
}

사용 예:

fn main() {
    let mut p = Point::origin(); // 연관 함수 호출
    p.move_by(5, 3);             // 메서드 호출
}

2. 특징

  1. 메서드와 연관 함수 구분
    • fn method_name(&self, ...) → 인스턴스에서 호출 (p.method_name())
    • fn function_name(...) → 타입에서 호출 (Type::function_name())
  2. 여러 개의 impl 블록 가능
    impl Point {
        fn length(&self) -> f64 { /* ... */ }
    }
    impl Point {
        fn reset(&mut self) { /* ... */ }
    }
    
    
  3. 큰 타입은 기능별로 나눠서 구현할 수 있습니다.
  4. 제네릭 / 트레잇과 결합 가능
    • impl<T> MyStruct<T> { ... } → 제네릭 타입 구현
    • impl MyTrait for MyType { ... } → 트레잇 구현 (다른 의미, 여기선 메서드 정의)

return 타입

Rust에서 함수의 **반환 타입(return type)**은 함수 시그니처에서 -> 뒤에 타입을 적어서 정의합니다.

return 값 또는 return 이 없을 땐 맨 마지막 세미콜론이 없는 표현식 값을 return 한다.

C/C++처럼 앞에 쓰는 게 아니라 함수 매개변수 뒤에 명시하는 방식이에요.


1. 기본 형태

fn add(a: i32, b: i32) -> i32 {
    a + b // 세미콜론이 없으면 마지막 표현식이 반환값
}
  • -> i32가 반환 타입
  • 마지막 줄이 세미콜론 없는 표현식이면 그 값이 반환됨
  • 세미콜론을 붙이면 ()(unit type, void 같은 것) 반환

2. return 키워드 사용

fn multiply(a: i32, b: i32) -> i32 {
    return a * b; // 조기 반환 가능
}
  • Rust에선 보통 마지막 표현식으로 반환하는 걸 선호
  • return은 중간에 빠져나올 때 주로 사용

3. 여러 타입 예시

fn get_pi() -> f64 {
    3.141592
}

fn is_even(n: i32) -> bool {
    n % 2 == 0
}

fn no_return() { // 반환 타입 생략 == -> ()
    println!("nothing returned");
}

4. 복합 타입 반환

튜플, 구조체, 제네릭 등도 그대로 -> 뒤에 씁니다.

fn min_max(values: &[i32]) -> (i32, i32) {
    let min = *values.iter().min().unwrap();
    let max = *values.iter().max().unwrap();
    (min, max)
}

struct Point { x: i32, y: i32 }

fn origin() -> Point {
    Point { x: 0, y: 0 }
}

5. 제네릭과 트레잇 바운드

fn first<T: Clone>(list: &[T]) -> Option<T> {
    list.first().cloned()
}
  • T는 제네릭 타입
  • 반환 타입은 Option<T>

6. 예외적인 반환: ! (never type)

fn never_return() -> ! {
    panic!("This function never returns");
}
  • !는 절대 반환하지 않는 함수(무한 루프, 패닉 등)

요약

Rust에서 반환 타입 정의는:

fn function_name(args...) -> ReturnType { ... }
  • -> 뒤에 타입을 쓴다
  • 마지막 표현식이 암묵적 반환
  • ()는 반환값 없음(void 같은 것)
  • 제네릭, 튜플, 구조체 등 모든 타입 그대로 작성 가능