# [Rust] 소유권 이해하기

# 스택 & 힙

소유권의 주 목표는 힙 데이터의 관리이다.

스택과 힙은 모두 프로그램이 런타임에 이용하게 될 메모리 영역이지만, 차이점이 있다.

## 스택

스택은 후입선출(last in first out)방식으로 값을 처리한다. 입출구가 하나인 통처럼 맨 꼭대기에서 무언가를 집어넣고 꺼내는 것은 가능하지만 중간과 아래에 있는 것은 바로 건드릴 수 없다. 스택에 저장될 데이터는 모두 명확하고 크기가 정해져 있어야 한다. 컴파일 타임에 크기를 알 수 없거나 크기가 변할 수 있는 데이터는 힙에 저장되어야 한다.

함수를 호출하면 호출한 함수에 넘겨준 값과 해당 함수의 로컬 변수들이 스택에 들어가고, 이 데이터들은 함수가 종료될 때 팝된다.

## 힙

데이터에 힙을 넣을 때 절차는 다음과 같다.

1. 저장할 공간이 있는지 운영체제에 물어보고
    
2. 메모리 할당자가 힙 영역 내에서 빈 지점을 찾는다.
    
3. 아까 발견한 빈 지점을 사용 중이라고 표시하고
    
4. 그 지점을 가리키는 포인터를 반환한다.
    

이 전체 과정을 `힙 공간 할당`이라고 한다.

레스토랑에 비유하자면

1. 레스토랑에 방문하는 인원수를 수용할 좌석이 있는지 묻는다.
    
2. 직원은 빈 테이블을 찾아서
    
3. 테이블이 사용 중임으로 상태를 바꾸고(비유가 이상하긴 한데 다른 손님이 들어왔을 때 여전히 빈 테이블이라고 하지는 않을테니까...)
    
4. 손님에게 테이블 위치를 안내할 것이다.
    

라고 할 수 있다.

힙 영역은 포인터가 가리키는 곳을 찾아가는 과정 때문에 느려진다. 힙에 있는 데이터들은 붙어 있는 것이 아니라 이곳 저곳에 떨어져 있는데 이렇게 되면 프로세서가 돌아다니면서 데이터들을 처리해야하기 때문에 느리다.

---

# 소유권 규칙

소유권 규칙은 세 가지가 있다.

1. 각각의 값은 소유자(Owner)가 정해져 있다.
    
2. 한 값의 소유자는 동시에 여럿 존재할 수 없다.
    
3. 소유자가 스코프 밖으로 벗어나면 값은 버려진다(dropped).
    

# 변수의 스코프

스코프란 프로그램 내에서 아이템이 유효한 범위를 말한다. 예제로 보자면

```rust
{ // s가 선언되기 전이다. 아직 유효하지 않다
	let s = "Hello"; // s가 선언되어 여기서부터 유효하다
	// s로 어떠한 작업을 한다.
} // 스코프가 종료되어 s가 유효하지 않다.
```

위 에제에서

1. s가 스코프 내에 나타나면 유효하고
    
2. 유효기간은 스코프 밖으로 벗어나기 전이다(벗어나면 유효하지 않다)
    

---

# String

바로 위의 문자열 리터럴은 불변(immutable)하기 때문에 사용자에게 문자열을 입력받아 사용하거나 하는 것은 불가능하다. 그렇기 때문에 러스트는 또 다른 문자열 타입인 `String` 타입을 제공하는데 소유권 규칙을 이해하기 좋은 타입이다. `String` 타입은

```rust
let s = String::from("Hello");
```

와 같이 생성한다. `::`은 `String` 타입에 있는 특정한 `from`함수라는 것을 지정할 수 있게 해주는 네임스페이스 연산자다.

대충 예상이 가겠지만 `String`은 가변(mutable)하다.

```rust
let s = String::from("Hello");
s.push_str(", world!"); //문자열에 리터럴 추가
```

를 하게되면 `s`는 `Hello, world!`가 된다.

그런데 문자열 리터럴과 `String`이 따로 존재할 이유는 무엇이며, 왜 하나는 불변이고 하나는 가변인가?

---

# 메모리와 할당

문자열 리터럴은 컴파일 타임에 내용을 알 수 있고 크기가 고정적이다. 아까 스택과 힙의 설명에 기반하자면 스택에 더 적합해보이는 친구이다.

`String`은 가변적인 값으로 크기가 변할 수 있다. 힙에 메모리를 할당해서 관리해야 하는 친구다.

어째거나 힙에 메모리를 할당해야 하는 `String`은

* 실행 중 메모리 할당자로부터 메모리를 요청하고
    
* 사용을 마치면 메모리를 해제해야 한다.
    

타 언어들과 비교하자만 가비지 컬렉터가 있으면 사용하지 않는 메모리를 자동으로 해제해주므로 신경쓸 필요가 없고, C같은 경우 `free()`로 해제해줘야 했다.

러스트에서는 이러한 메모리 해제가 **변수가 자신이 소속된 스코프를 벗어나는 순간 메모리가 자동으로 해제되는 방식**으로 진행된다.

```rust
{
	let s = String::from("hello");
} // 스코프 종료
```

위 코드에서 `s`가 스코프를 벗어날 때(닫힌 중괄호 } 가 나타나는 지점) 러스트는 `drop`이라는 함수를 자동으로 호출한다.

아직은 별 게 아닌 것 같지만 이러한 패턴 때문에 복잡한 상황이 생기기도 한다.

---

## 이동(Move)

```rust
let s1 = String::from("hello");
let s2 = s1;
```

코드의 첫 줄에서는

![](https://i.imgur.com/UjEZD99.png align="left")

와 같은 일이 일어난다.

그런데 `s2`에 `s1`를 대입할 때 스택에 있는 데이터만 복사되지 포인터가 가리키는 힙 영역의 데이터는 복사되지 않는다.

![](https://i.imgur.com/Kh3Jrtr.png align="left")

그래서 위와 같은 메모리 구조를 가지게 된다.

앞에서 변수가 스코프 밖으로 벗어날 때 러스트에서 `drop`을 호출하여 변수가 사용하는 힙 메모리를 제거한다고 했는데, 포인터 두 개가 같은 곳을 가리키면 어떻게 될까?

`s1`, `s2`가 스코프 밖으로 벗어날 때 각각 메모리를 해제하게 되면 중복 해제(double free) 에러가 발생하게 될 것이다.

이 문제를 해결하기 위해 러스트는 `let s2 = s1;` 라인 뒤로 `s1`이 유효하지 않다고 판단한다. 그래서

```rust
let s1 = String::from("hello");
let s2 = s1;
println!("{}, world!", s1);
```

는 유효하지 않은 참조자 사용을 감지했다며 컴파일 에러를 일으킬 것이다.

다른 언어의 얕은 복사(shallow copy), 깊은 복사(deep copy) 개념을 떠올려본다면 아까처럼 힙 데이터를 복사하지 않는

![](https://i.imgur.com/tqj8Naz.png align="left")

이러한 상황을 얕은 복사

힙 메모리까지 복사되는

![](https://i.imgur.com/oZbPWvp.png align="left")

이러한 상황을 깊은 복사라고 할텐데

러스트에서는 얕은 복사 개념이 존재하지 않는다. 두 포인터가 같은 곳을 가리키는 게 아니라 이제 `s1`은 유효하지 않아질 것이기 때문이다. `s1`에서 `s2`로 **이동**한 것과 같은 상황이 되는데 진짜로 이동(move)라고 한다.

더해서 러스트는 깊은 복사를 자동으로 수행하는 일이 없다. 힙 메모리까지 복사하는 것은 비효율적인 작업이 될 수 있는데(특히 데이터가 크다면) 러스트가 자동으로 수행하는 복사는 이러한 경우가 없다. 물론 수동으로는 가능하다.

---

## 클론(Clone)

이거다. 힙 데이터까지 복사(깊은 복사)하고 싶으면 `clone`을 쓰면 된다.

```rust
let s1 = String::from("Hello");
let s2 = s1.clone();
println!("s1 = {}, s2 = {}", s1, s2);
```

깊은 복사이기 때문에 애초에 포인터는 다른 곳을 가리킨다.(데이터 내용이 같은거지 위치는 다르다) 그래서 `s1`은 유효한 것.

---

## 복사(Copy)

그런데

```rust
let x = 1;
let y = x;
println("x = {}, y = {}", x, y);
```

를 실행해보면 `x`가 여전히 유효한데 애초에 이런 컴파일 타임에 크기를 알 수 있는 고정 크기 값들은 스택에 저장되기 때문이다.

소유권 개념은 힙 메모리 관리에 적용되는 개념이므로 여기에는 적용되지 않는다.

이런 복사가 가능한 타입은

* 일반적인 스칼라 타입
    
* 복사 가능한 타입으로 구성된 튜플 이 가능하다.
    

---

# 소유권과 함수

함수로 값을 전달하는 것도 비슷한 매커니즘으로 작동한다. 함수에 변수를 전달하면 대입과 마찬가지로 이동이나 복사가 일어나기 때문.

```rust
fn main() {
	let s = String::from("Hello"); //여기서부터 s가 스코프 안으로

	takes_ownership(s): //s의 값이 함수로 이동
	// 여기서부턴 s가 유효하지 않다.
	// s를 사용하려고 하면 컴파일 에러가 나타날 것.

	let x = 5; // x가 스코프 안으로

	makes_copy(x); // x가 함수로 Copy
}

fn takes_ownership(some_string: String) { //  some_string이 스코프 안으로
	println!("{}", some_string);
} // some_string이 스코프 밖으로 벗어남.
// 메모리 해제

fn makes_copy(some_integer: i32) { // some_integer가 스코프 안으로
	println!("{}", some_integer);
} // 닫는 중괄호가 나타났지만 어차피 스택 써서 아무런 일이 발생하지 않았다...
```

원리는 다르지 않다. Copy의 경우 신경 쓸 게 없지만 문자열을 함수에 집어넣어 호출한 이후 다시 `s`를 사용하려고 하면 컴파일 에러가 나타난다.

---

# 반환 값과 스코프

값을 넣는 과정뿐 아니라 반환하는 과정에서도 소유권이 이동한다.

```rust
fn main() {
    let s1 = gives_ownership(); // gives_ownership이 반환 값을 s1으로
    let s2 = String::from("Hello"); // s2가 스코프 안으로

    let s3 = takes_and_gives_back(s2); // s2가 함수로 값을 이동
    // 함수도 값을 s3으로 이동
} // s3이 스코프 밖으로 벗어나면서 drop
// s1도 스코프 밖으로 벗어나고 버려진다

fn gives_ownership() -> String {
    // gives_ownership은 자신의 반환 값을 자신의 호출자 함수로
    // 이동시키게 된다
    let some_string = String::from("yours"); //some_string이 스코프 안으로

    some_string // some_string이 반환되고 호출자 함수로 이동
}

fn takes_and_gives_back(a_string: String) -> String {
    // a_string이 스코프 안으로
    a_string // a_string이 반환되고 호출자 함수로 이동
}
```

상황이 달라도 소유권 규칙은 동일하다.

그런데 함수에 값을 넘겨줬어도 그 값을 여전히 쓰고 싶을 수 있다. 그래서 값을 사용할 수 있게 하되 소유권은 가져가지 말라고 하고 싶다면 어떻게 하는가?

```rust
fn main() {
    let s1 = String::from("hello");

    let (s2, len) = calculate_length(s1);

    println!("The length of '{}' is {}.", s2, len);
}

fn calculate_length(s: String) -> (String, usize) {
    let length = s.len(); // len()은 String의 길이를 반환합니다

    (s, length)
}
```

위의 코드처럼 튜플을 이용해 여러 값을 반환받는 것은 가능하지만, 상당히 보기 좋기 않다...그래서 소유권 이동 없이 값을 사용할 수 있도록 참조자(reference)라는 기능을 가지고 있다.
