728x90
반응형

4장. 소유권 이해하기

4.1 소유권의 기본 개념

  • 소유권이란 무엇인가?

    • Rust의 핵심 기능으로 가비지 컬렉터 없이 메모리 안전성 보장
    • 메모리 관리를 컴파일 타임에 체크
    • 힙 데이터 관리를 위한 방법
      예제:
      fn main() {
        let s = String::from("hello"); // s는 문자열의 소유자
        takes_ownership(s);            // s의 값이 이동됨
        // println!("{}", s);          // 오류: s는 이미 이동됨
      }
      

    fn takes_ownership(some_string: String) {

    println!("{}", some_string);

    } // some_string이 스코프를 벗어나고 drop이 호출됨

  • 소유권 규칙 3가지

    • Rust의 각 값은 해당하는 변수를 소유자로 가짐

    • 한 번에 딱 하나의 소유자만 존재할 수 있음

    • 소유자가 스코프 밖으로 벗어나면 값은 버려짐(drop)
      예제:

      fn main() {
        let x = 5;
        let y = x; // 복사
        println!("x = {}, y = {}", x, y); // 둘 다 사용 가능
      
        let s1 = String::from("hello");
        let s2 = s1; // 이동
        // println!("{}", s1); // 오류: s1은 이미 이동됨
        println!("{}", s2); // 정상 작동
      }
  • 변수의 스코프

    • 변수가 유효한 범위
    • 중괄호 {}로 정의되는 범위
    • 변수는 선언된 시점부터 현재 스코프가 끝날 때까지 유효
      예제:
      fn main() {
        {                      // s는 유효하지 않음
            let s = "hello";   // s는 이제부터 유효
            println!("{}", s);
        }                      // 이 스코프는 끝났고, s는 더 이상 유효하지 않음
        // println!("{}", s);  // 오류: s는 이 스코프에서 유효하지 않음
      }
  • String 타입으로 이해하는 소유권

    • String과 문자열 리터럴의 차이

    • 힙에 할당되는 String 타입의 메모리 구조

    • move 시맨틱스와 깊은 복사
      예제:

      fn main() {
        let s1 = String::from("hello");
        let s2 = s1.clone(); // 깊은 복사
      
        println!("s1 = {}, s2 = {}", s1, s2);
      
        let x = 5;
        let y = x; // 정수형은 Copy 트레이트를 구현하므로 복사됨
      
        println!("x = {}, y = {}", x, y);
      }
  • 예제: 문자열 소유권 이동하기

    let s1 = String::from("hello");
    let s2 = s1; // s1의 소유권이 s2로 이동
    // println!("{}", s1); // 오류: s1은 이미 이동됨

4.2 참조와 대여

  • 참조를 사용하는 이유

    • 소유권을 이전하지 않고 값을 사용하기 위해
    • 함수에서 값을 여러 번 사용할 때 유용
      예제:
      fn main() {
        let s1 = String::from("hello");
        let len = calculate_length(&s1); // s1의 참조를 전달
        println!("길이는 {}입니다.", len);
        println!("s1은 여전히 사용 가능: {}", s1);
      }
      

    fn calculate_length(s: &String) -> usize {

    s.len()

    }

  • 가변 참조와 불변 참조

    • 불변 참조는 여러 개 가능

    • 가변 참조는 한 번에 하나만 가능

    • 가변/불변 참조 동시 사용 불가
      예제:

      fn main() {
        let mut s = String::from("hello");
      
        let r1 = &s; // 불변 참조 ok
        let r2 = &s; // 불변 참조 ok
        println!("{}, {}", r1, r2);
      
        let r3 = &mut s; // 가변 참조
        println!("{}", r3);
      }
  • 댕글링 참조 방지하기

    • 컴파일러가 참조의 유효성 검사
    • 수명이 끝난 데이터 참조 방지
      예제:
      fn main() {
        let reference_to_nothing = dangle();
      }
      

    fn dangle() -> &String { // 컴파일 에러

    let s = String::from("hello");
    &s // s가 함수 끝에서 drop되어 댕글링 참조 발생

    }
    ```

  • 예제: 참조로 함수 매개변수 전달하기

    fn calculate_length(s: &String) -> usize {
        s.len()
    }
    
    let s1 = String::from("hello");
    let len = calculate_length(&s1);
    println!("The length of '{}' is {}.", s1, len);

4.3 슬라이스로 데이터 참조하기

  • 문자열 슬라이스란?

    • 문자열의 일부분을 참조하는 슬라이스
    • 예제:
      let s = String::from("hello world");
      let hello = &s[0..5];  // "hello"
      let world = &s[6..11]; // "world"
  • 배열 슬라이스 사용법

    • 배열의 일부분을 참조하는 슬라이스
    • 예제:
      let a = [1, 2, 3, 4, 5];
      let slice = &a[1..3]; // [2, 3]
  • 슬라이스 매개변수 활용

    • 문자열 슬라이스를 매개변수로 사용

    • &str 타입으로 String과 &str 모두 처리 가능

    • 예제:

      fn first_word(s: &str) -> &str {
          let bytes = s.as_bytes();
      
          for (i, &item) in bytes.iter().enumerate() {
              if item == b' ' {
                  return &s[0..i];
              }
          }
          &s[..]
      }
      
      // String이나 &str 모두 사용 가능
      let my_string = String::from("hello world");
      let word = first_word(&my_string);
      
      let my_string_literal = "hello world";
      let word = first_word(my_string_literal);
  • 예제: 첫 번째 단어 찾기 함수 만들기

    fn first_word(s: &str) -> &str {
        let bytes = s.as_bytes();
    
        for (i, &item) in bytes.iter().enumerate() {
            if item == b' ' {
                return &s[0..i];
            }
        }
    
        &s[..]
    }

4.4 메모리와 할당

  • 스택과 힙의 차이점

    • 스택: 고정된 크기의 데이터 저장
      let x = 5; // 스택에 저장
      let y = [1, 2, 3]; // 고정 크기 배열도 스택에 저장
    • 힙: 크기가 변할 수 있는 데이터 저장
      let s = String::from("hello"); // 힙에 문자열 데이터 저장
      let v = vec![1, 2, 3]; // 벡터도 힙에 저장
  • Copy 트레이트와 Drop 트레이트

    • Copy: 값의 복사가 가능한 타입
      let x = 5;
      let y = x; // x의 값이 y로 복사됨
      println!("x = {}, y = {}", x, y); // 둘 다 사용 가능
    • Drop: 메모리 해제가 필요한 타입
      let s1 = String::from("hello");
      let s2 = s1; // s1의 소유권이 s2로 이동
      // println!("{}", s1); // 오류! s1은 이미 이동됨
  • 소유권과 함수

    • 함수에 값 전달 시 소유권 이동

      fn main() {
          let s = String::from("hello");
          takes_ownership(s); // s의 소유권이 함수로 이동
          // println!("{}", s); // 오류! s는 이미 이동됨
      
          let x = 5;
          makes_copy(x); // x는 Copy 타입이라 복사됨
          println!("{}", x); // 정상 동작
      }
      
      fn takes_ownership(s: String) {
          println!("{}", s);
      }
      
      fn makes_copy(i: i32) {
          println!("{}", i);
      }
  • 예제: Vector를 이용한 메모리 할당/해제

    fn main() {
        let mut v = Vec::new();
        v.push(1);
        v.push(2);
        v.push(3);
    
        // v의 소유권이 print_vector로 이동
        print_vector(v);
    
        // v는 이미 이동되어 사용할 수 없음
        // println!("{:?}", v); // 오류!
    }
    
    fn print_vector(v: Vec<i32>) {
        println!("Vector: {:?}", v);
    } // v가 스코프를 벗어나면서 자동으로 메모리 해제

4.5 실전 연습 문제

  • 문자열 처리하기

    fn main() {
        let mut s = String::from("hello world");
        let word = first_word(&s);
        s.clear(); // 오류! word는 여전히 전체 문자열을 참조하고 있음
    }
    
    fn first_word(s: &String) -> &str {
        let bytes = s.as_bytes();
    
        for (i, &item) in bytes.iter().enumerate() {
            if item == b' ' {
                return &s[0..i];
            }
        }
    
        &s[..]
    }
  • 벡터 다루기

    fn main() {
        let mut numbers = vec![1, 2, 3, 4, 5];
    
        // 벡터의 가변 참조로 수정
        modify_vector(&mut numbers);
        println!("수정된 벡터: {:?}", numbers);
    
        // 벡터의 불변 참조로 합계 계산
        let sum = calculate_sum(&numbers);
        println!("벡터의 합: {}", sum);
    }
    
    fn modify_vector(v: &mut Vec<i32>) {
        v.push(6);
        v[0] = 10;
    }
    
    fn calculate_sum(v: &Vec<i32>) -> i32 {
        v.iter().sum()
    }
  • 소유권 이동 추적하기

    fn main() {
        let s1 = String::from("안녕하세요");
        let s2 = s1; // s1의 소유권이 s2로 이동
    
        // println!("{}", s1); // 오류! s1은 이미 이동됨
        println!("{}", s2); // 정상 동작
    
        let s3 = s2.clone(); // 깊은 복사로 새로운 소유권 생성
        println!("s2: {}, s3: {}", s2, s3); // 둘 다 사용 가능
    }
  • 참조 규칙 적용하기

    fn main() {
        let mut data = String::from("테스트");
    
        let r1 = &data; // 첫 번째 불변 참조
        let r2 = &data; // 두 번째 불변 참조 (가능)
        println!("{}, {}", r1, r2);
    
        let r3 = &mut data; // 가변 참조 (r1, r2는 더 이상 사용되지 않으므로 가능)
        r3.push_str("123");
        println!("{}", r3);
    }
728x90
반응형

'IT Best Practise > Rust' 카테고리의 다른 글

06. 열거형과 패턴 매칭  (0) 2024.11.07
05. 구조체  (1) 2024.11.07
03. 일반적인 프로그래밍 개념  (0) 2024.11.07
02. 추리 게임 튜토리얼  (0) 2024.11.07
01. Getting Start  (0) 2024.11.07

+ Recent posts