Collection type, Primitive type

성장하는개발자·2024년 11월 21일
0

Rust에서는 String&str의 관계처럼, 소유권을 가진 타입소유권 없는 참조 타입의 관계가 다른 타입들에서도 존재합니다. Rust의 소유권 및 참조 시스템은 이러한 패턴을 여러 곳에서 활용하며, 특히 컬렉션 타입데이터 타입에서 유사한 관계를 볼 수 있습니다. 몇 가지 대표적인 예를 들어 설명하겠습니다.


1. Collection Type

1. Vec<T>&[T] (벡터와 슬라이스)

관계

  • Vec<T>: 힙에 저장된 동적 크기의 배열로, 데이터를 소유합니다. 데이터를 추가, 제거, 수정할 수 있습니다.
  • &[T]: 배열 슬라이스로, Vec<T>나 배열의 참조를 나타냅니다. 데이터를 소유하지 않으며, 읽기 전용입니다.

예제

fn print_slice(slice: &[i32]) {
    for item in slice {
        println!("{}", item);
    }
}

fn main() {
    let vec = vec![1, 2, 3, 4]; // Vec<i32>
    print_slice(&vec);          // Vec<T>를 &[T]로 참조 전달

    let array = [5, 6, 7, 8];   // 고정 크기 배열
    print_slice(&array);        // 배열도 &[T]로 참조 전달 가능
}

설명:

  • Vec<T>는 데이터를 소유하며, 동적으로 크기를 조정할 수 있습니다.
  • &[T]는 데이터를 소유하지 않고 읽기 전용으로 참조합니다. 함수에 데이터를 복사하지 않고 전달할 때 유용합니다.

2. Box<T>&T (Box와 참조)

관계

  • Box<T>: 힙에 데이터를 저장하고 소유하는 스마트 포인터입니다.
  • &T: 힙에 저장된 데이터를 참조하는 불변 참조입니다.

예제

fn print_boxed_value(value: &i32) {
    println!("Value: {}", value);
}

fn main() {
    let boxed = Box::new(42); // Box<i32>
    print_boxed_value(&boxed); // Box<T>를 &T로 참조 전달
}

설명:

  • Box<T>는 힙에 데이터를 저장하고 소유합니다.
  • &T는 데이터를 소유하지 않고 읽기 전용으로 참조합니다. Box<T>의 값을 참조로 전달할 때 주로 사용됩니다.

3. Cow<T>&T (Copy-On-Write와 참조)

관계

  • Cow<T>: "Copy-On-Write" 타입으로, 데이터를 소유하거나 참조할 수 있습니다. 읽기 전용으로 사용하다가, 필요하면 소유권을 복사하여 수정할 수 있습니다.
  • &T: 데이터를 소유하지 않고 참조만 합니다.

예제

use std::borrow::Cow;

fn modify_string(input: Cow<str>) {
    if input.contains("Rust") {
        println!("Already contains 'Rust': {}", input);
    } else {
        let mut owned = input.into_owned(); // Cow를 String으로 변환
        owned.push_str(" in Rust!");
        println!("Modified: {}", owned);
    }
}

fn main() {
    let borrowed = "Learning Rust";       // &str
    let owned = String::from("Learning"); // String

    modify_string(Cow::Borrowed(borrowed)); // 참조 전달
    modify_string(Cow::Owned(owned));       // 소유권 전달
}

설명:

  • Cow<T>는 읽기 전용 데이터를 참조(Borrowed)하거나, 필요에 따라 데이터를 복사하여 소유(Owned)할 수 있습니다.
  • Cow는 데이터가 수정될 가능성이 있는 경우에 효율적으로 사용할 수 있습니다.

4. PathBuf&Path (파일 경로와 경로 참조)

관계

  • PathBuf: 힙에 저장된 파일 경로를 소유하는 타입입니다.
  • &Path: 파일 경로의 참조로, 데이터를 소유하지 않습니다.

예제

use std::path::{Path, PathBuf};

fn print_path(path: &Path) {
    println!("Path: {}", path.display());
}

fn main() {
    let owned_path = PathBuf::from("/home/user"); // PathBuf
    print_path(&owned_path);                     // PathBuf를 &Path로 참조 전달

    let borrowed_path = Path::new("/etc");       // &Path
    print_path(borrowed_path);                   // &Path 그대로 전달
}

설명:

  • PathBuf는 동적으로 파일 경로를 소유하고 수정할 수 있습니다.
  • &Path는 파일 경로를 참조하여 읽기 전용으로 사용할 때 적합합니다.

5. HashMap<K, V>&HashMap<K, V> (소유와 참조)

관계

  • HashMap<K, V>: 키-값 쌍을 저장하는 소유 타입입니다.
  • &HashMap<K, V>: HashMap을 참조하여 데이터를 읽기 전용으로 사용할 수 있습니다.

예제

use std::collections::HashMap;

fn print_map(map: &HashMap<String, i32>) {
    for (key, value) in map {
        println!("{}: {}", key, value);
    }
}

fn main() {
    let mut scores = HashMap::new();
    scores.insert(String::from("Alice"), 50);
    scores.insert(String::from("Bob"), 75);

    print_map(&scores); // HashMap의 참조 전달
}

설명:

  • HashMap<K, V>는 데이터를 소유하며, 삽입, 삭제, 수정이 가능합니다.
  • &HashMap<K, V>는 데이터를 읽기 전용으로 참조할 때 사용됩니다.

6. Rc<T>/Arc<T>&T (참조 카운팅과 참조)

관계

  • Rc<T>: 단일 스레드 환경에서 참조 카운팅을 통해 데이터를 공유합니다.
  • Arc<T>: 멀티스레드 환경에서 참조 카운팅을 통해 데이터를 공유합니다.
  • &T: 데이터를 소유하지 않고 참조만 합니다.

예제

use std::rc::Rc;

fn print_shared(data: &i32) {
    println!("Shared data: {}", data);
}

fn main() {
    let shared = Rc::new(42); // Rc<i32>
    print_shared(&shared);    // Rc<T>를 &T로 참조 전달
}

설명:

  • Rc<T>Arc<T>는 데이터를 소유하며, 여러 곳에서 공유할 수 있습니다.
  • &T는 데이터를 읽기 전용으로 참조할 때 사용됩니다.

요약: Rust의 소유 타입과 참조 타입의 관계

Rust에서는 소유권과 참조를 기반으로 한 타입 관계가 여러 곳에서 나타납니다. 아래는 대표적인 사례들입니다:

소유 타입참조 타입설명
String&str문자열 데이터의 소유와 참조
Vec<T>&[T]벡터와 배열 슬라이스
Box<T>&T힙 데이터와 참조
PathBuf&Path파일 경로와 참조
HashMap<K, V>&HashMap<K, V>해시맵 소유와 참조
Rc<T> / Arc<T>&T참조 카운팅을 통한 데이터 공유와 참조
Cow<T>&TCopy-On-Write 타입과 참조

이러한 관계는 Rust의 소유권, 참조, 불변성 개념을 기반으로 설계되어 있으며, 효율적이고 안전한 메모리 관리를 가능하게 합니다.


2. Primitive Type

Rust에서 String&str, Vec<T>&[T] 같은 소유 타입과 참조 타입의 관계는 컬렉션 타입이나 복잡한 데이터 구조에서 주로 나타납니다. 반면, 정수형, 부동소수점형, 불리언 같은 기본 데이터 타입(primitive types)에서는 이러한 관계가 필요하지 않은 이유가 있습니다. 이를 하나씩 살펴보겠습니다.


1. 기본 타입은 크기가 고정되어 있고 값이 간단하다

Rust의 기본 타입(정수형, 부동소수점형, 불리언 등)은 스택에 저장되며, 크기가 고정되어 있고 값이 간단합니다.

예를 들어:

  • i32는 항상 4바이트 크기를 가지며, 복사 비용이 매우 작습니다.
  • bool은 1바이트로 표현됩니다.
  • f64는 8바이트 크기를 가지며, 부동소수점 연산에 사용됩니다.

이러한 기본 타입은 값 자체가 매우 작고 가볍기 때문에, 데이터를 굳이 힙에 저장하거나 소유권과 참조를 구분할 필요가 없습니다. 복사가 간단하고 비용이 거의 들지 않으므로, 소유 타입과 참조 타입의 구분이 필요하지 않은 것입니다.


2. 기본 타입은 Copy 트레이트를 구현한다

Rust에서 정수형, 부동소수점형, 불리언 같은 기본 타입은 모두 Copy 트레이트를 구현합니다. 이는 값이 다른 변수로 복사될 때, 소유권이 이동하지 않고 값이 복사된다는 것을 의미합니다.

예제: 기본 타입의 복사

fn main() {
    let x = 42;  // i32
    let y = x;   // 값이 복사됨 (소유권 이동 아님)

    println!("x: {}, y: {}", x, y); // x와 y 모두 사용 가능
}

설명:

  • x의 값이 y로 복사되었지만, x는 여전히 유효합니다.
  • 이는 정수형 같은 기본 타입이 Copy 트레이트를 구현하기 때문입니다.
  • 복사가 간단하고 비용이 적으므로, 참조 타입이 따로 필요하지 않습니다.

3. 참조는 기본 타입에서도 사용할 수 있다

Rust에서 기본 타입은 참조를 사용할 수 있습니다. 하지만 이는 소유 타입과 참조 타입의 구분이 아니라, 단순히 값을 참조로 전달하기 위한 것입니다.

예제: 기본 타입의 참조

fn print_value(value: &i32) {
    println!("Value: {}", value);
}

fn main() {
    let x = 42;
    print_value(&x); // x를 참조로 전달
    println!("x: {}", x); // x는 여전히 유효
}

설명:

  • 기본 타입에서도 참조(&T)를 사용할 수 있지만, 이는 값의 소유권을 이동하지 않고 읽기 전용으로 전달하기 위해 사용됩니다.
  • 기본 타입은 크기가 작기 때문에 참조를 사용하지 않고 값 자체를 복사해서 전달하는 것도 큰 문제가 되지 않습니다.

4. 컬렉션과 기본 타입의 차이

String, Vec<T> 같은 컬렉션 타입은 크기가 동적으로 변할 수 있고, 데이터가 힙에 저장됩니다. 따라서 소유권과 참조를 명확히 구분해야 효율적인 메모리 관리가 가능합니다.

예를 들어:

  • String은 힙에 저장된 문자열 데이터를 소유하며, 데이터를 수정하거나 크기를 변경할 수 있습니다.
  • &str은 힙에 저장된 데이터를 참조만 하므로, 소유권을 이동하지 않고 읽기 전용으로 사용할 수 있습니다.

반면, 정수형(i32), 부동소수점형(f64), 불리언(bool) 같은 기본 타입은:

  • 크기가 고정되어 있으며, 값 자체가 스택에 저장됩니다.
  • 값의 복사가 간단하고 비용이 적으므로, 소유 타입과 참조 타입의 구분이 필요하지 않습니다.

5. 기본 타입에서 소유권과 참조가 필요하지 않은 이유 요약

  1. 값이 작다: 정수형, 부동소수점형, 불리언은 크기가 고정되어 있고, 값이 작아서 복사 비용이 적다.
  2. 스택에 저장: 기본 타입은 스택에 저장되므로, 힙 메모리 관리가 필요하지 않다.
  3. Copy 트레이트: 기본 타입은 Copy 트레이트를 구현하여 값 복사가 소유권 이동 없이 가능하다.
  4. 참조는 선택 사항: 기본 타입에서도 참조를 사용할 수 있지만, 값 복사가 간단하기 때문에 참조가 필수적이지 않다.

6. 만약 기본 타입에 소유권과 참조를 구분해야 한다면?

기본 타입에서도 소유권과 참조를 명확히 구분하고 싶다면, 힙에 저장되는 타입으로 감싸는 방법을 사용할 수 있습니다. 예를 들어:

예제: Box<T>로 기본 타입 감싸기

fn main() {
    let x = Box::new(42); // i32를 힙에 저장
    let y = x;            // 소유권 이동

    // println!("{}", x); // 컴파일 에러: x의 소유권이 y로 이동
    println!("{}", y);     // y는 유효
}

설명:

  • Box<T>를 사용하면 기본 타입도 힙에 저장할 수 있으며, 소유권 이동이 발생합니다.
  • 이는 기본 타입을 소유권 기반으로 관리해야 할 특별한 경우에 사용할 수 있습니다.

7. 결론

Rust에서 정수형, 부동소수점형, 불리언 같은 기본 타입에는 별도의 소유 타입과 참조 타입이 없는 이유는 다음과 같습니다:

  • 기본 타입은 크기가 작고 복사가 빠르며, 값을 복사하는 것이 효율적입니다.
  • 기본 타입은 스택에 저장되므로 힙 메모리 관리가 필요하지 않습니다.
  • Copy 트레이트를 구현하여 소유권 이동 없이 값 복사가 가능합니다.
  • 참조(&T)는 필요하면 사용할 수 있지만, 기본적으로 값 자체를 복사해서 사용하는 것이 더 간단하고 효율적입니다.

따라서, 기본 타입에서는 소유 타입과 참조 타입의 구분이 필요하지 않은 것입니다. Rust는 이러한 설계를 통해 성능과 안전성을 모두 만족시킵니다.

profile
rust programming language

0개의 댓글