2025년 4월 7일 월요일

3-2강 - 동적 메모리 할당

 Box 와 Rc를 사용해 동적 메모리를 할당 받을 수 있다.


1. Box

- Box::new : 메모리 할당

- * : 메모리에 접근 

fn main() {
    let mut x = Box::new(10); //Box를 사용하여 힙에 데이터를 할당
    println!("x: {}", x);  // x: 10

    *x = 20; // 할당받은 메모리에 접근 하려면 * 사용
    println!("x: {}", x); // x: 20
}

2. Rc(Reference-counting pointer) : 강한 참조
- Rc로 관리되는 데이터는 공유가 가능해서 여러 변수가 동일한 값을 참조할 수 있다.
- 데이터를 참조하는 모든 변수들이 메모리에 존재하면 값을 해체 하지 않는다.
- 참조 횟수를 추적해 참조 횟수가 0이 되면 데이터를 삭제한다.
- 순환 참조가 발생하면 참조 횟수가 0이 되지 않아서 메모리 누수 발생한다.
use std::rc::Rc;

struct Person {
    age: i32,
}

fn main() {
    // person을 공유 객체로 생성
    let person = Rc::new(Person { age: 10 });

    // person복제
    let p1 = person.clone();
    println!("person: {} p1: {}", person.age, p1.age);
    println!("RefCount: {}", Rc::strong_count(&person)); //+1
   
    // person복제
    let p2 = person.clone();
    println!("RefCount: {}", Rc::strong_count(&person)); //+1

    { //새로운 스코프 시작
        // person복제
        let p3 = person.clone();
        println!("RefCount: {}", Rc::strong_count(&person)); //+1
    } //스코프 종료 (그래서 p3는 소멸됨)

    println!("RefCount: {}", Rc::strong_count(&person)); //-1
}

3. RefCell
- Rc는 불변성을 가진 참조형이기에 공유 데이터를 변경할 수 없다.
- 공유 데이터를 수정하려고 하면 컴파일 오류가 발생한다.
- RefCell 은 변경 불가능한 변수를 임시로 변경 가능하게 해준다.
// RefCell<T>를 사용해 next 의 참조값을 변경하는 예제
use std::rc::Rc;
use std::cell::RefCell; //내부 가변성을 제공

struct Person {
    name: String,
    age: i32,
    next: RefCell<Option<Rc<Person>>>, // RefCell<>로 감싸서 next는 수정 가능
}

fn main() {
    // p1 노드 생성
    let mut p1 = Rc::new(Person {
        name: String::from("Luna"),
        age: 30,
        next: RefCell::new(None), //처음에는 다음 노드가 없음.
    });

    // p2 노드 생성
    let mut p2 = Rc::new(Person {
        name: String::from("Rust"),
        age: 10,
        next: RefCell::new(None), //처음에는 다음 노드가 없음.
    });

    // p1의 next 필드에 대한 가변 참조를 얻음
    let mut next = p1.next.borrow_mut();

    // * 키워드를 사용하면 RefCell<T>의 내부 값을 얻을 수 있다.
    *next = Some(p2.clone()); // p1뒤에 p2를 추가
}
 4. 약한 참조
- Rc는 순환참조가 발생하면 메모리 누수가 발생한다.
- Weak 를 사용하면 약한 참조를 만들어서 순환 참조 문제를 해결 가능하다.
// Rc와 Weak를 사용해서 순환 참조 문제를 해결하는 예제
use std::rc::{Rc, Weak};  // Rc: 강한 참조 / Weak: 약한 참조
use std::cell::RefCell;   // 내부 가변성을 위해 사용 (가변 borrow)

struct Person {
    id: i32,                          // Person 식별자 (id)
    next: RefCell<Option<Weak<Person>>>, // 다음 사람 참조 (약한 참조)
    // Weak<T> 를 사용하면 참조카운트를 증가시키지 않음
}

// Rc 참조카운트가 0이 되면 자동 호출되는 drop 함수 구현
impl Drop for Person {
    fn drop(&mut self) {
        // 메모리 해제될 때 어떤 Person이 drop 되었는지 출력
        println!("p{} Drop!", self.id);
    }
}

fn main() {
    // p1 객체 생성 (참조카운트 strong=1)
    let mut p1 = Rc::new(Person {
        id: 1,
        next: RefCell::new(None),
    });

    // p2 객체 생성 (참조카운트 strong=1)
    let mut p2 = Rc::new(Person {
        id: 2,
        next: RefCell::new(None),
    });

    // p1 -> p2 연결 (약한 참조로 연결)
    {
        let mut next = p1.next.borrow_mut();        // RefCell을 사용해 내부 가변 접근
        *next = Some(Rc::downgrade(&p2));           // Rc::downgrade() : Weak 참조 생성
        // Rc::downgrade를 사용하면 참조카운트 증가하지 않음 (순환참조 해결)
    }

    // p2 -> p1 연결 (약한 참조로 연결)
    {
        let mut next = p2.next.borrow_mut();
        *next = Some(Rc::downgrade(&p1));           // 약한 참조로 연결
    }

    // 현재 Rc 참조카운트 출력
    println!("p1 RefCount: {} p2: RefCount: {}",
        Rc::strong_count(&p1), Rc::strong_count(&p2));
    // 출력 예시
    // p1 RefCount: 1 p2 RefCount: 1

    // 약한 참조는 strong_count 증가 안되므로
    // main 스코프 종료시 Rc 카운트 1 -> 0 으로 떨어짐
    // -> Drop 호출됨
}
Box 와 Rc
- 외부 공유가 필요한 경우 Rc 사용, 그렇지 않다면 Box 사용
- 수정이 불가능한 Rc변수를 변경하려면 RefCell 기법도 사용

3-1강 - 소유권

러스트 소유권(Ownership) 정리

소유권이란?

러스트(Rust)의 가장 핵심 개념
메모리 안전(Memory Safety)을 보장하기 위한 설계 철학

"러스트는 데이터에 대한 소유권 규칙을 통해 메모리를 자동으로 관리한다."


소유권 규칙 3가지



1. 값 이동은 오직 하나의 소유자(owner)만 가진다.
2. 소유자가 스코프를 벗어나면 데이터는 자동으로 drop(메모리 해제) 된다.
3. 데이터 이동(move)시 원래 소유자는 더 이상 데이터 사용 불가

(1)소유권
// 소유권을 다른 변수로 이관하는 예제
fn main() {
    //새로운 문자열 변수 생성
    let s1 = String::from("Hello Rust!");

    // s2로 소유권을 이동
    let s2 = s1;

   // s1은 소유권을 상실했기 때문에 s1에 접근하는 순간 컴파일 에러 발생
   println!("{}", s1);
}

// clone를 사용해 값을 복제해서 소유권 문제 해결
fn main() {
    // 새로운 문자열 변수를 생성
    let s1 = String::from("Hello Rust!");

    // s1을 "복사"하여 s2에 저장
    let s2 = s1.clone();

    // s1은 여전히 소유권을 가지고 있기 때문에 문제없음
    println!("{}", s1);
}

(2) 빌림
//함수 파라미터로 값을 전달하는 경우 소유권도 함께 이전 되기에
// 변수의 재사용이 어렵다.

fn main() {
    let s = String::from("Hello");
    push_str(s); // push_str 함수에 소유권을 전달
    println!("{}", s); // s를 사용하는 순간 컴파일 오류 발생
}

fn push_str(mut s: String) {
    s.push_str(" Rust!");
}

// 빌림을 사용해 소유권 공유
// 값은 전달하되 소유권은 유지하고 싶을 경우 사용
fn main() {
    let mut s = String::from("Hello");

    //s 의 소유권을 이전하지 않고, 참조를 전달해 문자열의 내용을 변경
    push_str(&mut s);

    // s는 소유권을 유지하고 있기에 정상 동작
    println!("{}", s);
}

fn push_str(s: &mut String) {
    s.push_str(" Rust!");
}

2025년 4월 6일 일요일

2강 - 러스트 기초

Rust 기초 정리 (2강)

Rust 기초 문법과 개념들을 정리


2.1 자료형 (Data Types)

Rust는 정적 타입 언어로 컴파일 타임에 자료형이 결정됨.

종류 예시 설명
정수형 i32, u64 등 부호/크기 명시
실수형 f32, f64 부동소수점 숫자
불린형 bool true / false
문자형 char 유니코드 문자
문자열 String, &str 힙 메모리 or 리터럴
튜플 (i32, f64, char) 여러 타입 묶음
배열 [i32; 3] 고정 크기 배열

2.2 불변성과 가변성 (Immutability & Mutability)

Rust는 기본적으로 변수는 불변(immutable)

let x = 10;       // 불변 변수
let mut y = 20;   // 가변 변수 (mut 키워드)
y = 30;           // 가능

안전성을 위해 기본은 불변 → 필요할 때만 mut 사용 권장.


2.3 제어문 (Control Flow)

조건문 → if, else if, else

let score = 80;
if score >= 90 {
    println!("A등급");
} else if score >= 80 {
    println!("B등급");
} else {
    println!("C등급");
}

2.4 반복문 (Loop)

반복 처리 구문 종류

문법 설명
loop 무한 반복
while 조건 반복
for 컬렉션 순회
for i in 1..=5 {
    println!("{}", i);
}

2.5 함수 (Function)

함수 정의와 반환값

fn add(a: i32, b: i32) -> i32 {
    a + b  // 세미콜론 없으면 return
}

Rust는 명시적 타입 선언 권장


2.6 클로저 (Closure)

익명 함수 (람다식)

let sum = |a: i32, b: i32| -> i32 {
    a + b
};

println!("{}", sum(3, 5));

클로저는 환경 변수 캡처 가능 (스코프 안 변수 사용 가능)


2.7 구조체 (Struct)

데이터 묶음 정의

struct User {
    name: String,
    age: u32,
}

let user = User {
    name: String::from("홍길동"),
    age: 30,
};

구조체를 이용해 객체지향 프로그래밍 스타일 가능


2.8 열거형 (Enum)

여러 값 중 하나를 표현

enum Color {
    Red,
    Green,
    Blue,
}

let c = Color::Red;

매칭 처리 가능

match c {
    Color::Red => println!("빨강"),
    Color::Green => println!("초록"),
    Color::Blue => println!("파랑"),
}

마무리 정리 Tip

Rust는 "안전하고 빠르고 현대적인 시스템 프로그래밍 언어"
기본 규칙 : 불변, 명시적, 안전성 추구
익숙해질수록 개발 생산성도 올라간다!

1강 - 러스트 소개

1.러스트를 왜 배워야 하는가?

Rust - C/C++ 수준의 고성능을 제공하면서도 메모리 안전성을 보장하는 현대적인 시스템 프로그래밍 언어
자바나 C#처럼 런타임에 GC(Garbage Collection)를 사용하지 않고, 소유권(Ownership)과 빌림(Borrowing) 개념을 통해 메모리를 컴파일 타임에 안전하게 관리

러스트 특징 

- 안전한 메모리 관리

- 철저한 오류 처리

- 쉽고 편한 비동기 프로그래밍

- 편리한 패키지 관리 도구


항목 C / C++ Java / C# Rust
메모리 관리 수동 (malloc/free, new/delete) 자동 (Garbage Collection) 소유권(Ownership), 빌림(Borrowing), 라이프타임(Lifetime)
실행 성능 매우 빠름 비교적 빠름 (GC 영향 있음) 매우 빠름 (C/C++ 수준)
안정성 낮음 (메모리 오류 발생 가능) 높음 (런타임 예외 처리) 매우 높음 (컴파일 타임에 대부분 검증)
학습 난이도 낮음 ~ 높음 (C++ 복잡도 높음) 낮음 높음 (소유권 개념 익숙해지기 어려움)
개발 생산성 낮음 (많은 직접 관리) 높음 (GC, 풍부한 라이브러리) 보통 (엄격한 문법 제약)

이런~ 내가 이 블로그를 너무 방치 했었네 ㅎㅎ;;

 마지막으로 글 남긴게 작년 1월이라니~!

깃허브에 집중하느라 여기에 글 남기는 것을 깜박했다.

ai 시대가 아무리 발전해도, 개발자라면 꾸준한 공부가 답 아닐까?

지금은 러스트 공부 중인데 새로운 언어니깐 

여기에 기록해서 까먹으면 내가 기억하기 용으로 써야겠다.

꾸준한 공부는 배신하지 않는다.