4.1. 소유권이 뭔가요?

소유권 규칙

  1. Rust의 각각의 값은 해당 값의 오너(owner)라고 불리는 변수를 갖고 있다.
  1. 한 번에 딱 하나의 오너만 존재할 수 있다.
  1. 오너가 스코프 밖으로 벗어나는 때, 값은 버려진다(dropped).
 
 

변수의 스코프

{ // s는 유효하지 않습니다. 아직 선언이 안됐거든요. let s = "hello"; // s는 이 지점부터 유효합니다. // s를 가지고 뭔가 합니다. } // 이 스코프는 이제 끝이므로, s는 더이상 유효하지 않습니다.
 

String 타입

  • 스트링 리터럴은 불변!
  • String 타입의 인스턴스는 가변!
let mut s = String::from("hello"); s.push_str(", world!"); // push_str()은 해당 스트링 리터럴을 스트링에 붙여줍니다. println!("{}", s); // 이 부분이 `hello, world!`를 출력할 겁니다.
 

메모리 할당

{ let s = String::from("hello"); // s는 여기서부터 유효합니다 // s를 가지고 뭔가 합니다 } // 이 스코프는 끝났고, s는 더 이상 // 유효하지 않습니다
 

변수와 데이터가 상호작용하는 방법: 이동(move)

  • 정수에서는 값 의미. 복사가 일어남.
let x = 5; let y = x;
 
  • String 타입에서는...
let s1 = String::from("hello"); let s2 = s1;
 
s1 변수에 "hello"값이 저장된 String의 메모리 구조s1 변수에 "hello"값이 저장된 String의 메모리 구조
s1 변수에 "hello"값이 저장된 String의 메모리 구조
s1의 포인터, 길이값, 용량값이 복사된 s2 변수의 메모리 구조 (참조 의미)s1의 포인터, 길이값, 용량값이 복사된 s2 변수의 메모리 구조 (참조 의미)
s1의 포인터, 길이값, 용량값이 복사된 s2 변수의 메모리 구조 (참조 의미)
러스트가 힙 데이터까지 복사하게 될 경우 s2 = s1가 만들 또다른 가능성 (값 의미)러스트가 힙 데이터까지 복사하게 될 경우 s2 = s1가 만들 또다른 가능성 (값 의미)
러스트가 힙 데이터까지 복사하게 될 경우 s2 = s1가 만들 또다른 가능성 (값 의미)
  • 그럼 실제로 어떻게 되나?
 
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
  • let s2 = s1;이 실행되는 순간 값이 이동된다!!!
 
s1이 무효화된 후의 메모리 구조. Rust가 실제로 하는 일.s1이 무효화된 후의 메모리 구조. Rust가 실제로 하는 일.
s1이 무효화된 후의 메모리 구조. Rust가 실제로 하는 일.
  • Rust는 실행 시간에 결코 자동으로 깊은 복사(deep copy)를 하지 않음.
 

변수와 데이터가 상호작용하는 방법: 클론

 
let s1 = String::from("hello"); let s2 = s1.clone(); println!("s1 = {}, s2 = {}", s1, s2);
  • 값을 복사하려면 명시적으로 clone() 메소드를 호출한다.
  • 성능이 느려질 수도 있음을 시각적으로 표시.
 

스택에만 있는 데이터: 복사

let x = 5; let y = x; println!("x = {}, y = {}", x, y);
  • 컴파일 시간에 결정되어 있는 크기의 자료형은 스택에 모두 저장!
  • 값이 박사되려면 Copy 트레잇을 갖고 있어야!
  • 정수, 실수, 부울 등은 Copy 가능
  • Copy 가능한 데이터들의 튜플 역시 Copy 가능
 

소유권과 함수

fn main() { let s = String::from("hello"); // s가 스코프 안으로 들어왔습니다. takes_ownership(s); // s의 값이 함수 안으로 이동했습니다... // ... 그리고 이제 더이상 유효하지 않습니다. let x = 5; // x가 스코프 안으로 들어왔습니다. makes_copy(x); // x가 함수 안으로 이동했습니다만, // i32는 Copy가 되므로, x를 이후에 계속 // 사용해도 됩니다. } // 여기서 x는 스코프 밖으로 나가고, s도 그 후 나갑니다. 하지만 s는 이미 이동되었으므로, // 별다른 일이 발생하지 않습니다. fn takes_ownership(some_string: String) { // some_string이 스코프 안으로 들어왔습니다. println!("{}", some_string); } // 여기서 some_string이 스코프 밖으로 벗어났고 `drop`이 호출됩니다. 메모리는 // 해제되었습니다. fn makes_copy(some_integer: i32) { // some_integer이 스코프 안으로 들어왔습니다. println!("{}", some_integer); } // 여기서 some_integer가 스코프 밖으로 벗어났습니다. 별다른 일은 발생하지 않습니다.
  • 와, 대박!! String 인스턴스를 함수로 넘기면 무효화된다고? 음... 그래서 참조자가 필요한 건가?
 

반환값과 스코프

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은 반환되고, 호출한 쪽의 함수로 이동됩니다. }
 
  • 헐... 이게 이래버리면 정말 함수 호출할 때 주고 받고 주고 받고... 뭐냐!
  • 흠.... 그래서 결국 참조자가 필요한 거군! Rust 좋아!