[Rust] unsafe: 수동 메모리 관리 - 동적배열 구현해보기

그냥 라이브러리를 갖다쓰기만 할거라면 메모리를 직접 관리할 필요는 없지만, 경우에 따라서는 필요할 때도 있다.

이를테면, 내부적으로 배열을 갖고 있는 자료구조 같은 구현이 그렇다.

이번 포스트에서는 동적 배열을 간단하게 구현해보는 시간을 가져보겠다.
Vec과는 다르게 생성 시점에서 길이가 고정되는 런타임 할당 배열이다.

우선, 당연히 다음과 같은 형태가 필요하다.
배열에 대한 포인터와, 길이 정보를 구조체에 정의한다.

전체 코드는 맨 아래에 있다.

그리고 new 함수로 할당 로직을 구현한다.
총 길이를 기반으로 해서 alloc 함수로 메모리를 할당하고, 그 포인터를 data 필드에 집어넣는다.

C의 malloc과 거의 같다고 보면 된다.
Layout::array 함수로 요소 타입의 크기와 요소의 개수를 넘겨주니, (타입 크기 * 개수)바이트로 할당을 해준다.

그리고 할당을 수동으로 했으니 할당 해제도 직접 해줘야 한다.
이건 Drop을 통해서 구현하면 된다. stack에서 소멸 시점을 만났을때 자동으로 호출이 된다.
C++의 소멸자와 동일하다고 보면 된다.

똑같이 레이아웃 정보를 주고 dealloc으로 날린다.

인덱싱도 구현해보자.
배열이면 일단 인덱스로 조회를 하거나 수정을 할 수 있어야 하지 않겠는가?
raw pointer의 offset 함수로 인덱싱은 어렵지 않게 구현할 수 있다.

그럼 이런식으로 사용이 가능해진다.

잘 굴러간다.

아래는 전체 코드다.

use std::ops::{Index, IndexMut};

pub struct DynamicArray<T> {
    pub data: *mut T,
    length: usize,
}

impl<T> DynamicArray<T> {
    pub fn new(size: usize) -> Self {
        unsafe {
            DynamicArray {
                data: std::alloc::alloc(std::alloc::Layout::array::<T>(size).unwrap()) as *mut T,
                length: size,
            }
        }
    }
}

impl<T> Index<usize> for DynamicArray<T> {
    type Output = T;

    fn index(&self, index: usize) -> &Self::Output {
        unsafe { &*self.data.offset(index as isize) }
    }
}

impl<T> IndexMut<usize> for DynamicArray<T> {
    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
        unsafe { &mut *self.data.offset(index as isize) }
    }
}

impl<T> Drop for DynamicArray<T> {
    fn drop(&mut self) {
        unsafe {
            std::alloc::dealloc(
                self.data as *mut u8,
                std::alloc::Layout::array::<T>(self.length).unwrap(),
            )
        };
        println!("dropped");
    }
}

fn main() {
    let mut arr = DynamicArray::<i32>::new(10);

    arr[0] = 10;
    arr[1] = 20;

    println!("{}, {} ", arr[0], arr[1]);
}