러스트를 배우면서 가장 신기했던 부분은 바로 소유권이였다.
러스트 소유권에는 세가지 규칙을 따른다.
- 각각의 값은 소유자 (owner) 가 정해져 있다.
- 한 값의 소유자는 동시에 여럿 존재할 수 없다.
- 소유자가 스코프 밖으로 벗어날 때, 값은 버려진다 (dropped).
소유자 개념을 이해하기 위해 한가지 예시를 들어보겠다.
위 코드는 "레디"라는 문자열 객체를 만든 다음 tmp라는 변수에 할당을 해준다음 name을 출력하는 코드이다.
자바로 작성하면 아래와 같다.
자바에서는 아무런 문제 없이 돌아가지만 이 코드가 러스트에선 에러가 발생한다.
에러 발생 이유는 바로 소유권 때문이다.
처음에는 name이 힙 메모리에 저장된 문자열 "레디"를 소유하게 된다. 그 다음 tmp = name을 실행하면, 소유권이 name에서 tmp로 이동하게 된다. 러스트는 기본적으로 값의 소유권을 이동하며, 이동 이후 원래 소유자는 더 이상 해당 값을 사용할 수 없다. 소유권이 이미 tmp로 이동했기 때문에, name은 더 이상 유효하지 않고 이 name을 사용하려 할 때 예외가 발생하는 것이다.
러스트의 String 저장 방식을 살펴봐보자.
String은 힙에 데이터를 저장하는 소유권이 있는 타입이다. 이를 관리하기 위해 String은 포인터, 길이, 용량 정보를 스택에 저장한다.
- 포인터(Pointer): 힙 메모리에서 실제 문자열 데이터를 가리킨다.
- 길이(length): 문자열 데이터의 길이를 나타낸다.
- 용량(capacity): 힙에 할당된 메모리의 크기를 나타낸다.
이 상태에서 tmp = name을 실행하면 아래 그림처럼 될 것이다.
위와 같은 경우는 한 값의 소유자가 둘 이상 있는 경우니 러스트에서는 처음 name의 소유권을 해제시킨다.
이제 러스트의 소유권 개념에 대해 살짝 감이 잡혔을거라 믿는다.
또 다른 예제를 가져와서 살펴봐보자.
위 코드는 파라미터로 받은 문자열을 출력하는 함수가 있고 그 함수를 두 번 호출한다. 위 코드도 에러가 발생하는데, 이유는 첫 번째 takes_ownership(name)을 호출할 때, name 변수의 소유권이 함수의 매개변수 val로 이동한다. 함수가 실행을 마치고 반환되더라도 val은 스코프를 벗어나기 때문에 메모리가 해제된다. 그래서 이제 name은 더 이상 유효하지 않는다. 두 번째 takes_ownership(name)을 호출하면 name은 첫 번째 호출에서 소유권을 잃었기 때문에 더 이상 사용할 수 없는 상태이다. 이 시점에서 "use of moved value" 에러가 발생한다.
소유권 때문에 문제가 발생한다는 건 이제 알았다. 그럼 어떻게 해결할 수 있을까.
참조(reference)를 사용하거나 값을 복사(clone)하여 사용한다면 문제를 소유권 걱정 없이 값을 사용할 수 있다.
'&'를 사용하면 값을 빌려주기만 하고 소유권은 유지할 수 있다.
.clone() 함수를 사용하여 값의 복사본을 생성하여 전달할 수도 있다.
러스트의 소유권 시스템은 메모리를 안전하게 관리하면서도 GC 없이 높은 성능과 제어권을 제공하기 위한 설계이다.
참고
'Programming Language > 러스트' 카테고리의 다른 글
[Rust] 러스트에게 반한 점, immutable & mutable 변수 (0) | 2024.11.21 |
---|---|
[Rust] 러스트에서 이분탐색 (0) | 2024.11.11 |