Ownership, Move, Borrowing

part 1 오너쉽 그리고 이동 (Ownership and moves)

메모리에 저장된 리소스는 오직 한 변수만을 오너로 삼는다. 참고할 것은 모든 변수가 리소스를 소유하고 있는 것은 아니다. 레퍼런스를 가질 때도 있기 때문이다.

let x = y를 선언하거나 foo라는 함수에 foo(x) 를 적용하였을 시에는 오너쉽이 이동하게 된다. y는 x 변수 안으로, 함수에서 x는 foo라는 함수 안으로 이동.

러스트에서는 이러한 이동을 move라고 얘기한다.

이렇게 이동하게 된 값, 즉 y나 함수에서 사용된 x값은 더 이상 사용할 수 없다. 이렇게 하면 dangling pointers 를 예방할 수 있다.

part 2 변환 (Mutability)

오너쉽이 다른 변수로 이동하였을 때, 변환타입(immutable -> mutable) 변경이 가능하다.

#![allow(unused)]

fn main() {
 let immut_box = Box::new(5u32);

 let mut mut_box = immut_box;

}

part 3 부분 이동(Partial moves)

변수를 destructuring 할 때, by-move, by-reference 패턴 바인딩이 동시에 가능하다. 다만 이렇게 할 때, 부분적으로 변수가 이동하게 된다. 즉 변수의 한 부분이 이동할 때 다른 한 부분은 변수에 머물게 된다. 이런 경우는 해당 변수는 전체로써 사용은 불가능하지만 부분적으로는 사용이 가능하다.


fn main() {


     #[derive(Debug)]
     struct Person {
         name : String,
         age : Box<u8>
     }
      
     let person = Person { name: String::from("Tony"), age: Box::new(35) };
    
     let Person { name, ref age } = person;
   
     println!("what is your name : {:?}", name);
     let name_move = name;
    
     println!("{:?}", name_move);
    
     println!("{:?}", person.age);
     // println!("{:?}", person); // 전체 객체로는 사용이 불가능
}

part 4 빌림 (Borrowing)

대부분 데이터에 접근할 때 오너쉽을 가져와서 사용하지 않는다. 이때 러스트에서는 빌림 메커니즘을 이용한다(borrowing mechanism). 값을 (T) 를 그대로 던져주는 것이 아닌 (&T) 를 사용한다.

러스트 컴파일러는 정적인 상태에서도 (borrow checker 를 통해) 우리가 사용 중인 reference 에 대해 항상 값이 존재하는 것을 보장한다. 즉 참조값이 존재한다는 것은 해당 객체가 없어지는 않는다는 것이다.


fn main() {


     fn eat_box_i32(boxed_i32 : Box<i32>) {
         println!("Destroying box that contains {}", boxed_i32);
     }
   
     fn borrow_i32(borrowed_i32 : &i32) {
         println!("This int is : {}", borrowed_i32);
     }
   
     let boxed_i32 = Box::new(5_i32);
     let stacked_i32 = 6_i32;
     
     borrow_i32(&boxed_i32);
     borrow_i32(&stacked_i32);
   
     {
         let _ref_to_i32 = &boxed_i32;
         println!("{:?}", _ref_to_i32);
     } 
     // &boxed_i32 값은 _ref_to_i32 로 이동하였지만 {} 스쿠핑 룰에 의해 참조값은 드랍된다. 
     // 온전한 값으로 다시 사용 가능.
    eat_box_i32(boxed_i32);
   }

part 5 변환 (Mutability)

변환 데이터 (Mutable data) 는 &mut T 를 이용하여 mutably borrowed 가 가능하다. 이를 mutable reference 라 하며 참조값에 대해 읽기/쓰기 접근이 가능하다. 반대로 &T 는 immutably borrowed 가 가능하며 immutable reference 하며 참조값에 대해 읽기 접근만 가능하다.



fn main() {
  
    struct Book {
        author : &'static str,
        title : &'static str,
        year : u32,
    }

    fn borrow_book(book : &Book) {
        println!("immutably borrowed {} - {} edition", book.title, book.year);
    }

    fn new_edition(book : &mut Book) {
        book.title = "Rust란";
        book.year = 2014;
        println!("mutably borrowed {} - {} edition", book.title, book.year);
    }

    let mut immutabook = Book {
        author: "tony",
        title: "rust란",
        year: 2020,
    };


    borrow_book(&immutabook);

    new_edition(&mut immutabook);

}

part 6 The ref pattern

pattern matching 이나 let 바인딩을 이용해 destructuring 할 때, ref는 해당 필드를 참조값으로 가져올 수 있다. 아래 예제를 살펴보자.


#[derive(Clone, Copy)]
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let c = 'Q';

    let ref ref_c1 = c;
    let ref_c2 = &c;


    println!("ref_c1 와 ref_c2 는 값이 같다.  {}", *ref_c2 == *ref_c1);

    let point = Point { x: 3, y: 3 };

    let _copy_of_x = {
        let Point{ x : ref ref_to_x, y : _ } = point;
    };

    // mutable copy of point
    let mut mutable_point = point;

    {
        let Point { x:_, y: ref mut mut_ref_to_y } = mutable_point;

        *mut_ref_to_y = 30;
    }

    println!(" point is ( {}, {})", point.x, point.y );

    println!(" mutable_point is ( {}, {})", mutable_point.x, mutable_point.y );


    // 포인터를 포함하는 mutable 튜플
    let mut mutable_tuple_with_pointer = (Box::new(5u32), 3u32);

    {
        let (_, ref mut val) = mutable_tuple_with_pointer;
        *val = 30u32;

    }

    println!("mutable_tuple_with_pointer : ( {}, {} ) ", mutable_tuple_with_pointer.0, mutable_tuple_with_pointer.1);


}