상세 컨텐츠

본문 제목

14. Rust 소유권

Rust를 처음부터 배우세요

by 러스트코리아 2024. 11. 15. 19:39

본문

반응형

프로그래밍 언어는 메모리를 두 가지 범주로 나눕니다.

  • 스택 (stack)
  • 힙(heap)

물론 이 두 가지 분류는 실제 메모리에 아무런 영향을 미치지 않습니다. 단지 시스템이 애플리케이션에 할당한 메모리를 위의 두 가지 범주로 표시할 뿐입니다.

14.1 스택

스택은 후입선출(LIFO) 방식의 컨테이너입니다. 우리의 저장용기와 마찬가지로 나중에 넣은 것은 먼저 꺼내야 합니다(나중에 넣은 것은 맨 위에 놓입니다).

스택에 저장된 요소의 크기를 알아야 합니다. 즉, 변수나 데이터를 스택에 배치하려면 컴파일 중에 해당 크기를 지워야 합니다.

예를 들어, 데이터 유형 i32 변수의 경우 해당 크기는 예측 가능하며 4바이트만 차지합니다.

Rust의 모든 스칼라 유형은 고정된 크기이기 때문에 스택에 저장할 수 있습니다.

문자열과 같은 복합형의 경우 런타임에 값이 할당되므로 컴파일 타임의 크기를 알 수 없습니다. 그러면 스택에 저장할 수 없고  에만 저장됩니다 .

14.2 힙

힙은 컴파일 타임에 크기를 알 수 없는 데이터, 즉 런타임에만 크기를 결정할 수 있는 데이터를 저장하는 데 사용됩니다.

우리는 일반적으로 동적으로 유형이 지정된 데이터를 힙에 저장합니다. 간단히 말해서, 우리는 일반적으로 프로그램 수명 동안 변경될 수 있는 데이터를 힙에 저장합니다.

힙은 시스템이 관리하는 것이 아니라 사용자가 관리하는 것이므로 잘못 사용하면 메모리 오버플로가 발생할 가능성이 크게 높아진다.

14.3 소유권이란 무엇입니까?

소유권이란 어떤 것이 자신의 소유인지, 그것을 주거나 버리는 등 마음대로 처리할 수 있는 권리가 있는지를 의미합니다.

Rust 언어의 각 값에는 해당 변수가 있으며 이 변수는 값의 소유자가 됩니다. 어떤 면에서 변수를 정의하는 것은 변수와 변수에 저장되는 데이터에 대한 소유권 관리를 정의하고 해당 값이 변수의 소유임을 선언하는 것입니다.

예를 들어, let age = 30 문은 값 30이 age 변수의 소유임을 선언하는 것과 동일합니다.

이 비유는 부적절합니다. 변수는 숫자 30을 소유하지 않고 특정 메모리 블록의 소유자를 소유하고 있으며 이 메모리 블록에는 숫자 30이 저장되어 있습니다.

모든 것의 소유자는 단 한 명이며 Rust에서는 공동 소유자라는 개념이 허용되지 않습니다.

Rust에서 데이터 조각은 주어진 시간에 단 한 명의 소유자만 가질 수 있습니다.

Rust에서는 두 변수가 동시에 동일한 메모리 영역을 가리키는 것이 허용되지 않습니다. 변수는 서로 다른 메모리 영역을 가리켜야 합니다.

14.3.1 소유권 이전

소유권이란 무엇인가가 자신의 소유인지 여부를 의미하므로, 그것을 주거나 버리는 등 마음대로 처리할 권리가 있으므로

그런 다음 소유권 이전이 때때로 발생합니다.

Rust 언어에서 소유권을 이전하는 방법에는 여러 가지가 있습니다:

하나의 변수를 다른 변수에 할당합니다.

중요한 변수를 함수에 매개변수로 전달합니다.

변수는 함수의 반환 값으로 반환됩니다.

다음으로 이 세 가지 방법을 자세히 소개하겠습니다.

14.3.2 한 변수를 다른 변수에 할당

Rust가 스스로 선언한 가장 큰 판매 포인트는 메모리 안전성입니다. 이는 Rust가 시스템 수준 언어로서 C++를 대체할 수 있다고 믿는 이유 중 하나입니다.

메모리 안전성을 달성하기 위해 Rust는 메모리를 사용할 수 있는 사람과 메모리 사용을 제한해야 하는 시기를 엄격하게 제어합니다.

말로 하기엔 좀 애매한 부분이 있으니, 코드를 직접 보고 코드를 통해 설명해 보겠습니다.

  1. fn main(){
  2.  
  3. // 벡터 v는 힙에 있는 데이터의 소유권을 가집니다.
  4. // 한 번에 하나의 변수만 힙에 있는 데이터의 소유권을 가질 수 있습니다.
  5. let v = vec![1,2,3];
  6.  
  7.  
  8. // 할당으로 인해 두 변수 모두 동일한 데이터의 소유권을 갖게 됩니다.
  9. // 두 변수 모두 동일한 메모리 블록을 가리키기 때문에
  10. let v2 = v;
  11.  
  12. // Rust는 두 변수 모두 힙에 있는 메모리 블록의 소유권을 가지고 있는지 확인합니다.
  13. // 소유권 경쟁이 발생하면 자동으로 새 변수에 소유권이 부여됩니다.
  14. // v가 더 이상 데이터 소유권을 갖고 있지 않기 때문에 오류가 발생했습니다.
  15. println!("{:?}",v);
  16. }

위 코드에서는 먼저 벡터 v를 선언합니다. 소유권 개념은 하나의 변수만 리소스에 바인딩되거나, v가 리소스에 바인딩되거나, v2가 리소스에 바인딩된다는 것입니다.

위 코드는 이동된 값을 사용하여 컴파일 오류를 발생시킵니다. v. 이는 할당 작업이 리소스 소유권을 v2로 이전하기 때문입니다. 이는 소유권이 v에서 v2(v2 = v)로 이동하고 이동 후 v가 유효하지 않음을 의미합니다.

14.3.3 변수를 함수에 매개변수로 전달하기

힙의 객체가 클로저나 함수에 전달되면 값의 소유권도 변경됩니다.

  1. fn main(){
  2. let v = vec![1,2,3]; // 벡터 v는 힙에 있는 데이터의 소유권을 갖습니다.
  3. let v2 = v; // 벡터 v는 소유권을 v2로 이전합니다.
  4. display(v2); // v2는 소유권을 함수 매개변수 v로 이전하며, v2는 사용할 수 없게 됩니다.
  5. println!("In main {:?}",v2); //v2를 사용할 수 없게 됩니다
  6. }
  7. fn display(v:Vec<i32>){
  8. println!("inside display {:?}",v);
  9. }

위의 Rust 코드를 컴파일하고 실행하면 오류는 다음과 같습니다.

  1. error[E0382]: borrow of moved value: `v2`
  2. --> src/main.rs:5:28
  3. |
  4. 3 | let v2 = v; // 벡터 v는 소유권을 v2로 이전합니다.
  5. | -- move occurs because `v2` has type `std::vec::Vec<i32>`, which does not implement the `Copy` trait
  6. 4 | display(v2);
  7. | -- value moved here
  8. 5 | println!("In main {:?}",v2);
  9. | ^^ value borrowed here after move

복구의 핵심은 최종 출력 v2를 주석 처리하는 것입니다.

  1. fn main(){
  2. let v = vec![1,2,3]; // 벡터 v는 힙에 있는 데이터의 소유권을 갖습니다
  3. let v2 = v; // 벡터 v는 소유권을 v2로 이전합니다.
  4. display(v2); // v2는 소유권을 함수 매개변수 v로 이전하며, v2는 사용할 수 없게 됩니다
  5. //println!("In main {:?}",v2); // v2를 사용할 수 없게 됩니다
  6. }
  7.  
  8. fn display(v:Vec<i32>){
  9. println!("inside display {:?}",v);
  10. }

위의 Rust 코드를 컴파일하고 실행하면 오류는 다음과 같습니다.

  1. inside display [1, 2, 3]

14.3.4 함수에서 변수를 반환 값으로 반환

함수에 전달된 소유권은 함수 실행이 완료되면 만료됩니다.

즉, 함수의 형식 매개변수로 얻은 소유권은 함수를 떠난 후에는 무효화됩니다. 만료되면 해당 데이터에 더 이상 액세스할 수 없습니다.

유효하지 않은 소유권 문제를 해결하기 위해 함수가 소유한 객체를 호출자에게 반환하도록 할 수 있습니다.

  1. fn main(){
  2. let v = vec![1,2,3]; // 벡터 v는 힙에 있는 데이터의 소유권을 갖습니다.
  3. let v2 = v; // 벡터 v는 소유권을 v2로 이전합니
  4. let v2_return = display(v2);
  5. println!("In main {:?}",v2_return);
  6. }
  7.  
  8. fn display(v:Vec<i32>)-> Vec<i32> {
  9. // 동일한 벡터를 반환합니다.
  10. println!("inside display {:?}",v);
  11. return v;
  12. }

위의 Rust 코드를 컴파일하고 실행하면 출력 결과는 다음과 같습니다.

  1. inside display [1, 2, 3]
  2. In main [1, 2, 3]

14.4 소유권과 기본(원시) 데이터 유형

모든 기본 데이터 유형의 경우 한 변수를 다른 변수에 할당하면 소유권이 이전되지 않지만 데이터가 다른 개체에 복사됩니다. 간단히 말하면, 메모리의 한 영역을 다시 열고 복사된 데이터를 저장한 다음 새 변수가 해당 영역을 가리키는 것을 의미합니다.

그 이유는 원시 데이터 유형이 그렇게 많은 메모리를 차지할 필요가 없기 때문입니다.

  1. fn main(){
  2. let u1 = 10;
  3. let u2 = u1; //u1은 데이터를 u2에 복사합니다.
  4. println!("u1 = {}",u1);
  5. }

위의 Rust 코드를 컴파일하고 실행하면 출력 결과는 다음과 같습니다.

  1. u1 = 10

참고: 소유권은 힙에 할당된 데이터에 대해서만 발생합니다. C++와 비교하면 소유권은 포인터에 대해서만 발생한다고 말할 수 있습니다. 기본 유형은 스택에 저장되므로 소유권 개념이 없습니다.

현재 콘텐츠 저작권은 chapin666 또는 그 계열사에 있습니다.
반응형

'Rust를 처음부터 배우세요' 카테고리의 다른 글

16. Rust 슬라이스  (0) 2024.11.15
15. Rust는 차용(빌려줌) - Borrowing  (0) 2024.11.15
13. Rust 배열  (0) 2024.11.15
12. Rust 듀플(tuple)  (0) 2024.11.15
11. Rust 함수 fn  (0) 2024.11.15

관련글 더보기