소유권
- 소유권은 각 값에 대해 해당 값을 관리하는 고유한 소유자를 배정한다
- 컴파일러는 컴파일 타임에 소유권을 검증해 메모리 안전성을 보장한다
- 값은 소유자를 통해 대여할 수 있으며, 이를 빌림이라고 한다
- 빌림을 통해 한 소유권에 대한 여러 참조자를 만들 수 있다
소유권 이관
fn main() {
let s1 = String::from("Hello world");
let s2 = s1;
println!("{}", s1); // value borrowed here after move
}
- 첫 번째 줄에서, "Hello world"라는 문자열을 담은 메모리를 s1이 소유한다
- s1이 가지고 있던 소유권을 s2로 이관했다
- s1은 더이상 사용할 수 없다
clone을 사용한 복제
use std::clone;
fn main() {
let s1 = String::from("Hello world");
let s2 = s1.clone();
println!("{}", s1);
}
- clone을 통해 값을 복제한다
- "Hello world"라는 문자열을 담은 메모리가 2개가 되었으므로 각 변수가 문자열을 하나씩 소유한다
빌림
함수 파라미터로 소유권을 전달하는 경우
fn main() {
let s = String::from("Hello world");
push_str(s);
println!("{}", s); // value borrowed here after move
}
fn push_str(mut s: String) {
s.push_str("!!");
}
- 함수 파라미터로 값을 전달하는 경우, 소유권도 함께 이전된다
- 이전의 변수 s를 재사용할 수 없다
- 이를 해결하는 방법은 2가지가 있다
섀도잉으로 소유권을 다시 획득
fn main() {
let s = String::from("Hello world");
let s = push_str(s);
println!("{}", s); // value borrowed here after move
}
fn push_str(mut s: String) -> String {
s.push_str("!!");
s
}
빌림을 사용해 소유권 공유
fn main() {
let mut s = String::from("Hello world");
push_str(&mut s);
println!("{}", s); // value borrowed here after move
}
fn push_str(s: &mut String) {
s.push_str("!!");
}
- 빌림은
& 키워드를 사용한다
- 문자열 s의 소유권을 이전하지 않고 참조를 전달해 문자열의 내용을 변경한다
- 빌림은 값은 전달하되 소유권을 유지하고 싶은 경우 사용할 수 있다
데이터 복제
fn main() {
let mut x = 10;
let y = x;
println!("x: {}, y: {}", x, y);
x = 20;
println!("x: {}, y: {}", x, y);
}
- 위 코드는 clone 함수를 사용하지 않고도 데이터를 복제한다
- 이는 i32 타입에 copy trait이 구현되어 있기 때문이다
copy trait
#[derive(Copy, Clone)]
struct Point {
x: i32,
y: i32
}
fn main() {
let mut p1 = Point {x: 1, y: 2};
let p2 = p1;
p1.x = 5;
}
- Point 구조체에 copy trait을 적용하면 소유권을 이관하는 대신 값을 복제한다
- copy trait이 구현되지 않은 경우 clone함수를 사용할 수 있다
동적 메모리 할당
- 동적 메모리 할당은 런타임에 메모리를 할당하는 것이다
Box
fn main() {
let mut x = Box::new(10);
*x = 20
}
Box를 사용해 메모리를 동적으로 할당받을 수 있다
- 일종의 스마트 포인터 역할을 한다
- 할당받은 메모리에 접근할 때는 * 키워드를 사용해야 한다
- 동적으로 생성한 메모리는 해제해주지 않아도 데이터가 더이상 사용되지 않을 때 알아서 해제된다
Rc
use std::rc::Rc;
fn main() {
let num0 = Rc::new(100);
let num1 = num0.clone();
let num2 = num1.clone();
println!("{}", Rc::strong_count(&num0)); // 3
}
- Rc는 Reference Counting Pointer의 약자이다
- Rc는 불변성을 가진 참조형으로 공유 데이터를 변경할 수 없다
- Rc로 관리되는 데이터는 공유가 가능해 여러 변수가 동일한 값을 참조할 수 있도록 한다
- 즉, clone을 해도 실제 값이 복사되는게 아니라 ref count만 늘어난다
- 이때 Rc는 참조하는 변수들이 존재하는 동안 값을 해제하지 않는다
- Box는 빌림 방식 외에는 공유가 불가능하지만, Rc는 공유가 가능하다
RefCell
use std::{cell::RefCell, rc::Rc};
struct Node {
next: RefCell<Option<Rc<Node>>>
}
fn main() {
let head = Rc::new(Node { next: RefCell::new(None) });
let new_node = Rc::new(Node { next: RefCell::new(None) });
let mut next = head.next.borrow_mut(); // head의 next에 대한 가변 참조를 얻음
*next = Some(new_node.clone());
}
- Rc가 불변성이므로 Rc만으로는 공유 데이터를 수정할 수 없다
- RefCell을 사용하면 변경 불가능한 변수를 임시로 변경 가능하게 할 수 있다
use std::{cell::RefCell};
fn main() {
let num = RefCell::new(0);
{
let mut mut_num = num.borrow_mut();
*mut_num = 10;
} // mut_num의 scope가 끝나고 mut_num이 더이상 소유권을 빌리지 않아 값을 읽을 수 있음
println!("{}", num.borrow()); // 내부 값을 불변 참조
}
약한 참조
use std::{cell::RefCell, rc::Rc};
struct Node {
id: i32,
next: RefCell<Option<Rc<Node>>>,
}
impl Drop for Node {
fn drop(&mut self) {
println!("{} is drop", self.id);
}
}
fn main() {
let p1 = Rc::new(Node {id: 1, next: RefCell::new(None)});
let p2 = Rc::new(Node {id: 2, next: RefCell::new(None)});
let p3 = Rc::new(Node {id: 3, next: RefCell::new(None)});
let mut next = p1.next.borrow_mut();
*next = Some(p2.clone());
let mut next = p2.next.borrow_mut();
*next = Some(p1.clone());
}
- Rc는 ref count가 0이 되면 데이터를 삭제한다
- p1과 p2는 서로 순환참조 관계이다
- main 함수가 끝나고 p3는 drop이 호출되지만 p1과 p2는 drop이 호출되지 않고 메모리 누수가 발생한다
use std::{cell::RefCell, rc::{Rc, Weak}};
struct Node {
id: i32,
next: RefCell<Option<Weak<Node>>>,
}
impl Drop for Node {
fn drop(&mut self) {
println!("{} is drop", self.id);
}
}
fn main() {
let p1 = Rc::new(Node {id: 1, next: RefCell::new(None)});
let p2 = Rc::new(Node {id: 2, next: RefCell::new(None)});
let p3 = Rc::new(Node {id: 3, next: RefCell::new(None)});
let mut next = p1.next.borrow_mut();
*next = Some(Rc::downgrade(&p2));
let mut next = p2.next.borrow_mut();
*next = Some(Rc::downgrade(&p1));
}
- Weak을 사용하면 순환참조에 의한 메모리 누수를 해결할 수 있다
lifetime 지시자
fn func<'a>(arg: &'a str) -> &'a str {
// ...
}
struct MyStruct<'a> {
name: &'a str,
}
- lifetime 지시자는 변수를 대여할 때 대여기간을 명시적으로 지정하는데 사용한다
- lifetime은
'를 사용해서 정의한다
- lifetime 지시자는 관례적으로 a, b, c같은 알파벳을 사용한다
fn longest(x: &str, y: &str) -> &str { // missing lifetime specifier
if x.len() > y.len() {
x
} else {
y
}
}
- 위 함수에서는 lifetime 매개변수가 없다는 컴파일 오류가 발생한다
- 함수가 반환하는 변수의 생명주기가 매개변수와 동일하므로 같은 lifetime 매개변수를 사용해야 한다
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() {
x
} else {
y
}
}
- 위와 같이
'a lifetime 매개변수를 추가하면 정상적으로 동작한다
정적변수
static GLOBAL_CONST: i32 = 10;
fn main() {
let str: &'static str = "Hello";
}
- 정적변수는 프로그램이 종료될 때 까지 메모리에 유지되는 변수이다
- 특정 변수를
&'static로 선언하거나
- 전역변수를
static으로 선언해서 만들 수 있다
DoubleLinkedList 예시
use std::{cell::RefCell, rc::Rc};
type NodeType = Option<Rc<RefCell<Node>>>;
struct Node {
item: i32,
prev: NodeType,
next: NodeType,
}
impl Node {
fn new(item: i32) -> Self {
Self {item, prev: None, next: None}
}
}
struct DoubleLinkedList {
head: NodeType,
tail: NodeType
}
impl DoubleLinkedList {
fn new() -> Self {
Self {head: None, tail: None}
}
fn push_back(&mut self, item: i32) {
let node = Rc::new(RefCell::new(Node::new(item)));
if let Some(tail) = self.tail.take() {
tail.borrow_mut().next = Some(Rc::clone(&node));
node.borrow_mut().prev = Some(tail);
self.tail = Some(node);
} else {
self.head = Some(Rc::clone(&node));
self.tail = Some(Rc::clone(&node));
}
}
fn push_front(&mut self, item: i32) {
let node = Rc::new(RefCell::new(Node::new(item)));
if let Some(head) = self.head.take() {
head.borrow_mut().prev = Some(Rc::clone(&node));
node.borrow_mut().next = Some(head);
self.head = Some(node);
} else {
self.head = Some(Rc::clone(&node));
self.tail = Some(Rc::clone(&node));
}
}
fn print_all(&self) {
let mut cur = match &self.head {
Some(node) => {
node.clone()
},
None => return
};
loop {
let borrowed_cur = cur.borrow();
println!("item: {}", borrowed_cur.item);
let next = match &borrowed_cur.next {
Some(next) => next.clone(),
None => break
};
drop(borrowed_cur);
cur = next;
}
}
}
fn main() {
let mut list = DoubleLinkedList::new();
list.push_back(1);
list.push_back(2);
list.push_back(3);
list.print_all();
list.push_front(0);
list.print_all();
}
- DoubleLinkedList의 print_all 함수의 loop부분을 보자
- 우선 loop 바깥의 cur의 소유권을 borrowed_cur로 빌려온다
- borrowed_cur를 사용하다가 cur을 next로 바꾸기 위해 borrowed_cur가 빌려온 소유권을 drop으로 해제한다
- 그럼 소유권은 다시 cur이 갖게 되고, cur의 값을 next로 바꿀 수 있다