러스트 튜토리얼 (3) - 소유권 이해하기

DaeyeolKim·2022년 5월 14일
0

러스트 내공부

목록 보기
3/4
  • 러스트의 핵심기능은 소유권
    • 다른 언어들은 프로그래머가 직접 명시적으로 메모리 할당 및 해제

    • 러스트는 제3의 접근법 이용

      • 힙 데이터를 관리하는 것이 곧 소유권의 이유(?)
        • 소유권을 가진다 == 메모리 해제/변경 할 권한을 갖는다의 느낌으로 받아들려진다!@
  • 소유권의 규칙
    1. 러스트의 각각의 값은 해당값의 owner라고 불리는 변수를 가짐
    2. 한번에 딱 하나의 오너만 존재
    3. 오너가 스코프 밖으로 벗어날때, 값은 버려짐(dropped)
  • 변수의 스코프

{                      // s는 유효하지 않습니다. 아직 선언이 안됐거든요.
    let s = "hello";   // s는 이 지점부터 유효합니다.

    // s를 가지고 뭔가 합니다.
}                      // 이 스코프는 이제 끝이므로, s는 더이상 유효하지 않습니다.
  • 메모리와 할당
    • 다른 프로그래밍 언어는 allocate와 free 한쌍으로 메로리 할당,해제가 필요함
    • 러스트는 변수 스코프를 벗어날떄 저절로 drop을 호출하여 자연스럽게 메모리 반납 ( Resource Acquisition is Initialization, RAII 패턴)
  • 변수와 데이터가 상호작용하는 방법(MOVE_ 데이터가 스택 & 힙에 있는 경우)
let s1 = String::from("hello");
let s2 = s1;

println!("{}, world!", s1);

error[E0382]: use of moved value: `s1`
 --> src/main.rs:4:27
  |
3 |     let s2 = s1;
  |         -- value moved here
4 |     println!("{}, world!", s1);
  |                            ^^ value used here after move
  |
  = note: move occurs because `s1` has type `std::string::String`,
which does not implement the `Copy` trait

  • 요약
    • 러스트에서는 다른 언어들 처럼 (=) 으로 데이터가 복사 되지 않음
    • (=)을 쓰면 데이터가 이동 되어 최초의 할당된 s1은 사용 불가함
    • 만약 move되지 않았다면, 두개의 변수가 같은 메모리를 참조하게 되고, 1할당 2해제의 경우가 발생하여 (double free) 문제가 발생함. 이는 메모리 손상(memory corruption)의 원인이 되고, 보안 취약성을 일으킬 수 있음
    • 포인터로 힙의 메모리를 가리키는 경우에만 해당하는 것으로 생각됨(런타임에서 생기는 변수들)
  • 변수와 데이터가 상호작용하는 방법(Clone)
    - 스택 데이터 뿐만아니라 힙데이터를 복사하려고하면 clone을 이용
    • 고 코스트의 연산
let s1 = String::from("hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
  • 스택에만 있는 데이터 복사
let x = 5;
let y = x;
println!("x = {}, y = {}", x, y);
  • 요약

    • 컴파일 타임에 결정되는 스택 메모리만 이용하는 변수들은 (=) 사용 가능

    • 스택에 저장할 수 있는 타입은 Copy 트레잇 라고 불리우는 어노테이션 소유(트레잇은 이후에 소개)

    • copy 가능 타입

      • u32
      • bool
      • f32
      • Copy 가능 타입으로 이루어진 튜플
  • 예제
fn main() {
    let s1 = gives_ownership();         // gives_ownership은 반환값을 s1에게
                                        // 이동시킵니다.

    let s2 = String::from("hello");     // s2가 스코프 안에 들어왔습니다.

    let s3 = takes_and_gives_back(s2);  // s2는 takes_and_gives_back 안으로
                                        // 이동되었고, 이 함수가 반환값을 s3으로도
                                        // 이동시켰습니다.

} // 여기서 s3는 스코프 밖으로 벗어났으며 drop이 호출됩니다. s2는 스코프 밖으로
  // 벗어났지만 이동되었으므로 아무 일도 일어나지 않습니다. s1은 스코프 밖으로
  // 벗어나서 drop이 호출됩니다.

fn gives_ownership() -> String {             // gives_ownership 함수가 반환 값을
                                             // 호출한 쪽으로 이동시킵니다.

    let some_string = String::from("hello"); // some_string이 스코프 안에 들어왔습니다.

    some_string                              // some_string이 반환되고, 호출한 쪽의
                                             // 함수로 이동됩니다.
}

// takes_and_gives_back 함수는 String을 하나 받아서 다른 하나를 반환합니다.
fn takes_and_gives_back(a_string: String) -> String { // a_string이 스코프
                                                      // 안으로 들어왔습니다.

    a_string  // a_string은 반환되고, 호출한 쪽의 함수로 이동됩니다.
}
  • 참조자(References)와 빌림(Borrowing)

    • 참조자 : 소유권을 갖지않지만 참조 가능하게함
    • 빌림 : 함수의 파리미터로 참조자를 만드는 것
      fn main() {
        let s1 = String::from("hello");
        let len = calculate_length(&s1);
        println!("The length of '{}' is {}.", s1, len);
      }
      fn calculate_length(s: &String) -> usize {
        s.len()
      }
  • 가변 참조자(Mutable References)

    • &mut 변수명 으로 가변 참조자 생성
    • 가번 참조자는 데이터 한개에 딱 하나만 만들 수 있음(두개의 가변참조자가 한 값을 참조하는 것 불가) - 이 때 불변 참조자도 못 만듬!
    • 데이터 레이스(data race) 방지 - 데이터레이스란 한개의 변수에 여러 스레드/상황에서 읽고 쓰기 할 때 순서에 따라 문제가 생기는 경우를 말함
      • 데이터 레이스의 조건
        • 두 개 이상의 포인터가 동시에 같은 데이터에 접근
        • 그 중 적어도 하나의 포인터가 데이터를 씀
        • 데이터를 동기화하는 어떠한 매커니즘도 없음
let mut s = String::from("hello");

let r1 = &s; // 문제 없음
let r2 = &s; // 문제 없음
let r3 = &mut s; // 큰 문제

error[E0502]: cannot borrow `s` as mutable because it is also borrowed as
immutable
 --> borrow_thrice.rs:6:19
  |
4 |     let r1 = &s; // 문제 없음
  |               - immutable borrow occurs here
5 |     let r2 = &s; // 문제 없음
6 |     let r3 = &mut s; // 큰 문제
  |                   ^ mutable borrow occurs here
7 | }
  | - immutable borrow ends here
  • 댕글링 참조자(Dangling References)

    • 포인터가 있는 언어에서는 잘못하면 댕글링 포인터를 만들기 쉬움

      • 댕글링 포인터 : 어떤 메모리를 가리키는 포인터가 잇는동안, 그 메모리를 해제함으로써 다른 개체에게 사용하도록 줘버렸을지 모를 메모리를 참조하고 있는 포인터. 쉽게 해제된 메모리를 참조 중

      • 아래 예제에서, 라이프 타임 때문에 댕글링 포인터가 생김

fn main() {
    let reference_to_nothing = dangle();
}

fn dangle() -> &String {
    let s = String::from("hello");

    &s
}

error[E0106]: missing lifetime specifier
 --> dangle.rs:5:16
  |
5 | fn dangle() -> &String {
  |                ^^^^^^^
  |
  = help: this function's return type contains a borrowed value, but there is no
    value for it to be borrowed from
  = help: consider giving it a 'static lifetime

error: aborting due to previous error

참조자 요약

  • 어떤 경우든 아래 둘 중 하나만 가질 수 있음
    • 하나의 가변 참조자
    • 여러개의 불변 참조자
    • 참조자는 항상 유효해야 함

  • 슬라이스

    • 소유권을 갖지 않는 데이터 타입

    • 스트링 슬라이스

    		fn main() {
    		let s = String::from("hello world");
    	
    		let hello = &s[0..5];
    		let world = &s[6..11];
    		}
    

    • 그 밖의 슬라이스
    	let a = [1, 2, 3, 4, 5];
    	let slice = &a[1..3];
profile
BioSignal Processing/ Deep Learning/ Block Chain

0개의 댓글