Programming

Rust기초 알기 - 4.3 소유권 이전과 복사

IT오이시이 2023. 6. 29. 05:03
728x90

 

Rust기초알기 (4) - 소유권 시스템

4.1 소유자와 대여자 개념
4.2 소유권 규칙
4.3 소유권 이전과 복사
4.4 소유권과 메모리 관리

[목차] Rust Programming - Rust 기초 알기 목차.Zip

#Rust프로그래밍  #Rust언어기초 #Rust기초문법 #Rust기초알기

 
 

 

Rust기초 알기 - 4.3 소유권 이전과 복사

 
 
Rust의 소유권 규칙에는 데이터의 소유권을 전달하는 방식으로 소유권 이전 (Ownership Transfer), 대여(Borrow), 복사Copy), 참조 (Reference) 에 대한 중요한 개념이 있습니다.

  • 소유권 이전 (Ownership Transfer, Move)
  • 대여(Borrow)
  • 복사Copy)
  • 참조 (Reference) 

 

1. 소유권 이전(Onwership Transfer, Move):

이전은 데이터의 소유권을 한 변수에서 다른 변수로 이동시키는 것을 말합니다.
이전이 발생하면 데이터를 소유하던 변수는 더 이상 해당 데이터를 소유하지 않게 됩니다.
Rust에서는 기본적으로 이전이 발생하면, 이는 변수 간의 대입(assignment)이 이루어질 때 발생합니다.
이전된 데이터를 소유하던 변수는 사용이 불가능해지므로 안전한 메모리 관리를 보장합니다.
Rust에서는 이전하기 위해서는 값을 전달하는 대신 변수 자체를 이전해야 합니다. 이를 "move"라고도 부릅니다. Move는 한 변수가 다른 변수로 소유권을 이전 할 때, 이전 변수는 해당 값에 대한 소유권을 잃게 됩니다.

fn main() {
    // String 타입의 데이터를 생성하여 original_owner가 소유자가 됩니다.
    let original_owner = String::from("Hello");

    // 소유권을 이전하여 transferred_owner가 새로운 소유자가 됩니다.
    let transferred_owner = original_owner;

    // original_owner는 이전된 후이므로 더 이상 해당 데이터의 소유권을 가지지 않습니다.
    // 따라서 아래와 같은 코드는 컴파일 오류를 발생시킵니다.
    // println!("Original Owner: {}", original_owner);

    // transferred_owner는 이전된 후이므로 새로운 소유자로서 해당 데이터에 대한 소유권을 가지고 있습니다.
    println!("Transferred Owner: {}", transferred_owner);
}

 

  • original_owner이라는 String 변수에 "Hello"라는 값을 할당합니다.
  • 다음 transferred_owner 변수에 original_owner을 할당합니다.
  • 이 할당은 소유권을 이전하므로 original_owner은 더 이상 유효하지 않아 컴파일 오류가 발생합니다.

 

2. Borrow (대여):


대여(Borrow)는 데이터에 대한 임시 대여를 의미합니다. Borrow를 사용하면 소유권을 이전하지 않고도 데이터에 대한 참조를 생성할 수 있습니다.

fn main() {
    let mut value = 10;
    borrow_example(&mut value);
    println!("Value after borrow: {}", value);
}

fn borrow_example(val: &mut i32) {
    *val += 5;
}

 

  •  value 변수를 정의하고 10으로 초기화합니다.
  •  borrow_example 함수를 호출하면서 value의 가변 참조(&mut value)를 전달합니다.
  •  borrow_example 함수에서는 전달된 값을 5만큼 증가시킵니다.
  • 이때 &mut을 사용하여 가변 참조를 생성하고, *val을 사용하여 참조된 값에 접근합니다.
  • 함수 호출이 끝나면 borrow_example 함수에서의 변경 사항이 value에 반영됩니다.

 

3. 참조 (Reference):


Rust에서 참조는 기존 값에 대한 불변 또는 가변 참조를 생성하는 데 사용됩니다. 참조는 데이터에 대한 포인터와 유효 범위를 저장하는데, 실제 데이터를 소유하지 않습니다. 따라서 참조는 해당 데이터의 소유권을 가져오지 않고 해당 데이터에 대한 참조를 전달할 수 있게 해줍니다.
참조는 대여의 한 형태로서 대여와 동일한 개념으로 불변 참조(&T)와 가변 참조(&mut T)로 구분됩니다. 불변 참조는 값을 읽기만 할 수 있고 변경할 수 없으며, 가변 참조는 값을 읽고 변경할 수 있습니다. 

fn main() {
    let value = 5;
    let reference = &value;
    println!("Reference: {}", reference);
}

 

  •  value라는 변수에 정수 5를 할당합니다.
  •  reference라는 변수에 value의 참조를 할당합니다.
  • reference는 value의 값을 직접 소유하지 않고,  value의 위치를 가리키는 포인터일 뿐입니다.
  • 따라서 reference를 통해 value의 값을 읽을 수 있습니다.

 
 

4. 복사(Copy):

복사는 데이터의 소유권을 전달하지 않고, 데이터의 복사본을 새로운 변수에 할당하는 것을 말합니다.
Copy 특성을 가진 값은 소유권을 이전하지 않고 여러 개의 변수에 할당 하여  각 변수는 독립적인 소유권을 가집니다.

- 이동(Move)은 변수가 소유권을 다른 변수로 이전하는 것을 의미하며, 원래의 변수는 해당 값에 대한 접근 권한을 잃습니다.

- 복사(Copy)는 변수가 값을 복사하여 새로운 변수에 할당하는 것을 의미하며, 원래의 변수와 새 변수는 서로 독립적인 소유권을 가집니다.

복사는 복사 가능한 데이터 타입(Copy trait, Clone trait 타입)에 대해서만 적용됩니다.  복사 가능한 타입은 데이터를 스택에 저장되므로 복사가 비용이 거의 없습니다.
복사 가능한 타입은 대표적으로 정수형, 부동소수점형, 불리언, 문자 등이 있습니다.
 

  1. 사이즈가 고정적이고 예측 가능한 타입 복사:
    스택에 저장할때 필요한 공간의 크기를 고정하여 할당합니다. 복사 할 때 메모리 크기가 고정되어 효율적으로 복사됩니다.
  2. 얕은 복사(Shallow Copy): 
    데이터의 복사를 수행할 때, 복사된 값은 원본 데이터의 참조 또는 포인터를 공유하는 것을 의미합니다. 복사된 값은 원본 값과 동일한 데이터를 가리키기 때문에 변경이 발생하면 원본 값에도 영향을 줍니다. 즉, 데이터의 실제 복사가 아닌 참조만 복사되는 것입니다. 

    얕은 복사는 주로 복합 데이터 타입(구조체, 배열, 벡터 등)의 복사 작업에 적용됩니다. 이를 통해 데이터 구조가 크거나 복잡할 때 데이터의 전체 복사를 피하고 복사 작업의 비용을 줄일 수 있습니다.

 

1) 사이즈가 고정적이고 예측 가능한 타입 복사

fn main() {

    // 복사 가능한 타입인 정수형을 생성하여 original_value 가 소유자가 됩니다.
    let original_vlaue = 40;

    // original_vlaue의 값을 복사하여 copied_value 가 새로운 소유자가 됩니다.
    let copied_vlaue = original_vlaue;  // 불변
    
    // copied_value = 50;  // 불변값을 수정 불가하여 컴파일 오류가 발생 합니다.

   // 복사가 수행되었으므로 원본 변수와 복사본은 각각 독립적인 소유권을 가집니다.
    println!("Original Value : {}", original_value); // 40 출력
    println!("Copied Value : {}", copied_value);    // 40 출력
}

 
복사한 값을 수정하려면 let mut 를 이용하여 복사 합니다.

fn main() {
    let original_value = 40;
    let mut copied_value = original_value;  // 가변 변수로 선언

    copied_value = 50;  // copied_value의 값을 50으로 변경

    println!("Original Value: {}", original_value); // 40 출력
    println!("Copied Value: {}", copied_value);    // 50 출력
}

 
 

2) 얕은 복사(Shallow Copy): 

Rust에서는 기본적으로 구조체나 벡터 등의 복합 데이터 타입은 얕은 복사로 동작합니다
 

#[derive(Copy, Clone)]  // Copy 트레이트를 구현
struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let point1 = Point { x: 1, y: 2 };
    let point2 = point1;  // 얕은 복사 발생

    println!("Point1: x={}, y={}", point1.x, point1.y);  // Point1: x=1, y=2 출력
    println!("Point2: x={}, y={}", point2.x, point2.y);  // Point2: x=1, y=2 출력
}

 
 
 

[참고 1] 데이터 이전과 복사 예시

 

fn main() {
    // 이전(Transfer)
    let owner1 = String::from("Hello");  // String 데이터를 생성하여 소유합니다.
    let owner2 = owner1;  // owner1의 데이터 소유권이 owner2로 이전됩니다.
    // 이후 owner1은 더 이상 소유자가 아니므로 사용할 수 없습니다.

    // 복사(Copy)
    let value1 = 5;  // 정수형 데이터는 복사 가능한 타입이므로 복사됩니다.
    let value2 = value1;  // value1의 값이 value2로 복사됩니다.
    // value1과 value2는 각각 독립적인 변수이므로 각자의 값을 가지고 있습니다.
}

 
owner1의 데이터 소유권은 owner2로 이전됩니다. 따라서 owner1은 이후 사용할 수 없게 됩니다. 이는 데이터의 이전(Transfer)이 발생하였기 때문입니다.
반면에 value1의 값은 복사 가능한 타입이므로 value2에 복사됩니다. value1과 value2는 각각 독립적인 변수이며, 서로 다른 값을 가지고 있습니다. 이는 복사(Copy)가 발생하여 데이터의 복사본이 새로운 변수에 할당되었기 때문입니다.
 

[참고 2] Clone 특성(Clone trait)

아래 예시에서  Person 구조체는 Clone 특성 으로 derive 어노테이션을 통해 자동으로 구현하였습니다. 따라서 person1.clone()을 호출하여 person1을 복사하여 person2에 할당할 수 있습니다. 이렇게 하면 각 변수는 원본 데이터의 독립적인 복사본을 소유합니다.

#[derive(Clone)]  // Clone 트레이트를 자동으로 구현합니다.
struct Person {
    name: String,
    age: u32,
}

fn main() {
    let person1 = Person {
        name: String::from("Alice"),
        age: 30,
    };

    // Clone 트레이트를 구현한 타입은 clone() 메서드를 호출하여 복사할 수 있습니다.
    let person2 = person1.clone();

    println!("Person 1: {:?}", person1);
    println!("Person 2: {:?}", person2);
}

///[Result] ///////////////////////////////
// Person 1: Person { name: "Alice", age: 30 }
// Person 2: Person { name: "Alice", age: 30 }

 

  • #[derive(Clone)] 어노테이션을 추가함으로써 Person 구조체에 대한 Clone 트레이트 구현이 자동으로 생성되었습니다. 
  • 이를 통해 person2에  person1.clone() 메서드를 호출하여 Person 객체를 복사 합니다.

 

[참조3] Clone 트레이트를 수동으로 구현

Person 구조체에 대해 impl Clone for Person 블록을 사용하여 Clone 트레이트를 수동으로 구현합니다.

// 수동으로 Clone() trait 를 선언하는 방법
#[derive(Debug)]
struct Person {
    name: String,
    age: u32,
}

impl Clone for Person {
    fn clone(&self) -> Person {
        Person {
            name: self.name.clone(),
            age: self.age,
        }
    }
}

fn main() {
    let person1 = Person {
        name: String::from("Alice"),
        age: 30,
    };

    let person2 = person1.clone();

    println!("Person 1: {:?}", person1);
    println!("Person 2: {:?}", person2);
}
728x90
반응형