Related to: Personal
스터디 멤버
참조
https://tourofrust.com/TOC_ko.html
1장 - 기초
Rust 놀이터
별도의 환경을 구성하지 않더라도, “Rust 놀이터”라고 하는 Web Service를 통해 기본적인 코드를 실행해볼 수 있다.
변수
- ‘let’ 키워드르 통해 변수 선언, 자료형은 일반적으로 적지 않아도 유추합니다.
- (마치 파이썬처럼..)
- 그러나 type을 명시할 수도 있습니다.
- variable shadowing
- 이미 선언한 변수 이름으로 다시 선언할 수 있습니다.
- 다시 선언하는 변수는 기존의 변수와 전혀 다른 타입일 수 있습니다.
fn main() {
// rust가 x의 자료형을 유추합니다
let x = 13;
println!("{}", x);
// 자료형을 명시적으로 지정할 수도 있습니다
let x: f64 = 3.14159;
println!("{}", x);
// 선언 후 나중에 초기화도 가능하지만, 보통 그렇게 하진 않습니다
let x;
x = 0;
println!("{}", x);
}변수의 값 변경하기
- mutable vs immutable
-
immutable(기본)
- 기본적으로 변수를 선언하면 c++의 const 키워드를 추가한 것처럼 변경이 불가한 상태가 됩니다.
- 다만, const와는 다릅니다. 어디까지나 런타임 데이터입니다.
-
mutable
- 변경 가능한 값은 mut 키워드를 추가하여 선언해야 합니다.
fn main() { let mut x = 42; println!("{}", x); x = 13; println!("{}", x); }
-
기본 자료형
- Rust에는 다양하지만 익숙한 자료형
- 부울 값 - 참/거짓 값을 나타내는
bool - 부호가 없는 정수형 - 양의 정수를 나타내는
u8u16u32u64u128 - 부호가 있는 정수형 - 양/음의 정수를 나타내는
i8i16i32i64i128 - 포인터 사이즈 정수 - 메모리에 있는 값들의 인덱스와 크기를 나타내는
usizeisize - 부동 소수점 -
f32f64 - 튜플(tuple) - stack에 있는 값들의 고정된 순서를 전달하기 위한
(값, 값, ...) - 배열(array) - 컴파일 타임에 정해진 길이를 갖는 유사한 원소들의 모음(collection)인
[값, 값, ...] - 슬라이스(slice) - 런타임에 길이가 정해지는 유사한 원소들의 collection
str(문자열 slice) - 런타임에 길이가 정해지는 텍스트- 텍스트는 다른 익숙한 언어에서보다 복잡한데, 이는 Rust가 시스템 프로그래밍 언어이며 여러분이 지금까지 익숙하지 않았을 메모리 문제에 신경쓰기 때문입니다. 이에 대해서는 나중에 더 자세히 다루겠습니다.
- 부울 값 - 참/거짓 값을 나타내는
- 숫자형 자료형들은 숫자 뒤에 자료형 이름을 붙여 명시적으로 지정할 수 있습니다 (예:
13u32,2u8).
fn main() {
let x = 12; // 기본적으로 i32
let a = 12u8;
let b = 4.3; // 기본적으로 f64
let c = 4.3f32;
let bv = true;
let t = (13, false);
let sentence = "hello world!";
println!(
"{} {} {} {} {} {} {} {}",
x, a, b, c, bv, t.0, t.1, sentence
);
}기본 자료형 변환
- as 키워드를 사용해 자료형을 매우 쉽게 변환할 수 있습니다.
fn main() {
let a = 13u8;
let b = 7u32;
let c = a as u32 + b;
println!("{}", c);
let t = true;
println!("{}", t as u8);
}상수
- const 키워드를 사용하여 상수를 선언할 수 있습니다.
- const로 선언된 값은 컴파일 타임에 텍스트 지정자를 직접 값으로 대체합니다.
const PI: f32 = 3.14159;
fn main() {
println!(
"아무 재료 없이 애플 {}를 만들려면, 먼저 우주를 만들어야 한다.",
PI
);
}배열
- _배열(array)_은 고정된 길이로 된 모두 같은 자료형의 자료를 가진 collection입니다.
fn main() {
let nums: [i32; 3] = [1, 2, 3];
println!("{:?}", nums);
println!("{}", nums[1]);
}함수
- fn 키워드르 메서드를 선언할 수 있습니다.
- typing hint가 추가된 python 메서드와 같이 [변수명:타입 → 리턴 값] 타입의 형태로 선언됩니다.
fn add(x: i32, y: i32) -> i32 {
return x + y;
}
fn main() {
println!("{}", add(42, 13));
}여러개의 리턴 값
- 튜플 형을 리턴함으로써 여러개의 값을 리턴할 수 있습니다.
- 또한 python 같은 destructuring 방법을 지원합니다.
fn swap(x: i32, y: i32) -> (i32, i32) {
return (y, x);
}
fn main() {
// 리턴 값의 튜플을 리턴
let result = swap(123, 321);
println!("{} {}", result.0, result.1);
// 튜플을 두 변수명으로 분해
let (a, b) = swap(result.0, result.1);
println!("{} {}", a, b);
}아무것도 리턴하지 않기
- 아무것도 반환하지 않는 메서드는 자동으로 empty tuple = ‘()’을 반환합니다.
fn make_nothing() -> () {
return ();
}
// 리턴 자료형은 ()로 암시
fn make_nothing2() {
// 리턴할 것이 지정되지 않으면 이 함수는 ()를 리턴함
}
fn main() {
let a = make_nothing();
let b = make_nothing2();
// 아무 것도 없는 것은 출력하기 힘들기 때문에
// a와 b의 디버그 문자열을 출력한다
println!("The value of a: {:?}", a);
println!("The value of b: {:?}", b);
}2장 - 기초적인 흐름 제어
if/else if/else
- 기존의 논리 연산자를 그대로 사용 할 수 있음
==,!=,<,>,<=,>=,!,||,&&
- python 처럼 조건문에 중괄호 ‘()’가 필요하지 않음
fn main() {
let x = 42;
if x < 42 {
println!("42보다 작다");
} else if x == 42 {
println!("42와 같다");
} else {
println!("42보다 크다");
}
}loop
- loop { … }로 반복
fn main() {
let mut x = 0;
loop {
x += 1;
if x == 42 {
break;
}
}
println!("{}", x);
}while
- whlie condition { … }로 반복
fn main() {
let mut x = 0;
while x != 42 {
x += 1;
}
}for
- for item in iterator { … }로 반복
fn main() {
for x in 0..5 {
println!("{}", x);
}
for x in 0..=5 {
println!("{}", x);
}
}match
- switch의 대용, 모든 case를 처리해야 한다.
fn main() {
let x = 42;
match x {
0 => {
println!("0 발견");
}
// 여러 개 값과 대조할 수 있다
1 | 2 => {
println!("1 또는 2 발견!");
}
// 범위로 대조할 수 있다
3..=9 => {
println!("3에서 9까지의 숫자 발견");
}
// 찾은 숫자를 변수에 바인딩할 수 있다
matched_num @ 10..=100 => {
println!("10에서 100까지의 숫자 {} 발견!", matched_num);
}
// 모든 케이스가 처리되지 않았을 경우 반드시 존재해야 하는 기본 match
_ => {
println!("뭔가 다른거 발견!");
}
}
}loop에서 값 리턴하기
- break 뒤에 값을 두면, loop 후 값이 리턴됨
fn main() {
let mut x = 0;
let v = loop {
x += 1;
if x == 13 {
break "13 찾았다";
}
};
println!("loop에서: {}", v);
}블록 표현에서 값 리턴하기
- 다른 언어들과 많이 다른 부분
- ‘return’이 없더라도,
if,match, 함수, 또는 범위 블록의 마지막 구문에 ’;‘가 없다면 Rust는 그 값을 블록의 리턴 값으로 간주
fn example() -> i32 {
let x = 42;
// Rust의 3항 연산 표현
let v = if x < 42 { -1 } else { 1 };
println!("if로부터: {}", v);
let food = "햄버거";
let result = match food {
"핫도그" => "핫도그다",
// 리턴문 하나 뿐이라면 중괄호는 필수가 아님
_ => "핫도그가 아니다",
};
println!("음식 판별: {}", result);
let v = {
// 이 범위 블록은 함수 범위를 더럽히지 않고 값을 가져오게 해준다
let a = 1;
let b = 2;
a + b
};
println!("block에서: {}", v);
// Rust에서 함수 마지막에 값을 리턴하는 관용적 표현
v + 4
}
fn main() {
println!("function에서: {}", example());
}2장 - 마무리
3장 - 기본 데이터 구조 자료형
구조체
struct는 필드(field)들의 collection- 이 정의는 메모리 상에 field들을 어떻게 배치할지에 대한 컴파일러의 청사진
- 정의한 순서에 따라 memory 순서까지 달라지는 것일까?
struct SeaCreature {
// String은 struct다
animal_type: String,
name: String,
arms: i32,
legs: i32,
weapon: String,
}메소드 호출하기
- c++과 비슷한 느낌으로 호출
- 스태틱 메소드(static methods) - 자료형 그 자체에 속하는 메소드로서,
::연산자를 이용하여 호출 - 인스턴스 메소드(instance methods) - 자료형의 인스턴스에 속하는 메소드로서,
.연산자를 이용하여 호출
fn main() {
// static method를 사용하여 String의 instance를 생성
let s = String::from("Hello world!");
// instance의 메소드를 사용
println!("{}의 글자 수는 {}입니다.", s, s.len());
}메모리
- Rust 프로그램에는 데이터가 저장되는 세 가지의 메모리 영역이 존재
- 데이터 메모리(data memory)
- 크기가 고정 되었으며 static한 데이터
- 예: “Hello World!”
- 이 텍스트의 바이트들은 오직 한 곳에서만 읽히므로 이 영역에 저장될 수 있음
- 이런 종류의 데이터는 컴파일러가 많은 최적화를 수행
- 위치가 알려져 있고 고정되어 있기 때문에 일반적으로 사용하기에 매우 빠름
- 크기가 고정 되었으며 static한 데이터
- 스택 메모리(stack memory)
- 함수 내에서 변수로 선언되는 데이터용
- 이 메모리의 위치는 함수 호출 동안에는 절대 변하지 않음
- 때문에 컴파일러가 코드를 최적화할 수 있으며, 이로 인해 접근하기에 매우 빠름
- 함수 내에서 변수로 선언되는 데이터용
- 힙 메모리(heap memory)
- 애플리케이션이 실행되는 동안 생성되는 데이터용
- 이 영역의 데이터는 추가하거나, 이동하거나, 제거하거나, 크기를 바꾸거나, 등을 할 수 있음
- 일반적으로 사용하기에 느리지만, 훨씬 더 창의적인 메모리 사용이 가능
- 데이터가 이 영역에 추가되면 할당(allocation)
- 데이터가 이 영역에서 제거되면 해제(deallocation)
- 애플리케이션이 실행되는 동안 생성되는 데이터용
메모리에 데이터 생성하기
- 데이터가 stack에 할당되는지, heap에 할당되는지에 대한 차이는 c++과 매우 유사
- struct의 field 값들은
.연산자를 통해 접근
let ferris = SeaCreature {
// String struct도 stack에 있지만,
// heap에 있는 데이터에 대한 참조를 갖고 있음
animal_type: String::from("게"),
name: String::from("Ferris"),
arms: 2,
legs: 4,
weapon: String::from("집게"),
};Tuple 같은 구조체
- 튜플형태로 struct를 생성할 수 있음
struct Location(i32, i32);
fn main() {
// 이것도 여전히 stack에 있는 struct임
let loc = Location(42, 32);
println!("{}, {}", loc.0, loc.1);
}Unit 같은 구조체
- struct 내에 아무것도 선언하지 않는다면 빈 튜플과 같아짐
- unit은 빈 튜플의 또 다른 이름
struct Location(i32, i32);
fn main() {
// 이것도 여전히 stack에 있는 struct임
let loc = Location(42, 32);
println!("{}, {}", loc.0, loc.1);
}열거형
- 열거형(enumeration)은
enum키워드를 통해 몇 가지 태그된 원소의 값을 가질 수 있는 새로운 자료형을 생성
enum Species {
Crab,
Octopus,
Fish,
Clam,
}열거형과 데이터
- 내용이 난해해서 원문 복붙..
enum의 원소들은 C의 _union_처럼 동작할 수 있도록 한 개 이상의 자료형을 가질 수 있습니다.
enum이match를 통해 패턴 일치될 때, 각각의 데이터 값에 변수명을 붙일 수 있습니다.
enum의 메모리 상세:
- enum 데이터 값은 가장 큰 원소의 메모리 크기와 같은 메모리 크기를 가집니다. 이는 가능한 모든 값이 동일한 메모리 공간에 들어갈 수 있게 해줍니다.
- 원소의 자료형(있는 경우)에 더하여, 각 원소는 무슨 태그에 해당하는지 나타내는 숫자값도 갖습니다.
다른 상세 정보:
- Rust의
enum은 _tagged union_으로도 알려져 있습니다.- Rust가 _대수적 자료형(algebraic types)_을 갖고 있다고 할 때 이는 자료형을 조합하여 새 자료형을 만드는 것을 의미합니다.
- Rust의
enum은 _tagged union_으로도 알려져 있음- tagged union
- https://80000coding.oopy.io/e1fcc2c2-8149-49a8-9b9b-544103481c9c
- enum 내에 선언된 원소 중 가장 큰 data size와 같은 메모리 크기를 가짐
- 가능한 모든 값이 동일 메모리 공간 안에 들어갈 수 있게 해줌
- 예
-
enum SomeType {
struct_1() // size = 3 byte,
struct_2() // size = 5 byte,
struct_3() // size = 1 byte,
} -
SomeType의 data 크기는 5 byte
-
- 즉, enum 내에 전혀 다른 data 크기의 값을 열거 할 수 있으며, 해당 enum의 data 크기는 열거된 원소 중 가장 큰 data size로 결정된다는 의미
- tagged union
enum PoisonType {
Acidic,
Painful,
Lethal,
}
enum Size {
Big,
Small,
}
enum Weapon {
Claw(i32, Size),
Poison(PoisonType),
None,
}
struct SeaCreature {
species: Species,
name: String,
arms: i32,
legs: i32,
weapon: Weapon,
}
fn main() {
// SeaCreature의 데이터는 stack에 있음
let ferris = SeaCreature {
// String struct도 stack에 있지만,
// heap에 있는 데이터에 대한 참조를 갖고 있음
species: Species::Crab,
name: String::from("Ferris"),
arms: 2,
legs: 4,
weapon: Weapon::Claw(2, Size::Small),
};
match ferris.species {
Species::Crab => match ferris.weapon {
Weapon::Claw(num_claws, size) => {
let size_description = match size {
Size::Big => "큰",
Size::Small => "작은",
};
println!(
"ferris는 {}개의 {} 집게를 가진 게이다",
num_claws, size_description
)
}
_ => println!("ferris는 다른 무기를 가진 게이다"),
},
_ => println!("ferris는 다른 동물이다"),
}
}3장 - 마무리
4장 - Generic 자료형
Generic 자료형이란?
- generic 자료형은
struct나enum을 부분적으로 정의하여, 컴파일러가 컴파일 타임에 코드 사용을 기반으로 완전히 정의된 버전을 만들 수 있게 해줍니다.struct BagOfHolding<T> { item: T, }
- Rust는 일반적으로 인스턴스화 하는 것을 보고 최종 자료형을 유추할 수 있습니다.
let float_bag = BagOfHolding { item: 3.14 };
- 그러나
::<T>연산자를 사용해 언제든 명시적으로 자료형을 지정할 수 있습니다.let i32_bag = BagOfHolding::<i32> { item: 42 };
아무 것도 없는 것을 표현하기 & 옵션
- Rust에는
null이 없습니다. - 한 개 이상의 선택 가능한 값에 대해
None선택지를 제공하는 방법은null값이 없는 Rust에서 매우 흔한 패턴입니다.-
실제 Rust에서 제공하는 generic enum : Option
- 참조 : https://showx123.tistory.com/58
- T와 Option
는 다른Type - null pointer dereference와 같은 메모리 취약점을 없앨 수 있다.
- Rust 컴파일러가 Option을 포인터로 컴파일 타임에 변환, 런타임 오버헤드는 사실상 0
- Enume은 원소 중 가장 큰 type의 사이즈를 갖도록 구현되어 있기 때문에 포인터의 사이즈(32비트면 4바이트, 64비트면 8바이트)보다 작은 타입의 경우 메모리를 아주 조금 낭비하게 됨.
enum Option<T> { Some(T), None, } struct BagOfHolding { item: Option<Inventory>, }
-
결과
-
Rust에는 실패할 가능성이 있는 값을 리턴할 수 있도록 해주는
Result라 불리는 내장된 generic enum- Rust에서 오류 처리를 하는 관용적인 방법
enum Result<T, E> { Ok(T), Err(E), } -
아래 코드를 돌려보면 “Standard Error”로 결과가 출력된다.
- 단순한 ‘표현’이 아닌듯.. throw exception 같은 표현일까?
fn do_something_that_might_fail(i: i32) -> Result<f32, String> { if i == 42 { Ok(13.0) } else { Err(String::from("맞는 숫자가 아닙니다")) } } fn main() { let result = do_something_that_might_fail(12); // match는 Result를 우아하게 분해하고, 모든 케이스를 처리하도록 해준다! match result { Ok(v) => println!("{} 발견", v), Err(e) => println!("오류: {}", e), } }
실패할 수 있는 메인
-
main 은 Result를 반환할 수 있음
fn do_something_that_might_fail(i: i32) -> Result<f32, String> { if i == 42 { Ok(13.0) } else { Err(String::from("맞는 숫자가 아닙니다")) } } // Main은 아무 값도 리턴하지 않지만, 오류를 리턴할 수 있다! fn main() -> Result<(), String> { let result = do_something_that_might_fail(12); match result { Ok(v) => println!("{} 발견", v), Err(_e) => { // 이 오류를 우아하게 처리한다 // main으로부터 무슨 일이 발생했는지 새 오류를 리턴한다! return Err(String::from("main에서 뭔가 잘못 되었습니다!")); } } // 모든 일이 잘 끝났음을 표현하기 위해 // Result Ok 안에 unit 값을 쓰고 있는걸 잘 봐두십시오 Ok(()) }
우아한 오류 처리
-
Result와 함께 쓸 수 있는 강력한 연산자?- match ~ 문을 이용한 예외처리를
?로 대체할 수 있음
fn do_something_that_might_fail(i: i32) -> Result<f32, String> { if i == 42 { Ok(13.0) } else { Err(String::from("맞는 숫자가 아닙니다")) } } fn main() -> Result<(), String> { // 얼마나 코드를 줄였는지 보세요! let v = do_something_that_might_fail(42)?; println!("{} 발견", v); Ok(()) } - match ~ 문을 이용한 예외처리를
추한 옵션/결과 처리
- nullable, OK/Err 반환에 대한 예외 처리를 아래와 같이 간결하게 사용 가능
- nullable
-
기존
match my_option { Some(v) => v, None => panic!("some error message generated by Rust!"), } -
unwrap 사용
let v = my_option.unwrap()
-
- OK / Err
-
기존
match my_result { Ok(v) => v, Err(e) => panic!("some error message generated by Rust!"), } -
unrwap 사용
let v = my_result.unwrap()
-
- nullable
벡터
- Vec
- struct로 표현되는 가변 크기의 리스트
- struct이나, 내부적으로는 내용물이 heap에 있는 고정 리스트에 대한 참조를 포함
- 기본 용량을 갖고 시작
- 용량보다 많은 내용물이 추가될 경우, 큰 용량을 가진 새 고정 리스트를 위해 heap에 데이터를 재할당
vec!- vector를 수동으로 일일이 만드는 대신, 손쉽게 생성할 수 있게 해줌
.iter()for반복문에 손쉽게 넣을 수 있도록 vector로부터 반복자를 생성하는 메소드
fn main() {
// 자료형을 명시적으로 할 수 있음
let mut i32_vec = Vec::<i32>::new(); // turbofish <3
i32_vec.push(1);
i32_vec.push(2);
i32_vec.push(3);
// 하지만 Rust가 얼마나 똑똑하게 자료형을 자동으로 결정하는지 보십시오
let mut float_vec = Vec::new();
float_vec.push(1.3);
float_vec.push(2.3);
float_vec.push(3.4);
// 아름다운 macro입니다!
let string_vec = vec![String::from("Hello"), String::from("World")];
for word in string_vec.iter() {
println!("{}", word);
}
}4장 - 마무리
5장 - 소유권과 데이터 대여
소유권
- 할당(binding)
- 자료형을 인스턴스화 하여 변수명에 하는 행위
- Rust 컴파일러가 전체 **생명주기(lifetime)**동안 검증할 메모리 리소스를 생성하는 것
- 할당된 변수는 리소스의 **소유자(owner)**라고 불립니다.
struct Foo {
x: i32,
}
fn main() {
// struct를 인스턴스화 하고 변수에 bind하여
// 메모리 리소스를 생성함
let foo = Foo { x: 42 };
// foo가 owner임
}범위 기반 리소스 관리
- Rust는 범위(scope)가 끝나는 곳에서 리소스를 소멸하고 할당 해제합니다.
- 이 소멸과 할당 해제를 의미하는 용어로 drop을 사용합니다.
- Rust에는 가비지 컬렉션이 없습니다.
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 됩니다
}Dropping은 계층적이다
- struct가 drop 될 때
- struct 자신이 제일 먼저 drop 됩니다
- 이후에 그 자식들이 각각 drop 되며, 등의 순서대로 처리됩니다.
- Rust에서는 메모리를 자동으로 해제함으로써 메모리 누수가 덜 일어나도록 합니다.
- 자식들이 포인터 형인 경우에도, heap에 있을 경우에도 알아서 해제해줄까?
- 메모리 리소스는 단 한 번 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 됩니다
}소유권 이전
- owner가 함수의 인자로 전달되면, ownership은 그 함수의 매개변수로 이동(move)됩니다.
- move 이후에는 원래 함수에 있던 변수는 더 이상 사용할 수 없습니다.
- move 중에는 owner 값의 stack 메모리가 함수 호출의 매개변수 stack 메모리로 복사됩니다.
struct Foo {
x: i32,
}
fn do_something(f: Foo) {
println!("{}", f.x);
// f가 여기서 drop 됩니다
}
fn main() {
let foo = Foo { x: 42 };
// foo가 do_something으로 move 됩니다
do_something(foo);
// foo는 더 이상 사용할 수 없습니다
}소유권 리턴하기
- ownership은 함수에서도 리턴될 수 있습니다.
- 기존 c++, c#에서는 포인터가 아닌 이상 값의 복사가 일어 났는데, 러스트는 기본적으로 메모리 주소 자체를 아에 넘겨버리는 것으로 보임
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 되거나 변경될 수 없습니다.
- Rust는 데이터 경합의 가능성 때문에 소유된 값을 변경하는 방법이 여러 개 생기는 것을 방지합니다.
→ 소유권을 대여해주면, 원래의 소유권을 가졌던 참조는 소유권이 반환될 때까지 변경 권한을 잃는다.
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하게 borrow된 상태에서는 move될 수 없기 때문입니다
// FAILURE: foo.x = 13; 는 여기서 실패할 것입니다
// 왜냐하면 foo는 mutable하게 borrow된 상태에서는 변경할 수 없기 때문입니다
f.x = 13;
// f는 이 시점 이후 더 이상 사용되지 않기 때문에 여기서 drop 됩니다
println!("{}", foo.x);
// 모든 mutable 참조가 drop 되었으므로 이제 문제 없이 동작합니다
foo.x = 7;
// foo의 ownership을 함수로 move 합니다
do_something(foo);
}역참조
&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의 참조 규칙은 다음과 같이 요약될 수 있습니다:
- Rust는 단 하나의 mutable한 참조 또는 여러개의 non-mutable 참조만 허용하며, 둘 다는 안됨.
- 참조는 그 owner보다 더 오래 살 수 없음.
- 첫 번째 참조 규칙은 데이터 경합을 방지합니다.
- 데이터를 읽는 행위가 동시에 데이터를 쓰는 이의 존재로 인해 동기화가 어긋날 가능성이 있을 때 일어납니다.
- 두 번째 참조 규칙은 존재하지 않는 데이터를 바라보는 잘못된 참조를 사용하는 것을 방지
- 이를 C에서는 허상 포인터(dangling pointers)라고 부름
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 됩니다
}참조의 참조
- 참조는 심지어 참조에도 사용될 수 있습니다.
struct Foo {
x: i32,
}
fn do_something(a: &Foo) -> &i32 {
return &a.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 됩니다
}명시적인 생명주기
- Rust 컴파일러는 모든 변수의 lifetime을 이해하며 참조가 절대로 그 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 됨
}정적인 생명주기
- static 변수는 컴파일 타임에 생성되어 프로그램의 시작부터 끝까지 존재하는 메모리 리소스입니다.
- 이들은 명시적으로 자료형을 지정해 주어야 합니다.
- static lifetime은 프로그램이 끝날 때까지 무한정 유지되는 메모리 리소스입니다.
- 이 정의에 따르면, 어떤 static lifetime의 리소스는 런타임에 생성될 수도 있습니다.
- static lifetime을 가진 리소스는
'static이라는 특별한 lifetime 지정자를 갖습니다. 'static한 리소스는 절대 drop 되지 않습니다.- static lifetime을 가진 리소스가 참조를 포함하는 경우, 그들도 모두
'static이어야 합니다- 그 이하의 것들은 충분히 오래 살아남지 못합니다
- static 변수는 어느 누구에 의해서든 전역적으로 접근 가능하기 때문에, 이를 변경하는 것은 데이터 경합을 유발하는, 본질적으로 위험한 행위입니다.
- Rust에서는
unsafe { ... }블록을 이용하여 특정 동작에 대해 컴파일러가 메모리 검사를 하지 않도록 할 수 있습니다.- 없을 경우 :
error[E0133]: use of mutable static is unsafe and requires unsafe function or block - 아래 예제의 경우, unsafe를 없애면
SECRET = "abracadabra";와println!("{}", SECRET);에 모두 오류가 발생
- 없을 경우 :
static PI: f64 = 3.1415;
fn main() {
// static 변수는 함수 scope 안에도 넣을 수 있습니다
static mut SECRET: &'static str = "swordfish";
// string 값들은 'static lifetime을 갖습니다
let msg: &'static str = "Hello World!";
let p: &'static f64 = &PI;
println!("{} {}", msg, p);
// 일부 규칙은 깰 수 있으나, 반드시 명시적으로 해야 합니다
unsafe {
// SECRET에 string 값을 설정할 수 있는데, 이 값 역시 'static이기 때문입니다
SECRET = "abracadabra";
println!("{}", SECRET);
}데이터 자료형의 생명주기
- 함수와 마찬가지로, 데이터 자료형의 구성원들도 lifetime 지정자로 지정할 수 있습니다.
- Rust는 참조가 품고 있는 데이터 구조가 참조가 가리키는 owner보다 절대 오래 살아남지 못하도록 검증합니다.
struct Foo<'a> {
i:&'a i32
}
fn main() {
let x = 42;
let foo = Foo {
i: &x
};
println!("{}",foo.i);
}5장 - 마무리
6장 - 텍스트
문자열
- 언제나 유니코드로 되어 있음
- 문자열의 자료형은
&'static str입니다:&는 메모리 내의 장소를 참조하고 있다는 의미&mut가 빠졌다는 것은 컴파일러가 값의 변경을 허용하지 않을 것이라는 뜻'static은 string 데이터가 프로그램이 끝날 때까지 유효하다는 의미(절대 drop 되지 않음)str은 언제나 유효한 utf-8인 바이트 열을 가리키고 있다는 의미
- Rust 컴파일러는 문자열을 프로그램 메모리의 데이터 세그먼트에 저장
let a: &'static str = "hi 🦀";
utf-8이란 무엇인가
- utf-8은 1에서 4 바이트의 가변 길이 바이트로 도입 되었음
- 사용 가능한 문자의 범위를 엄청나게 늘어남
- ASCII 문자에 쓸데 없는 바이트를 필요로 하지 않음(utf-8에서도 여전히 1 바이트만 필요로 함)
- 단순한 인덱싱(예:
my_text[3]으로 네 번째 문자를 가져옴)시, O(1) 상수 시간으로 문자를 찾을 수 없음- 바로 앞의 글자가 가변 길이를 가질 수 있으므로, 바이트 열에서 4번째 문자가 실제로 시작하는 위치가 달라질 수도 있음
- utf-8 바이트 열을 하나하나 돌면서 각각의 유니코드 문자가 실제로 어디에서 시작하는지 찾아야 함 (O(n) 선형 시간)
예외처리문자
-
어떤 문자들은 시각적으로 표현하기 어려우므로, 예외처리 코드(escape code) 로 대체하여 사용
-
Rust는 C 기반 언어들의 일반적인 예외처리 코드를 지원
\n- 줄바꿈\r- 캐리지리턴\t- 탭\\- 역슬래시\0- null\'- 작은 따옴표
→ 전체 목록은 이곳에서
여러 줄로 된 문자열
- Rust의 문자열은 기본적으로 여러 줄로 되어 있음
- 줄바꿈 문자를 원하지 않을 경우, 줄 맨 뒤에
\를 사용
fn main() {
let haiku: &'static str = "
나는 쓰고, 지우고, 다시 쓴다
다시 지우고, 그러고 나면
양귀비 꽃이 핀다.
- 가쓰시카 호쿠사이";
println!("{}", haiku);
println!(
"hello \
world"
) // w 앞의 공백이 무시 되었음을 주의하세요
}원시 문자열
r#"로 시작하고"#로 끝남- 문자열을 있는 그대로 쓸 수 있음
- 큰 따옴표나 역슬래시 같은 문자들을 바로 사용할 수 있음
fn main() {
let a: &'static str = r#"
<div class="advice">
원시 문자열은 일부 상황에서 유용합니다.
</div>
"#;
println!("{}", a);
}파일에서 문자열 가져오기
include_str!macro를 사용하여 local에 있는 문자열을 가져올 수 있음let 00_html = include_str!("00_ko.html");
문자열 슬라이스
- 문자열 slice는 메모리 상의 바이트 열에 대한 참조
- utf-8의 특성 상, char에 대한 index 접근과 다르다.
- slice의 문자열 slice (sub-slice)도 역시 유효한 utf-8이어야 함
- slice의 결과물이 문자열을 만들 지 못할 때 EROR
- 흔히 사용되는 메소드
len은 문자열의 바이트 길이(글자수가 아님)starts_with/ends_with는 기본적인 비교에 사용is_empty는 길이가 0일 경우 true를 반환find는 주어진 텍스트가 처음 등장 하는 위치인Option<usize>값을 반환
문자
- utf-8 바이트 열을
char자료형의 vector로 돌려주는 기능을 제공 char하나는 4 바이트(각각의 문자를 효율적으로 찾을 수 있음)
fn main() {
// 문자들을 char의 vector로 가져옵니다
let chars = "hi 🦀".chars().collect::<Vec<char>>();
println!("{}", chars.len()); // 4여야 합니다
// char가 4 바이트이므로 u32로 변환할 수 있습니다
println!("{}", chars[3] as u32);
}스트링
- String은 utf-8 바이트 열을 heap memory에 소유하는 struct
- heap에 있기 때문에, 문자열과는 달리 늘리거나 변경하거나 기타 등등을 할 수 있음
push_str은 string의 맨 뒤에 utf-8 바이트들을 더 붙일 때 사용replace는 utf-8 바이트 열을 다른 것으로 교체할 때 사용to_lowercase/to_uppercase는 대소문자를 바꿀 때 사용trim은 공백을 제거할 때 사용
fn main() {
let mut helloworld = String::from("hello");
helloworld.push_str(" world");
helloworld = helloworld + "!";
println!("{}", helloworld);
}함수의 매개변수로서의 텍스트
- 문자열과 string은 일반적으로 함수에 문자열 slice 형태로 전달
fn say_it_loud(msg: &str) {
println!("{}!!!", msg.to_string().to_uppercase());
}
fn main() {
// say_it_loud는 &'static str을 &str로 대여할 수 있습니다
say_it_loud("hello");
// say_it_loud는 또한 String을 &str로 대여할 수 있습니다
say_it_loud(&String::from("goodbye"));
}스트링 만들기
concat과join은 string을 만드는 간단하지만 강력한 방법
fn main() {
let helloworld = ["hello", " ", "world", "!"].concat();
let abc = ["a", "b", "c"].join(",");
println!("{}", helloworld);
println!("{}",abc);
}→ hello world!
→ a,b,c
스트링 양식 만들기
format!macro는 값이 어디에 어떻게 놓일지 매개변수화 된 (예:{}) string을 정의하여 string을 생성
fn main() {
let a = 42;
let f = format!("삶, 우주, 그리고 모든 것에 대한 해답: {}", a);
println!("{}", f);
}스트링 변환
to_string을 이용하여 string으로 변환될 수 있음(모든 자료형이 되는 것은 아님)- generic 함수인
parse로 string이나 문자열을 다른 자료형을 가진 값으로 변환할 수 있음- 실패할 수도 있기 때문에
Result를 리턴
- 실패할 수도 있기 때문에
fn main() -> Result<(), std::num::ParseIntError> {
let a = 42;
let a_string = a.to_string();
let b = a_string.parse::<i32>()?;
println!("{} {}", a, b);
Ok(())
}6장 - 마무리
7장 - 객체 지향 프로그래밍
OOP란 무엇인가?
- 다음과 같은 상징적 특징을 가진 프로그래밍 언어를 뜻함
- 캡슐화 (encapsulation) - _객체_라 불리는 단일 유형의 개념적 단위에 데이터와 함수를 연결지음.
- 추상화 (abstraction) - 데이터와 함수를 숨겨 객체의 상세 구현 사항을 알기 어렵게 함.
- 다형성 (polymorphism) - 다른 기능적 관점에서 객체와 상호작용하는 능력.
- 상속 (inheritance) - 다른 객체로부터 데이터와 동작을 상속받는 능력.
Rust는 OOP가 아니다
- Rust에서는 어떠한 방법으로도 데이터와 동작의 상속이 불가능
- struct는 부모 struct로부터 field를 상속받을 수 없습니다.
- struct는 부모 struct로부터 함수를 상속받을 수 없습니다.
메소드 캡슐화 하기
-
모든 메소드의 첫번째 매개변수는 메소드 호출과 연관된 인스턴스에 대한 참조여야 함
&self- 인스턴스에 대한 immutable한 참조.&mut self- 인스턴스에 대한 mutable한 참조.- ex:
instanceOfObj.foo()
-
메소드는
impl키워드를 쓰는 구현 블록 안에 정의impl MyStruct { ... fn foo(&self) { ... } }
struct SeaCreature {
noise: String,
}
impl SeaCreature {
fn get_sound(&self) -> &str {
&self.noise
}
}
fn main() {
let creature = SeaCreature {
noise: String::from("blub"),
};
println!("{}", creature.get_sound());
}선택적 노출을 통한 추상화
- 기본적으로, filed와 메소드들은 그들이 속한 module에서만 접근 가능
pub키워드는 struct의 field와 메소드를 module 밖으로 노출시킴
struct SeaCreature {
pub name: String,
noise: String,
}
impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}
fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
println!("{}", creature.get_sound());
}다형성과 Trait
- trait은 메소드의 집합을 struct 자료형에 연결할 수 있게 해줌
- struct가 trait을 구현할 때, 실제 자료형이 무엇인지 알지 못하더라도 그 trait 자료형을 통해 간접적으로 struct와 상호작용할 수 있도록 (예:
&dyn MyTrait) 협약을 맺게 됨
struct SeaCreature {
pub name: String,
noise: String,
}
impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}
trait NoiseMaker {
fn make_noise(&self);
}
impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}
fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
creature.make_noise();
}Trait에 구현된 메소드
- trait에 메소드를 구현해 넣을 수 있음
- struct 내부의 field에 직접 접근할 수는 없지만, trait 구현체들 사이에서 동작을 공유할 때 유용
struct SeaCreature {
pub name: String,
noise: String,
}
impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}
trait NoiseMaker {
fn make_noise(&self);
fn make_alot_of_noise(&self){
self.make_noise();
self.make_noise();
self.make_noise();
}
}
impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}
fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
creature.make_alot_of_noise();
}Trait 상속
- trait은 다른 trait의 메소드들을 상속 받을 수 있음
struct SeaCreature {
pub name: String,
noise: String,
}
impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}
trait NoiseMaker {
fn make_noise(&self);
}
trait LoudNoiseMaker: NoiseMaker {
fn make_alot_of_noise(&self) {
self.make_noise();
self.make_noise();
self.make_noise();
}
}
impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}
impl LoudNoiseMaker for SeaCreature {}
fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
creature.make_alot_of_noise();
}동적 vs 정적 디스패치
- 정적 디스패치 (static dispatch) - 인스턴스의 자료형을 알고 있는 경우, 어떤 함수룰 호출해야 하는지 정확히 알고 있음
- 동적 디스패치 (dynamic dispatch) - 인스턴스의 자료형을 모르는 경우, 올바른 함수를 호출할 방법을 찾아야 함
- trait 자료형인
&dyn MyTrait은 동적 디스패치를 통해 객체의 인스턴스들을 간접적으로 작동시킬 수 있게 함 - 동적 디스패치를 사용할 경우, Rust에서는 사람들이 알 수 있도록 trait 자료형 앞에
dyn을 붙일 것을 권고
- trait 자료형인
- 동적 디스패치는 실제 함수 호출을 위한 포인터 추적으로 인해 조금 느릴 수 있음
struct SeaCreature {
pub name: String,
noise: String,
}
impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}
trait NoiseMaker {
fn make_noise(&self);
}
impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}
fn static_make_noise(creature: &SeaCreature) {
// 실제 자료형을 압니다
creature.make_noise();
}
fn dynamic_make_noise(noise_maker: &dyn NoiseMaker) {
// 실제 자료형을 모릅니다
noise_maker.make_noise();
}
fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
static_make_noise(&creature);
dynamic_make_noise(&creature);
}Trait 객체
- 객체의 인스턴스를
&dyn MyTrait자료형을 가진 매개변수로 넘길 때, 이를 _trait 객체_라고 부름 - trait 객체
- 인스턴스의 올바른 메소드를 간접적으로 호출할 수 있게 해줌
- 인스턴스에 대한 포인터와 인스턴스 메소드들에 대한 함수 포인터 목록을 갖고있는 struct
- 이런 함수 목록을 C++에서는 _vtable_이라고 함
크기를 알 수 없는 데이터 다루기
- trait을 다른 struct에 저장하는 것
- trait은 원본 struct를 알기 어렵게 하느라 원래 크기 또한 알기 어렵게 함
- Rust에서 크기를 알 수 없는 값이 struct에 저장될 때는 다음의 두 가지 방법으로 처리
generics- 매개변수의 자료형을 효과적으로 활용하여 알려진 자료형 및 크기의 struct/함수를 생성indirection- 인스턴스를 heap에 올림으로써 실제 자료형의 크기 걱정 없이 그 포인터만 저장
Generic 함수
-
Rust의 generic은 trait과 함께 작동
-
매개변수 자료형
T를 정의할 때 해당 인자가 어떤 trait을 구현해야 하는지 나열함으로써 인자에 어떤 자료형을 쓸 수 있는지 제한할 수 있음fn my_function<T>(foo: T) where T:Foo { ... } -
generic을 이용하면 컴파일 시 자료형과 크기를 알 수 있는 정적 자료형의 함수가 만들어짐
- 정적 디스패치와 함께 크기가 정해진 값으로 저장할 수 있게 됨
struct SeaCreature {
pub name: String,
noise: String,
}
impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}
trait NoiseMaker {
fn make_noise(&self);
}
impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}
fn generic_make_noise<T>(creature: &T)
where
T: NoiseMaker,
{
// 컴파일 타임에 실제 자료형을 알게 됩니다
creature.make_noise();
}
fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
generic_make_noise(&creature);
}Generic 함수 줄여쓰기
-
trait으로 제한한 generic은 다음과 같이 줄여쓸 수 있음
fn my_function(foo: impl Foo) { ... } // 줄여쓰기 전 fn my_function<T>(foo: T) where T:Foo { ... }
struct SeaCreature {
pub name: String,
noise: String,
}
impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}
trait NoiseMaker {
fn make_noise(&self);
}
impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}
fn generic_make_noise(creature: &impl NoiseMaker) {
// 컴파일 타임에 실제 자료형을 알게 됩니다
creature.make_noise();
}
fn main() {
let creature = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
generic_make_noise(&creature);
}Box
- stack에 있는 데이터를 heap으로 옮길 수 있게 해주는 데이터 구조
- _smart pointer_로도 알려진 struct이며 heap에 있는 데이터를 가리키는 포인터를 들고 있음
- 크기가 알려져 있는 struct이므로 (왜냐하면 그저 포인터만 들고 있으므로) field의 크기를 알아야 하는 struct에 뭔가의 참조를 저장할 때 종종 사용 됨
struct SeaCreature {
pub name: String,
noise: String,
}
impl SeaCreature {
pub fn get_sound(&self) -> &str {
&self.noise
}
}
trait NoiseMaker {
fn make_noise(&self);
}
impl NoiseMaker for SeaCreature {
fn make_noise(&self) {
println!("{}", &self.get_sound());
}
}
struct Ocean {
animals: Vec<Box<dyn NoiseMaker>>,
}
fn main() {
let ferris = SeaCreature {
name: String::from("Ferris"),
noise: String::from("blub"),
};
let sarah = SeaCreature {
name: String::from("Sarah"),
noise: String::from("swish"),
};
let ocean = Ocean {
animals: vec![Box::new(ferris), Box::new(sarah)],
};
for a in ocean.animals.iter() {
a.make_noise();
}
}Generic 구조체 다시 보기
- generic struct는 trait으로 제한된 매개변수 자료형을 가질 수 있음
struct MyStruct<T>
where
T: MyTrait
{
foo: T
...
}
//매개변수 자료형은 generic structure의 구현 블록 안에 표시
impl<T> MyStruct<T> {
...
}7장 - 마무리
8장 - 스마트 포인터
참조 다시 보기
- 참조는 메모리 상의 어떤 바이트들의 시작 위치를 가리키는 숫자
- 용도
- 특정 자료형의 데이터가 어디에 존재하는지에 대한 개념을 나타내는 것
- 일반 숫자와의 차이점
- Rust에서 참조가 가리키는 값보다 더 오래 살지 않도록 lifetime을 검증하는 것
원시 포인터
- 참조는 더 원시적인 자료형인 _raw pointer_로 변환될 수 있음
- raw pointer는 숫자와 마찬가지로 거의 제한 없이 여기저기 복사하고 이동할 수 있음
- Rust는 raw pointer가 가리키는 메모리 위치의 유효성을 보증하지 않습니다.
- raw pointer에는 두 종류
const T- 자료형 T의 데이터를 가리키는 절대 변경되지 않는 raw pointer.mut T- 자료형 T의 데이터를 가리키는 변경될 수 있는 raw pointer.
- raw pointer는 숫자와 상호 변환이 가능
- 예:
usize
- 예:
- raw pointer는 _unsafe_한 코드의 데이터에 접근할 수 있음
- 참조 vs raw pointer
- Rust에서의 참조
- 사용 방법에 있어서 C의 pointer와 매우 유사하나, 저장되는 방식이나 다른 함수에 전달되는 부분에 있어 훨씬 많은 컴파일 타임의 제약을 받음
- Rust에서의 raw pointer
- 복사하고 전달하고 심지어 pointer 연산을 할 수 있는 숫자 자료형으로 변환할 수도 있다는 점에서 C의 pointer와 유사
- Rust에서의 참조
fn main() {
let a = 42;
let memory_location = &a as *const i32 as usize;
println!("데이터는 여기 있습니다: {}", memory_location);
}
-> 데이터는 여기 있습니다: 140725281338716역참조
- 참조 (i.e.
&i32)를 통해 참조되는 데이터를 접근/변경하는 것 - 참조로 데이터에 접근/변경하는 데에는 다음의 두 가지 방법(=역참조 방법)
- 변수 할당 중에 참조되는 데이터에 접근
- 참조되는 데이터의 field나 메소드에 접근
* 연산자
-
*연산자는 참조를 역참조 하는 명시적인 방법 -
i32는 ==
Copytrait을 구현하는 기본 자료형이기 때문에, stack에 있는 변수a==의 바이트들은 변수 ==b==의 바이트들로 복사https://velog.io/@undefcat/Rust-T-mut-T-그리고-Copy
- copy trait : 일종의 shallow copy? ↔ clone trait
- trait : rust의 object
- copy trait : 일종의 shallow copy? ↔ clone trait
fn main() {
let a: i32 = 42;
let ref_ref_ref_a: &&&i32 = &&&a;
let ref_a: &i32 = **ref_ref_ref_a;
let b: i32 = *ref_a;
println!("{}", b)
}
-> 42. 연산자
-
참조의 field와 메소드에 접근하는 데에 쓰임, 이건 좀 더 미묘하게 동작
let f = Foo { value: 42 }; let ref_ref_ref_f = &&&f; println!("{}", ref_ref_ref_f.value);.연산자가 참조 열을 자동으로 역참조- 저 마지막 줄은 컴파일러에 의해 자동적으로 다음과 같이 바뀌게 됨
println!("{}", (***ref_ref_ref_f).value);
스마트 포인터
&연산자로 이미 존재하는 데이터의 참조를 생성하는 기능과 더불어, Rust에서는 smart pointer라 불리는 참조 같은 struct를 생성하는 기능을 제공- 고수준에서 보자면 참조는 다른 자료형에 대한 접근을 제공하는 자료형이라고 볼 수 있음
- smart pointer가 일반적인 참조와 다른 점은, 프로그래머가 작성하는 내부 로직에 기반해 작동한다는 것
- 일반적으로 smart pointer는 struct가
*와.연산자로 역참조될 때 무슨 일이 발생할지 지정하기 위해Deref,DerefMut, 그리고Droptrait을 구현
use std::ops::Deref;
struct TattleTell<T> {
value: T,
}
impl<T> Deref for TattleTell<T> {
type Target = T;
fn deref(&self) -> &T {
println!("{} was used!", std::any::type_name::<T>());
&self.value
}
}
fn main() {
let foo = TattleTell {
value: "secret message",
};
// foo가 `len` 함수를 위해 자동참조된 후
// 여기서 역참조가 즉시 일어납니다
println!("{}", foo.len());
}
-> &str was used!
14==→ 문자열은 필드에 존재하는데, foo의 참조를 재정의해서 self.value의 참조를 반환하도록 했기 때문에 len을 호출 할 수 있게 되었다.==
위험한 스마트 코드
- smart pointer는 _unsafe_한 코드를 꽤 자주 쓰는 경향이 있음.
- smart pointer는 Rust에서 가장 저수준의 메모리를 다루기 위한 일반적인 도구
- unsafe한 코드
- Rust 컴파일러가 보증할 수 없는 몇 가지 기능이 있다는 예외사항을 제외하고는 일반적인 코드와 완전히 똑같이 동작
- unsafe한 코드의 주기능은 _raw pointer를 역참조_하는 것
- 이는 _raw pointer_를 메모리 상의 위치에 가져다 놓고 “데이터 구조가 여깄다!”고 선언한 뒤 사용할 수 있는 데이터 표현으로 변환하는 것을 의미
- 예:
*const u8을u8
- 예:
- Rust에는 메모리에 쓰여지는 모든 바이트의 의미를 추적하는 방법은 없음
- Rust는 _raw pointer_로 쓰이는 임의의 숫자에 무엇이 존재하는지 보증할 수 없기 때문에, 역참조를
unsafe { ... }블록 안에 넣음
fn main() {
let a: [u8; 4] = [86, 14, 73, 64];
// 이게 원시 pointer입니다.
// 무언가의 메모리 주소를 숫자로 가져오는 것은 완전히 안전한 일입니다
let pointer_a = &a as *const u8 as usize;
println!("데이터 메모리 주소: {}", pointer_a);
// 숫자를 원시 pointer로, 다시 f32로 변환하는 것 역시
// 안전한 일입니다.
let pointer_b = pointer_a as *const f32;
let b = unsafe {
// 이건 unsafe한데,
// 컴파일러에게 우리의 pointer가 유효한 f32라고 가정하고
// 그 값을 변수 b로 역참조 하라고 하고 있기 때문입니다.
// Rust는 이런 가정이 참인지 검증할 방법이 없습니다.
*pointer_b
};
println!("맹세하건대 이건 파이다! {}", b);
}
-> 데이터 메모리 주소: 140725241194940
맹세하건대 이건 파이다! 3.1415익숙한 친구들
Vec<T>- 바이트들의 메모리 영역을 소유하는 smart pointer
- Rust 컴파일러는 이 바이트들에 뭐가 존재하는지 모름
- smart pointer
- 관리하는 메모리 영역에서 내용물을 꺼내기 위해 자기가 뭘 의미하는지 해석
- 데이터 구조가 그 바이트들 내 어디에서 시작하고 끝나는지 추적
- raw pointer를 데이터 구조로, 또 쓰기 편한 멋지고 깔끔한 인터페이스로 역참조
String- 바이트들의 메모리 영역을 추적
- 쓰여지는 내용물이 언제나 유효한
utf-8이도록 프로그램적으로 제한 - 메모리 영역을
&str자료형으로 역참조할 수 있도록 도와줌
- 이 데이터 구조들 둘 다, 자기 할 일을 하기 위해 raw pointer에 대한 unsafe한 역참조를 사용
use std::alloc::{alloc, Layout};
use std::ops::Deref;
struct Pie {
secret_recipe: usize,
}
impl Pie {
fn new() -> Self {
// 4 바이트를 요청해 봅시다
let layout = Layout::from_size_align(4, 1).unwrap();
unsafe {
// 메모리 위치를 숫자로 할당하고 저장합니다
let ptr = alloc(layout) as *mut u8;
// pointer 연산을 사용해 u8 값 몇 개를 메모리에 써봅시다
ptr.write(86);
ptr.add(1).write(14);
ptr.add(2).write(73);
ptr.add(3).write(64);
Pie {
secret_recipe: ptr as usize,
}
}
}
}
impl Deref for Pie {
type Target = f32;
fn deref(&self) -> &f32 {
// secret_recipe pointer를 f32 raw pointer로 변환합니다
let pointer = self.secret_recipe as *const f32;
// 역참조 하여 &f32 값으로 리턴합니다
unsafe { &*pointer }
}
}
fn main() {
let p = Pie::new();
// Pie struct의 smart pointer를 역참조 하여
// "파이를 만듭니다"
println!("{:?}", *p);
}
-> 3.1415힙에 할당된 메모리
Box는 데이터를 stack에서 heap으로 옮길 수 있게 해주는 smart pointer- 이를 역참조하면 마치 원래 자료형이었던 것처럼 heap에 할당된 데이터를 편하게 쓸 수 있음
struct Pie;
impl Pie {
fn eat(&self) {
println!("heap에 있으니 더 맛있습니다!")
}
}
fn main() {
let heap_pie = Box::new(Pie);
heap_pie.eat();
}
->heap에 있으니 더 맛있습니다!실패할 수 있는 메인 다시 보기
- Rust 코드에는 많고도 많은 오류 표현 방법이 있지만, 그 중에도 standard library에는 오류를 설명하기 위한 범용 trait인
std::error::Error가 있음 - smart pointer인
Box를 사용하면Box<dyn std::error::Error>를 오류 리턴 시 공통된 자료형으로 사용할 수 있음 - 이는 오류를 heap에 전파하고 특정한 자료형을 몰라도 고수준에서 상호작용할 수 있도록 해주기 때문
- Tour of Rust 초반에
main은 오류를 리턴할 수 있다고 배웠습니다. - 이제 우리는 오류의 데이터 구조가 Rust의 일반적인
Errortrait을 구현하는 한, 프로그램에서 발생할 수 있는 거의 모든 종류의 오류를 설명할 수 있는 자료형을 리턴할 수 있습니다.
use core::fmt::Display;
use std::error::Error;
struct Pie;
#[derive(Debug)]
struct NotFreshError;
impl Display for NotFreshError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "이 파이는 신선하지 않군요!")
}
}
impl Error for NotFreshError {}
impl Pie {
fn eat(&self) -> Result<(), Box<dyn Error>> {
Err(Box::new(NotFreshError))
}
}
fn main() -> Result<(), Box<dyn Error>> {
let heap_pie = Box::new(Pie);
heap_pie.eat()?;
Ok(())
}
->
Standard Error
Compiling playground v0.0.1 (/playground)
Finished dev [unoptimized + debuginfo] target(s) in 0.61s
Running `target/debug/playground`
Error: NotFreshError참조 카운팅
Rc- stack에 있는 데이터를 heap으로 옮겨주는 smart pointer
- heap에 놓인 데이터를 immutable하게 대여하는 기능을 가진 다른
Rcsmart pointer들을 복제할 수 있게 해줌 - smart pointer가 drop 될 때에만 heap에 있는 데이터가 할당 해제
use std::rc::Rc;
struct Pie;
impl Pie {
fn eat(&self) {
println!("heap에 있으니 더 맛있습니다!")
}
}
fn main() {
let heap_pie = Rc::new(Pie);
let heap_pie2 = heap_pie.clone();
let heap_pie3 = heap_pie2.clone();
heap_pie3.eat();
heap_pie2.eat();
heap_pie.eat();
// 모든 참조 카운트 smart pointer가 여기서 drop 됩니다
// heap 데이터인 Pie가 드디어 할당 해제됩니다
}
-> heap에 있으니 더 맛있습니다!
heap에 있으니 더 맛있습니다!
heap에 있으니 더 맛있습니다!접근 공유하기
RefCell- 보통 smart pointer가 보유하는 컨테이너 데이터 구조
- 데이터를 가져오거나 안에 있는 것에 대한 mutable 또는 immutable한 참조를 대여할 수 있게 해줌
- 데이터를 대여할 때, Rust는 런타임에 다음의 메모리 안전 규칙을 적용하여 남용을 방지
- 단 하나의 mutable한 참조 또는 여러개의 immutable한 참조만 허용
- 둘 다는 불가
- 이 규칙을 어기면
RefCell은 panic을 일으킴
use std::cell::RefCell;
struct Pie {
slices: u8,
}
impl Pie {
fn eat(&mut self) {
println!("heap에 있으니 더 맛있습니다!");
self.slices -= 1;
}
}
fn main() {
// RefCell은 런타임에 메모리 안전성을 검증합니다
// 주의: pie_cell은 mut가 아닙니다!
let pie_cell = RefCell::new(Pie { slices: 8 });
{
// 그렇지만 mutable 참조를 대여할 수 있습니다!
let mut mut_ref_pie = pie_cell.borrow_mut();
mut_ref_pie.eat();
mut_ref_pie.eat();
// mut_ref_pie는 scope의 마지막에 drop 됩니다
}
// 이제 mutable 참조가 drop 되고 나면 immutable하게 대여할 수 있습니다
let ref_pie = pie_cell.borrow();
println!("{} 조각 남았습니다", ref_pie.slices);
}
-> heap에 있으니 더 맛있습니다!
heap에 있으니 더 맛있습니다!
6 조각 남았습니다쓰레드 간에 공유하기
- Mutex
- 보통 smart pointer가 보유하는 컨테이너 데이터 구조
- 데이터를 가져오거나 내부 데이터에 대한 mutable 또는 immutable한 참조를 대여할 수 있게 해줌
- 잠긴 대여를 통해 운영체제가 동시에 오직 하나의 CPU만 데이터에 접근 가능
- 원래 쓰레드가 끝날 때까지 다른 쓰레드들을 막음으로써 대여가 남용되는 것을 방지
Mutex는 여러 개의 CPU 쓰레드가 같은 데이터에 접근하는 것을 조율하는 근본적인 부분
- Arc
- 특별한 smart pointer인
Arc도 있는데, 쓰레드-안전성을 가진 참조 카운트 증가 방식을 사용한다는 것을 제외하고는Rc와 동일- 동일한
Mutex에 다수의 참조를 가질 때 종종 사용
- 동일한
- 특별한 smart pointer인
use std::sync::Mutex;
struct Pie;
impl Pie {
fn eat(&self) {
println!("지금은 오직 나만이 파이를 먹는다!");
}
}
fn main() {
let mutex_pie = Mutex::new(Pie);
// 파이에 대한 잠겨있는 immutable한 참조를 빌려봅시다
// lock은 실패할 수도 있기 때문에 그 결과는 unwrap 해야합니다
let ref_pie = mutex_pie.lock().unwrap();
ref_pie.eat();
// 잠긴 참조는 여기서 drop 되며, mutex로 보호되는 값은 다른 이에 의해 쓰일 수 있습니다
}스마트 포인터 조합하기
- smart pointer는 한계가 있는 것처럼 보이지만, 조합해서 사용하면 매우 강력해질 수 있음
Rc<Vec<Foo>>- heap에 있는 immutable한 데이터 구조의 동일한 vector를 대여할 수 있는 복수의 smart pointer를 복제할 수 있게 해줌
Rc<RefCell<Foo>>- 복수의 smart pointer가 동일한
Foostruct를 mutable/immutable하게 대여할 수 있게 해줌
- 복수의 smart pointer가 동일한
Arc<Mutex<Foo>>- 복수의 smart pointer가 임시의 mutable/immutable한 대여를 CPU 쓰레드 독점 방식으로 잠글 수 있게 해줌
- “내부 가변성” 패턴
- Rust의 컴파일 타임 체크와 동일 수준의 안전성으로 런타임의 메모리 사용 규칙을 변경할 수 있는 패턴
- 내부 데이터를 변경하기 위해 immutable한 데이터 유형(복수의 smart pointer가 소유할 수 있음)을 사용
use std::cell::RefCell;
use std::rc::Rc;
struct Pie {
slices: u8,
}
impl Pie {
fn eat_slice(&mut self, name: &str) {
println!("{}가 한 조각 먹었습니다!", name);
self.slices -= 1;
}
}
struct SeaCreature {
name: String,
pie: Rc<RefCell<Pie>>,
}
impl SeaCreature {
fn eat(&self) {
// mutable 대여를 위해 파이에 대한 smart pointer를 사용
let mut p = self.pie.borrow_mut();
// 한 입 먹자!
p.eat_slice(&self.name);
}
}
fn main() {
let pie = Rc::new(RefCell::new(Pie { slices: 8 }));
// ferris와 sarah에겐 파이에 대한 smart pointer의 복제가 주어집니다
let ferris = SeaCreature {
name: String::from("ferris"),
pie: pie.clone(),
};
let sarah = SeaCreature {
name: String::from("sarah"),
pie: pie.clone(),
};
ferris.eat();
sarah.eat();
let p = pie.borrow();
println!("{} 조각 남았습니다", p.slices);
}
-> ferris가 한 조각 먹었습니다!
sarah가 한 조각 먹었습니다!
6 조각 남았습니다8장 - 마무리
9장 - 프로젝트 구성과 구조
모듈
- 크레이트(crate)
- 모든 Rust 프로그램이나 라이브러리(library)
- 모든 crate는 _모듈(module)_의 계층구조로 이루어짐
- 모든 crate에는 최상위(root) module이 있음
- module
- 전역변수, 함수, struct, trait, 또는 다른 module까지도 포함될 수 있음
- Rust에서는 파일과 module 트리 계층구조 간의 1:1 대응은 없음
- module의 트리 구조는 코드로 직접 작성
프로그램 작성하기
- 프로그램은
main.rs라 불리는 파일에 root module을 가짐- 프로그램인 경우, main.rs가 진입점
라이브러리 작성하기
- library는
lib.rs라 불리는 파일에 root module을 가짐- library인 경우, lib.rs가 진입점
다른 모듈과 크레이트 참조하기
- module 내의 항목은 전체 module 경로를 이용해 참조 가능
- 예 :
std::f64::consts::PI
- 예 :
- 더 간단한 방법 : use 키워드 사용
- module에서 쓰고자 하는 특정 항목을 전체 경로를 쓰지 않고도 코드 어디에서든 사용할 수 있음
use std::f64::consts::PI를 쓰면 main 함수에서PI만으로 사용할 수 있음
- std
- 유용한 데이터 구조 및 OS와 상호 작용할 수 있는 함수로 가득한 **표준 라이브러리(standard library)**의 crate
- 커뮤니티 crate
use std::f64::consts::PI;
fn main() {
println!("놀이터에 오신 것을 환영합니다!");
println!("{} 한 조각 먹고 싶군요!", PI);
}
-> 놀이터에 오신 것을 환영합니다!
3.141592653589793 한 조각 먹고 싶군요!여러 개의 항목을 참조하기
- 복수의 항목을 하나의 module 경로로 참조하는 경우 {} 사용
- 예:
use std::f64::consts::{PI,TAU}
- 예:
모듈 작성하기
- Rust에서 module을 선언하는 데에는 두 가지 방법
foo.rs라는 이름의 파일foo라는 이름의 디렉토리에 들어있는 파일mod.rs
모듈 계층구조
- module과 하위모듈(sub-module)
- 한 module은 다른 module에 의존할 수 있음
- 하위 모듈 만들기
- 부모 module에 다음과 같은 코드를 작성
mod foo;foo.rs파일이나foo/mod.rs파일을 찾아 이 scope 내의foomodule안에 그 내용물을 삽입
- 부모 module에 다음과 같은 코드를 작성
인라인 모듈
-
sub-module
- module의 코드 내에 직접 치환(inline)됨
-
inline module의 가장 흔한 용도
- 단위 테스트를 만들 때
// 이 macro는 Rust가 테스트 모드가 아닐 경우 // 이 inline module을 제거합니다. #[cfg(test)] mod tests { // 부모 module에 즉시 접근이 가능하지 않다는 데에 주의하세요. // 반드시 명시적으로 써줘야 합니다. use super::*; ... tests go here ... }
내부 모듈 참조하기
use경로에 사용할 수 있는 몇 가지 키워드crate- root modulesuper- 현재 module의 부모 moduleself- 현재 module
내보내기
pub키워드- 사용하면 module의 구성원들을 접근 가능하게 할 수 있음
- 기본적으로 _module_의 구성원들은 외부에서 접근이 불가능
- 그 자식 module도 접근 불가
- 기본적으로 _crate_의 구성원들도 외부에서 접근이 불가능
- crate의 root module (
lib.rs또는main.rs) pub을 표시하면 구성원들을 접근 가능하게 할 수 있음
- crate의 root module (
구조체 가시성
- 함수와 마찬가지로, structure도 module 외부로 무엇을 노출할 지를
pub을 사용해 선언할 수 있음
// SeaCreature struct는 우리 module 외부에서도 사용 가능합니다
pub struct SeaCreature {
pub animal_type: String,
pub name: String,
pub arms: i32,
pub legs: i32,
// 우리의 무기는 비밀로 남겨둡시다
weapon: String,
}전주곡 (Prelude)
preludemoduleuse로 가져오지도 않았는데 어떻게 어디서나Vec나Box를 쓸 수 있는 이유- Rust의 standard library에서는
std::prelude::*로 내보내기 된 모든 것들이 어디에서든 자동으로 사용 가능Vec와Box가 바로 이런 경우이며, 다른 것들(Option, Copy, 기타 등등)도 마찬가지
여러분만의 Prelude
- standard library의 prelude로 인해, 흔히들 library마다 고유의 prelude module을 만듬
- library 사용을 위해 필요한 가장 흔한 데이터 구조들을 모두 가져오는 시작점으로 사용
- 예:
use my_library::prelude::*
- 예:
- standard library와 달리 프로그램이나 library에서 자동으로 쓸 수 있는 것은 아니지만, library 사용자들이 어디서부터 시작할지 도움을 줄 좋은 습관
- library 사용을 위해 필요한 가장 흔한 데이터 구조들을 모두 가져오는 시작점으로 사용
9장 - 마무리
- 참고하면 좋은 자료 : rust 사용 시 가이드라인
Toy Project
- Rust Backend를 사용한 inference server (Image Denoising)
- Image input → Pre Processing → GPU Inference → Post Processing → image output
- GPU Inference : ONNX Runtime - TensorRT
- 할 수 있는 만큼 최적화, 그리고 속도 check
- Actix
- Rust OpenCV
- Rust ONNX Runtime
- https://docs.rs/onnxruntime/latest/onnxruntime/
- cuda 가능 할 것인가
- 위 구현 완료 후, 여유가 된다면 +@ 계획 및 진행
- Image input → Pre Processing → GPU Inference → Post Processing → image output
추가 자료 조사 - Smart Pointer
c/c++의 포인터처럼 작동하지만 추가적인 메타 데이터와 기능들을 가지고 있는 “데이터 구조”
스마트 포인터를 사용하여 구현한 대표적인 자료형
- String
- Vec
Rust의 스마트 포인터는 Rust의 소유권 시스템의 기본 요소이며 메모리를 효율적으로 관리하는 데 사용 됨
- 스마트 포인터는 변수의 수명을 관리하는 데 사용되는 데이터 유형
- 데이터가 더 이상 필요하지 않을 때 자동으로 메모리를 해제
- 복잡한 메모리 상황을 관리하고 널 포인터 참조와 메모리 누수와 같은 일반적인 버그를 방지하는 데 유용
Rust의 세 가지 내장 스마트 포인터 유형
-
Box
- 소유권 및 힙에 대한 할당을 제공, 힙에 메모리를 할당하고 거기에 값을 저장하는 데 사용
fn main() { // i32형 데이터를 힙에 할당! 스택에는 이 힙 메모리에 대한 주소 값이 할당 됨 let b = Box::new(5); println!("b = {}", b); // 주의깊게 볼 점 : 'b'는 i32 type이 아닌 Box형 type } -
Rc
- 동일한 값에 대한 여러 소유자를 허용, 코드의 여러 부분 간에 값 소유권을 공유 하려는 경우 사용
-
RefCell
- 내부 가변성을 제공하여 그렇지 않은 경우 값의 가변 대여를 허용
참조자와 스마트 포인터 간의 추가적인 차이점은?
- 참조자 : 데이터를 오직 빌리기만 하는 포인터
- 스마트 포인터 : (많은 경우)그들이 가리키고 있는 데이터를 소유
스마트 포인터의 구현
-
Deref와Drop트레잇을 구현 -
Deref
-
스마트 포인터 구조체의 인스턴스가 참조자처럼 동작하도록 하여 참조자나 스마트 포인터 둘 중 하나와 함께 작동하는 코드를 작성하게 해 줌
-
우리가 (곱하기 혹은 글롭 연산자와는 반대 측에 있는) 역참조 연산자 (dereference operator)
*의 동작을 커스터마이징 하는 것을 허용 -
기존 역참조 vs Deref
// 기본적인 역참조 예시 fn main() { let x = 5; let y = &x; assert_eq!(5, *y); } // Box로 힙에 할당한 데이터에 대한 역참조 예시 fn main() { let x = 5; let y = Box::new(x); assert_eq!(5, x); assert_eq!(5, *y); } -
예시로 이해해보기
// Box와 유사한 trait 구현 use std::ops::Deref; struct MyBox<T>(T); // new 메서드 구현 impl<T> MyBox<T> { fn new(x: T) -> MyBox<T> { MyBox(x) } } // deref 메서드 구현 impl<T> Deref for MyBox<T> { type Target = T; fn deref(&self) -> &T { // MyBox 타입은 T 타입의 하나의 요소를 가진 튜플 구조체 // 아래 코드를 통해 자신이 가지고 있는 데이터를 리턴 &self.0 } } fn main() { let x = 5; let y = MyBox::new(x); assert_eq!(5, x); // 위에서 Deref를 구현했으므로 러스트는 실제로 "*(y.deref())"를 실행해 줌 assert_eq!(5, *y); }
-
-
Drop
-
스마트 포인터의 인스턴스가 스코프 밖으로 벗어 났을 때 실행되는 코드를 커스터마이징 가능하도록 해 줌
-
특정한 코드는 파일이나 네트워크 연결 같은 자원을 해제하는 데에 사용될 수 있음. 일종의 Dispose
-
예시로 이해하기
// 인스턴스가 스코프 밖으로 벗어났을 때 Dropping CustomSmartPointer!를 출력 // 하는 커스텀 기능만을 갖춘 CustomSmartPointer 구조체 // 구조 정의 struct CustomSmartPointer { data: String, } // Drop 구현 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은 Rust상에서 자동적으로 호출되는 메서드이므로, 우리가 직접 호출 할 수 없음
error[E0040]: explicit use of destructor method
⇒ src/main.rs:14:7
|
14 | c.drop();
| ^^^^ explicit destructor calls not allowed -
만약 직접 호출할 필요가 있다면 std::mem::drop을 가져와서 구현해야 함
-
-
-
참조
https://rinthel.github.io/rust-lang-book-ko/ch15-00-smart-pointers.html