소유권범위 기반 리소스 관리Dropping은 계층적이다소유권 이전소유권 리턴하기참조로 소유권 대여하기참조로 변경 가능한 소유권 대여하기역참조대여한 데이터 전달하기참조의 참조명시적인 생명주기여러 개의 생명주기정적인 생명주기데이터 자료형의 생명주기5장 - 마무리The Rust Programming Language 링크의문점
Rust만의 메모리 관리 패러다임인 소유권(Ownership)에 대한 내용.
소유권
자료형의 인스턴스화 (타언어의 객체 생성)해서 변수에 할당(binding)하는 행위
→ Rust 컴파일러가 lifetime 동안 검증할 메모리 리소스를 생성하는 것
- 할당된 변수 = 리소스 소유자(owner)
struct Foo { x: i32, } fn main() { // struct를 인스턴스화 하고 변수에 bind하여 // 메모리 리소스를 생성함 let foo = Foo { x: 42 }; // foo가 owner임 }
범위 기반 리소스 관리
Rust는 범위(scope)가 끝나는 곳에서 리소스를 소멸하고 해제
- drop: 소멸과 할당 해제를 의미하는 용어
struct Foo { x: i32, } fn main() { let foo_a = Foo { x: 42 }; let foo_b = Foo { x: 13 }; println!("{}", foo_a.x); println!("{}", foo_b.x); // foo_b가 여기서 drop 됩니다 // foo_a가 여기서 drop 됩니다 }
메모리 상세
- Rust에는 가비지 컬렉션이 없음
- C++에서는 Resource Acquisition Is Initialization ( RAII )라고 부름
Dropping은 계층적이다
- Ex> 구조체(struct)의 drop 순서
- struct 먼저 drop
- struct의 각 필드들 drop
struct Bar { x: i32, } struct Foo { bar: Bar, } fn main() { let foo = Foo { bar: Bar { x: 42 } }; println!("{}", foo.bar.x); // foo가 먼저 drop 되고 // 그 다음에 foo.bar가 drop 됩니다 }
메모리 상세
- Rust는 메모리 자동 해제 → 메모리 누수(Memory Leak)이 덜 발생
- 메모리 리소스는 단 한 번만 drop
소유권 이전
소유자(owner)가 함수의 인자로 전달되면, 소유권(ownership)은 그 함수의 매개변수로 이동(move)
→ move 이후에는 원래 함수에 있던 변수(=owner)는 더 이상 사용 불가
struct Foo { x: i32, } fn do_something(f: Foo) { println!("{}", f.x); // f가 여기서 drop 됩니다 } fn main() { let foo = Foo { x: 42 }; do_something(foo); // foo가 do_something으로 move 됩니다 // foo는 더 이상 사용할 수 없습니다 }
메모리 상세
move 중에는 owner 값의 stack 메모리가 함수 호출의 매개변수 stack 메모리로 복사
소유권 리턴하기
소유권(ownership)은 함수에서도 리턴됨
struct Foo { x: i32, } fn do_something() -> Foo { Foo { x: 42 } // ownership이 밖으로 move 됩니다 } fn main() { let foo = do_something(); // foo가 owner가 되었습니다 // 함수의 scope 끝에 도달했기 때문에 foo는 drop 됩니다 }
참조로 소유권 대여하기
&
연산자를 통해 참조로 리소스에 대한 접근권한을 대여
- 참조도 다른 리소스와 마찬가지로 drop
struct Foo { x: i32, } fn main() { let foo = Foo { x: 42 }; let f = &foo; println!("{}", f.x); // f는 여기서 drop 됩니다 // foo는 여기서 drop 됩니다 }
참조로 변경 가능한 소유권 대여하기
&mut
연산자로 mutable 리소스 접근 권한도 대여 가능
- 리소스의 owner는 mutable하게 대여된 상태에서는 move 되거나 변경될 수 없음
struct Foo { x: i32, } fn do_something(f: Foo) { println!("{}", f.x); // f는 여기서 drop 됩니다 } fn main() { let mut foo = Foo { x: 42 }; let f = &mut foo; // FAILURE: do_something(foo) 은 실패할 것입니다 // 왜냐하면 foo는 mutable하게 대여된 상태에서는 move될 수 없기 때문입니다 // FAILURE: foo.x = 13; 는 여기서 실패할 것입니다 // 왜냐하면 foo는 mutable하게 대여된 상태에서는 변경할 수 없기 때문입니다 f.x = 13; // f는 이 시점 이후 더 이상 사용되지 않기 때문에 여기서 drop 됩니다 println!("{}", foo.x); // 모든 mutable 참조가 drop 되었으므로 이제 문제 없이 동작합니다 foo.x = 7; // foo의 ownership을 함수로 move 합니다 do_something(foo); }
메모리 상세
Rust는 데이터 경합의 가능성(동기화 이슈…) 때문에 소유된 값을 변경하는 방법이 여러 개 생기는 것을 방지
역참조
&mut
참조를 이용해*
연산자로 owner의 값을 설정
*
연산자로 own된 값의 복사본도 가져 올 수 있음
fn main() { let mut foo = 42; let f = &mut foo; let bar = *f; // owner의 값의 복사본을 가져옴 *f = 13; // 참조의 owner의 값을 설정함 println!("{}", bar); println!("{}", foo); }
대여한 데이터 전달하기
Rust의 참조 규칙 요약
- 1. Rust는 단 하나의 mutable한 참조 OR 2. 여러개의 immutable 참조만 허용 (둘다는 안됨)
- 참조는 소유자(owner)보다 더 오래 살 수 없음
struct Foo { x: i32, } fn do_something(f: &mut Foo) { f.x += 1; // mutable 참조 f는 여기서 drop 됩니다 } fn main() { let mut foo = Foo { x: 42 }; do_something(&mut foo); // 모든 mutable 참조가 do_something 함수 내에서 drop 되므로, // 하나 더 생성할 수 있습니다. do_something(&mut foo); // foo는 여기서 drop 됩니다 }
메모리 상세
- 첫 번째 참조 규칙은 데이터 경합을 방지
- 두 번째 참조 규칙은 존재하지 않는 데이터를 바라보는 잘못된 참조를 사용하는 것을 방지
- (NullPointException)
참조의 참조
struct Foo { x: i32, } fn do_something(a: &Foo) -> &i32 { return &a.x; // Foo 참조의 필드 x 참조를 리턴 (소유권 이전) } fn main() { let mut foo = Foo { x: 42 }; let x = &mut foo.x; *x = 13; // 여기서 x가 drop 되어 non-mutable 참조를 생성할 수 있습니다 let y = do_something(&foo); // y는 참조의 참조 println!("{}", y); // y는 여기서 drop 됩니다 // foo는 여기서 drop 됩니다 }
명시적인 생명주기
- Rust 컴파일러는 참조가 절대로 그 owner보다 더 오래 존재하지 못하도록 검증
- 함수에서는 명시적으로 심볼 표시로 서로 같은 lifetime을 공유하는지 식별할 수 있음
'
← lifetime 지정자 (예:'a
,'b
,'c
)
struct Foo { x: i32, } // 매개변수 foo와 리턴 값은 동일한 lifetime을 공유함 fn do_something<'a>(foo: &'a Foo) -> &'a i32 { return &foo.x; } fn main() { let mut foo = Foo { x: 42 }; let x = &mut foo.x; *x = 13; // x가 여기서 drop 되어, non-mutable 참조를 생성할 수 있음 let y = do_something(&foo); println!("{}", y); // y는 여기서 drop 됨 // foo는 여기서 drop 됨 }
여러 개의 생명주기
- lifetime 지정자는 lifetime을 명시적으로 지정
struct Foo { x: i32, } // foo_b와 리턴 값은 동일한 lifetime을 공유함 // foo_a는 무관한 lifetime을 가짐 fn do_something<'a, 'b>(foo_a: &'a Foo, foo_b: &'b Foo) -> &'b i32 { println!("{}", foo_a.x); println!("{}", foo_b.x); return &foo_b.x; } fn main() { let foo_a = Foo { x: 42 }; let foo_b = Foo { x: 12 }; let x = do_something(&foo_a, &foo_b); // 여기 이후에는 foo_b의 lifetime만 존재하므로 foo_a만 drop 됨 println!("{}", x); // 여기서 x가 drop 됨 // 여기서 foo_b가 drop 됨 }
Lifetime 지정자를 설정 하지 않으면?
fn do_something(foo_a: &Foo, foo_b: &Foo) -> &i32 { ---- ---- ^ expected named lifetime parameter
정적인 생명주기
- static 변수는 컴파일 타임에 생성되어 프로그램의 시작부터 끝까지 존재하는 메모리 리소스
- static lifetime은 프로그램이 끝날 때까지 무한정 유지되는 메모리 리소스
- static lifetime을 가진 리소스는
'static
lifetime 지정자 사용 → 절대 drop 되지 않음
- 만약 static lifetime을 가진 리소스가 참조를 포함하는 경우, 그들도 모두
'static
static PI: f64 = 3.1415; fn main() { // static 변수는 함수 scope 안에도 넣을 수 있습니다 static mut SECRET: &'static str = "swordfish"; // string 값들은 'static lifetime을 갖습니다 let msg: &str = "Hello World!"; let p: &f64 = &PI; println!("{} {}", msg, p); // 일부 규칙은 깰 수 있으나, 반드시 명시적으로 해야 합니다 unsafe { // SECRET에 string 값을 설정할 수 있는데, 이 값 역시 'static이기 때문입니다 SECRET = "abracadabra"; println!("{}", SECRET); } }
메모리 상세
- static 변수는 어느 누구에 의해서든 전역적으로 접근 가능. → 동기화 이슈
-
unsafe { ... }
블록으로 특정 동작에 대해 컴파일러가 메모리 검사를 하지 않음
→ Rustonomicon 언급데이터 자료형의 생명주기
- 데이터 자료형의 원소들도 lifetime 지정자로 지정 가능
- 참조를 포함하는 데이터 구조가 참조가 가리키는 owner보다 오래 지속되지 않도록 검증
- Null을 가리키는 참조가 있는 구조체는 존재 할 수 없음!
struct Foo<'a> { i:&'a i32 } fn main() { let x = 42; let foo = Foo { i: &x }; println!("{}",foo.i); }
Lifetime 지정자가 없으면?
error
[E0106]
: missing lifetime specifier
2 | i:&i32 | ^ expected named lifetime parameter
5장 - 마무리
Rust 소유권 목적
- 의도하지 않은 리소스 변경
- 리소스를 깜빡하고 소멸하지 않음
- 리소스를 실수로 두 번 소멸함
- 리소스가 소멸된 뒤에 사용함
- 다른 곳에서 읽고 있는 리소스에 쓰기를 하여 데이터 경합을 일으킴
- 컴파일러가 보증할 수 없는 코드가 뻔히 보임
The Rust Programming Language 링크
- 4. 소유권 이해하기
- 10. 3 라이프타임으로 참조자의 유효성 검증하기
의문점
- 여러 개의 생명주기 파트에서 lifetime 지정자를 설정하지 않은 파라메터가 함수 리턴 후에도 살아있는 이유?