[0]
포인터는 프로그래밍에서 필수적인 요소이지만, 각종 메모리 취약점을 발생시키는 주범이다. Rust에선 레퍼런스라는 형태로 포인터를 사용할 수 있다. 레퍼런스는 Ownership-Borrow 체계를 통해서 메모리 취약점이 발생하지 않음을 보장한다. 반면 C/C++와 같은 방식의 일반적인 포인터는 주어지지 않는데, 대신에 스마트 포인터가 존재한다.
[1]
스마트 포인터는 Rust에서 처음 나온 개념은 아니다. 메모리 안정성을 위해 다양한 언어에서 빌트인으로, 혹은 패키지 등의 행태로 스마트 포인터가 제공된다. 스마트 포인터가 일반 포인터와 다른 점은 가리키는 대상에 대한 metadata를 기록한다는 점이다. 예컨대 reference counter를 기록하여 언제 해당 메모리 영역이 free 되어야 하는지 추적할 수 있다. Rust의 스마트 포인터 또한 이러한 종류의 metadata를 지니고 있으며, 일반적으로 구조체의 형태로 구현된다. Rust의 스마트 포인터가 가장 독특한 측면은 구조체에 Deref와 Drop trait을 구현하는 방식으로 스마트 포인터가 제공된다는 점이다. 이 포스팅에서는 Deref와 Drop trait에 대해 다룬다. 하지만 우선 가장 기본적인 형태의 스마트 포인터를 잠깐 살펴보자.
[2]
Rust의 가장 단순한 스마트 포인터는 Box<T>이다. 다음과 같은 식으로 선언할 수 있다.
let tmp = Box::new(1);
prinln!("{}", tmp); //print 1
tmp는 컴파일러에 의해 Box<i32> 타입으로 결정된다. 또한 println! 매크로에서 Box<T>의 T를 출력해주는 것을 확인할 수 있다.
Box<T>는 기본적으로 heap에 할당된 메모리를 가리키며, T에 대한 ownership을 소유한다. 즉 오브젝트를 레퍼런스가 아닌 ownership을 전달하고 싶지만, 오브젝트 사이즈가 너무 클 때 Box<T>를 전달할 수 있다. C에서 파라미터로 포인터를 넘겨주는 방식을 생각하면 된다.
[3]
많은 언어에서 쓰는 역참조 기호 *는 Rust에서는 기본적으로 레퍼런스에만 사용 가능하다. 하지만 오버라이딩 할 수 있는 방법을 제공하는데, Deref라는 trait으로 제공한다. Deref는 다음과 같이 구현할 수 있다.
use std::ops::Deref;
struct MyStruct<T> {
val: T,
}
impl<T> Deref for MyStruct<T> {
type Target = T;
fn deref(&self) -> &T {
&self.val
}
}
이제 아래와 같이 * 연산자를 통해 MyStruct를 역참조할 수 있다.
let obj = MyStruct{val:10};
println!("{}", *obj);
여기에서 *obj는 실제로는 *(obj.deref())로 변환된다.
[4]
Deref와 더불어서 스마트 포인트의 핵심 trait으로 Drop이 있다. Drop은 다음과 같이 구현할 수 있다.
impl<T> Drop for MyStruct<T>{
fn drop(&mut self){
//any cleanup code here!
}
}
drop()은 오브젝트가 scope 밖으로 나가서 소유권이 소멸될때 자동으로 실행된다. drop에 메모리 cleanup code를 넣어두는 식으로 보통 구현을 하게 된다. 이를 통해 사용자가 까먹거나, 혹은 2번 이상 호출해서 double-free가 발생하는 경우 등을 컴파일 타임에 찾아낼 수 있다.
[참고]
https://doc.rust-lang.org/book/ch15-00-smart-pointers.html
https://stackoverflow.com/questions/55075458/understand-smart-pointers-in-rust
'Computer Science > Rust' 카테고리의 다른 글
Rust의 async/await와 Future (0) | 2020.07.21 |
---|---|
Rust의 Copy trait와 Clone trait (2) | 2020.06.30 |
Rust의 lifetime parameter (0) | 2020.05.05 |
Rust의 Trait (1) | 2020.03.10 |
Rust에서 null을 도입하지 않은 이유 (0) | 2020.01.17 |