Life time
- second borrowing 등이 일어났을 경우 컴파일러는 해당 레퍼런스에 대해 어디까지 유효한지 체크하는 것이 불가능
- 컴파일러에게 레퍼런스가 어디까지 유효한지를 알려주는 장치
- life time을 설정해준다고 해서 실제 레퍼런스의 life time이 바뀌는 것은 아님, 단순히 컴파일러에게 알려주기 위한 용도
- 자세한 내용은 링크 참조
Vector Macro
- 기본적으로
vec
은 어떤 타입이 들어갈지 모르기 때문에, 데이터를 집어 넣어줘야 컴파일이 됨
vec
을 초기화하고 데이터를 집어넣는데 번거로운 코드를 매크로를 통해 쉽게 해결 가능
let name1="harvey";
let name2="mike";
vec![name1,name2];
from, into
- 타입을 바꾸는 것
- 어떤 타입 간에 변경이 가능한지를 확인하기 위해서는 Docs의 trait implementation을 참고하면 됨
let my_name = String::from("harvey");
let my_city:String = "Seoul".into();
tuple
let random_tuple = ("aaa",1,vec!['a'])
attribute
#[ATTR]
: inner attribute
#[ATTR]
: outer attribute
- Debug : struct 등에서 struct의 멤버를 println 에 넣을 경우 디버깅 가능하게 해줌
enum
use Enum::*
을 통해 어느 부분에서든 import 하는 것이 가능함
- enum은 default가 0부터며 값이 지정된다면 그 이후 값은 +1된 값을 가짐
- struct는 and, enum은 or 에 가까움
- enum이 function을 가지는 경우
enum Number{
U32(u32),
I32(i32)
}
fn get_number(input: i32) -> Number{
let number = match input.is_positive(){
true => Number::U32(input as u32),
false => Number::I32(input)
};
number
}
fn main(){
let my_vec = vec![get_number(-800),get_number(3)];
for item in my_vec{
match item{
Number::U32(number) => println!("U32 {}", number),
Number::I32(number) => println!("I32 {}", number),
}
}
}
loop
- loop 는 while로 변경 가능
- 제일 많이 쓰는 loop는 for loop임
fn main(){
let mut counter = 0;
let mut counter2 = 0;
'first_loop:loop{
counter +=1 ;
println!("The counter is now : {}", counter);
if counter > 9 {
'second_loop:loop{
println!("The second counter is now : {}", counter2);
counter2 +=1;
if counter2 == 4 {
break 'first_loop;
}
}
}
}
}
impl
#[derive(Debug)]
struct Animal{
age : u8,
animal_type : AnimalType
}
#[derive(Debug)]
enum AnimalType{
Cat,
Dog
}
impl Animal{
fn new_cat(age:u8) -> Self{
Self{
age : age,
animal_type:AnimalType::Cat
}
}
fn print(&self) {
println!("I am a : {:?}",self);
}
fn change_to_dog(&mut self){
self.animal_type = AnimalType::Dog;
}
}
fn main(){
let mut my_animal=Animal::new_cat(19);
my_animal.print();
my_animal.change_to_dog();
my_animal.print();
}
generic type
T
, U
는 원하는 것으로 바꿀수도 있음
where
를 사용하여 더 깔끔하게 표현 가능
use std::cmp::PartialOrd;
use std::fmt::Display;
fn compare_and_print<T: Display, U: Display + PartialOrd>(statement: T, num1: U, num2: U)
{
println!(
"{}! is {} greater than {}? {}",
statement,
num1,
num2,
num1 > num2
)
}
fn main() {
compare_and_print("Listen up!", 9, 8)
}
Result and Option
definition
enum Option<T> {
Some(T),
None,
}
enum Result<T, E> {
Ok(T),
Err(E),
}
Result
- Result는 enum type으로 Result<T,E>에서 T와 E는 각각 generic이며, success와 failure를 표현함
Ok(T)
: a Result
container which has succeeded, containing T
Err(E)
: a Result
container which has failed, containing E
- Ok를 unwrap()하면 안의 값이 안전하게 나오고, Err를 unwrap()하면 panic이 발생함
let good_result: Result<i32, i32> = Ok(10);
let bad_result: Result<i32, i32> = Err(10);
assert!(good_result.is_ok() && !good_result.is_err());
assert!(bad_result.is_err() && !bad_result.is_ok());
let good_result: Result<i32, i32> = good_result.map(|i| i + 1);
let bad_result: Result<i32, i32> = bad_result.map(|i| i - 1);
let good_result: Result<bool, i32> = good_result.and_then(|i| Ok(i == 11));
let bad_result: Result<i32, i32> = bad_result.or_else(|i| Ok(i + 20));
let final_awesome_result = good_result.unwrap();
Option
- Rust에서는
nil
이나 null
의 개념이 없는 대신 Option
타입이 존재함
- 이는 컨테이너 타입 안에 존재하거나 존재하지 않는다는 개념임
Some(<wrapped-value>)
: Some
은 내부의 value를 wrap하며 unwrap을 통해 액세스가 가능함
None
: None
- panic을 회피하고 안전하게 동작하는데 큰 도움을 줌
- if let 문법을 이용하여 match 에서 None을 작성하는 번거로움 해소 가능
fn take_fifth(value:Vec<i32>)->Option<i32>{
if value.len() <5 {
None
}else{
Some(value[4])
}
}
fn main() {
let new_vec = vec![1,2];
let index = take_fifth(new_vec);
match index{
Some(number) => println!("I got a number : {}", number),
None => println!("There was nothing inside"),
}
}
dyn
dyn
키워드는 Trait
과 연관된 매서드 콜이 동적으로 dispatch 되는 것을 강조하는데 사용 됨
- trait을 이와 같은 방법으로 사용하기 위해선 object safe 해야함
- object safe trait이란
- return 타입이
Self
가 아님
- generic type parameter가 없어야 함
- generic parameter나
impl Trait
와 달리, 컴파일러는 전달 받은 concrete type을 알지 못함 (type has been erased)
dyn Trait
레퍼런스는 2개의 포인터를 가짐
- 하나는 data (e.g., instance of struct)
- 다른 하나는 map of method (function pointer known as virtual method table or vtable)
- 런타임에서 매서드가
dyn Trait
에서 호출될 필요가 있을때, vtable은 function pointer를 가져오고 호출됨
- Trade-off
- 이와 같은 indirection 은 추가적인 runtime cost를 발생시킴
- dynamic dispatch에 의해 호출된 매서드는 컴파일러에 의해 inline되지 못함
- 하지만
dyn Trait
는 impl Trait
이나 generic parameter보다 더 적은 코드를 필요로함 (method가 각각의 concrete type에 대해 중복되지 않기 때문에)
smart pointer
Box<T>
- 데이터를 stack이 아닌 heap에 저장할 수 있도록 해줌
- 스택에는 힙 데이터를 가리키는 포인터가 존재함
- Box는 스택을 힙에 저장하는 것 이외에 성능적 오버헤드가 없음 (일반적으로 스택이 좋음)
- 다음과 같은 상황에서 자주 사용
- 컴파일 타임에 크기를 알 수 없는 타입을 갖고, 정확한 사이즈를 알 필요가 있는 타입의 값 이용
- 큰 사이즈의 데이터를 소유권 이동 시 데이터 카피가 되지 않을 것을 원할 때
- 어떤 값을 소유하고 해당 값의 타입을 알기보다는 특정 trait을 구현한 타입임만을 신경쓸 때
- 관련 글 포스트
Deref trait
- Deref trait을 이용하여 참조자를 통해 데이터 접근이 가능
- 기본적으로 integer와 &integer 다른 타입이기 때문에 비교가 불가능하여
*
를 통해 참조자를 따라가 해당 참조자가 가리키는 값을 얻어야 함
fn main() {
let x = 5;
let y = &x;
assert_eq!(5, x);
assert_eq!(5, *y);
}
error[E0277]: the trait bound `{integer}: std::cmp::PartialEq<&{integer}>` is
not satisfied
--> src/main.rs:6:5
|
6 | assert_eq!(5, y);
| ^^^^^^^^^^^^^^^^^ can't compare `{integer}` with `&{integer}`
|
= help: the trait `std::cmp::PartialEq<&{integer}>` is not implemented for
`{integer}`
fn main() {
let x = 5;
let y = Box::new(x);
assert_eq!(5, x);
assert_eq!(5, *y);
}
- Deref 트레잇을 구현하여 임의의 타입을 참조자처럼 다루기가 가능
use std::ops::Deref;
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
*y
는 러스트 내부적으로 *(y.deref())
와 같이 동작하게 됨
- Deref가 가변 참조자에 대한 오버라이딩 시 다음과 같이 동작
T: Deref<Target=U>
일 때 &T 에서 &U로
T: DerefMut<Target=U>
일 때 &mut T 에서 &mut U로
T: Deref<Target=U>
일 때 &mut T 에서 &U로
- 자세한 내용은 여기
Drop trait
- Drop trait은 메모리 정리 코드를 실행 시킴
- value가 scope 밖으로 벗어날려고 할 때 발생되는 일을 커스터마이징 가능
- Drop 트레잇은 대부분 스마트 포인터를 구현할 때 사용됨
Box<T>
는 박스가 가리키고 있는 힙 상의 공간을 할당해제 하기 위해 Drop 사용
- 일부 언어에서 메모리 해제를 매번 할당할 때 마다 해야하는데, 이와 달리 러스트는 특정 타입에 대해 자동으로 해결될 수 있게 해줌
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer { data: String::from("my stuff") };
let d = CustomSmartPointer { data: String::from("other stuff") };
println!("CustomSmartPointers created.");
}
CustomSmartPointers created.
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!
- 자동적으로 호출되는
drop
기능을 비활성화 할 필요는 보통 없지만 가끔 일찍 호출하길 원할 수 있음
- 이럴 경우
std::mem::drop
을 이용하여 값을 일찍 버리는 것이 가능함
fn main() {
let c = CustomSmartPointer { data: String::from("some data") };
println!("CustomSmartPointer created.");
c.drop();
println!("CustomSmartPointer dropped before the end of main.");
}
error[E0040]: explicit use of destructor method
--> src/main.rs:14:7
|
14 | c.drop();
| ^^^^ explicit destructor calls not allowed
--------------------------------------------------------------------
fn main() {
let c = CustomSmartPointer { data: String::from("some data") };
println!("CustomSmartPointer created.");
drop(c);
println!("CustomSmartPointer dropped before the end of main.");
}
CustomSmartPointer created.
Dropping CustomSmartPointer with data `some data`!
CustomSmartPointer dropped before the end of main.
Rc<T>
, 참조 카운팅 스마트 포인터
- 하나의 값을 여러 소유자가 가질 수가 있음
- 그래프 데이터 구조에서 여러 에지가 동일한 노드를 가리키는 것이 가능
- 해당 노드는 개념적으로 해당 노드를 가리키는 에지들의 소유가 됨
- 에지가 연결되어 있는 한 해당 노드가 해제되어선 안 됨
- 복수 소유권을 가능하게 하기 위해 러스트는
Rc<T>
(참조 카운팅)를 사용 함
- 프로그램의 여러 부분에서 읽을 데이터를 힙에 할당하고 싶고, 어떤 부분이 그 데이터를 마지막에 이용하게 되는지를 컴파일 타임에 알 수 없으면
Rc<T>
를 이용함
Rc<T>
는 오직 단일 스레드에서만 사용가능 함. 다중 스레드에서는 다르게 동작
enum List {
Cons(i32, Box<List>),
Nil,
}
use List::{Cons, Nil};
fn main() {
let a = Cons(5,
Box::new(Cons(10,
Box::new(Nil))));
let b = Cons(3, Box::new(a));
let c = Cons(4, Box::new(a));
}
error[E0382]: use of moved value: `a`
--> src/main.rs:13:30
|
12 | let b = Cons(3, Box::new(a));
| - value moved here
13 | let c = Cons(4, Box::new(a));
| ^ value used here after move
|
= note: move occurs because `a` has type `List`, which does not implement
the `Copy` trait
------------------------------------------------------------
enum List {
Cons(i32, Rc<List>),
Nil,
}
use List::{Cons, Nil};
use std::rc::Rc;
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
let b = Cons(3, Rc::clone(&a));
let c = Cons(4, Rc::clone(&a));
}
Rc<T>
의 클론 생성은 참조 카운트를 증가 시킴
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("count after creating a = {}", Rc::strong_count(&a));
let b = Cons(3, Rc::clone(&a));
println!("count after creating b = {}", Rc::strong_count(&a));
{
let c = Cons(4, Rc::clone(&a));
println!("count after creating c = {}", Rc::strong_count(&a));
}
println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}
count after creating a = 1
count after creating b = 2
count after creating c = 3
count after c goes out of scope = 2
RefCell<T>
- interior mutability (내부 가변성)은 어떤 데이터에 대해 불변 참조자가 있더라도 데이터를 변형할 수 있게하는 디자인 패턴
- 기본적으로는 borrow rule에 의해 허용되지 않음. 이를 위해 데이터 구조내에서 unsafe 코드를 사용함
순환참조
- 러스트는 뜻하지 않게 해제되지 않는 메모리를 발생시키는 것을 컴파일적으로 어렵게 하지만, 불가능한 것은 아님
- 순환 참조를 통해 메모리 릭을 발생시킬 수 있음
Rc<T>
및 RefCell<T>
를 활용하여 메모리릭을 발생시킬 수도 있음
- 기본적으로 순환 고리 안에서는 참조 카운트가 0이 되지 않는다느 점을 이용
use std::rc::Rc;
use std::cell::RefCell;
use List::{Cons, Nil};
#[derive(Debug)]
enum List {
Cons(i32, RefCell<Rc<List>>),
Nil,
}
impl List {
fn tail(&self) -> Option<&RefCell<Rc<List>>> {
match *self {
Cons(_, ref item) => Some(item),
Nil => None,
}
}
}
fn main() {
let a = Rc::new(Cons(5, RefCell::new(Rc::new(Nil))));
println!("a initial rc count = {}", Rc::strong_count(&a));
println!("a next item = {:?}", a.tail());
let b = Rc::new(Cons(10, RefCell::new(Rc::clone(&a))));
println!("a rc count after b creation = {}", Rc::strong_count(&a));
println!("b initial rc count = {}", Rc::strong_count(&b));
println!("b next item = {:?}", b.tail());
if let Some(link) = a.tail() {
*link.borrow_mut() = Rc::clone(&b);
}
println!("b rc count after changing a = {}", Rc::strong_count(&b));
println!("a rc count after changing a = {}", Rc::strong_count(&a));
}
a initial rc count = 1
a next item = Some(RefCell { value: Nil })
a rc count after b creation = 2
b initial rc count = 1
b next item = Some(RefCell { value: Cons(5, RefCell { value: Nil }) })
b rc count after changing a = 2
a rc count after changing a = 2
- 순환 참조를 방지하기 위해
Rc<T>
를 Weak<T>
로 바꿀 수 있음