추상화
는 성능을 희생해야 한다.
이 개념을 깨고, 추상화가 있어도 성능 손실이 없다
는 원칙을 Zero-Cost Abstraction
이라고 한다.
rust에서는 고수준 코드를 런타임 오버헤드가 거의 없이 사용할 수 있다.
fn generic_function<T>(t: T) -> T
where
T: std::ops::Mul<Output = T> + Copy
{
t * t
}
fn main() {
// i32
let num_i32 = generic_function(9);
// f32
let num_f32 = generic_function(9.0);
println!("{}", num_i32);
println!("{}", num_f32);
}
rust
는 다른 언어와 다르게 컴파일 타임에 모든 제네릭 타입에 대해서 정확한 타입을 가지는 함수를 생성한다고 보면된다.
위 코드를 예시로 보면
fn generic_function_i32(t: i32) -> i32 {
t * t
}
fn generic_function_f32(t: f32) -> f32 {
t * t
}
(함수명이 정확한지는 모름..)
위와 같이 생성한다.
즉, 컴파일 타임은 길어질 수 있으나 런타임 비용은 사라진다고 볼 수 있다.
더 자세한 Monomorphization
에 대해서는 아래 사이트를 참조하면 된다.
https://rustc-dev-guide.rust-lang.org/backend/monomorph.html#monomorphization
### trait bound
fn main() {
let dog = Dog;
let cat = Cat;
animal_speak(dog);
animal_speak(cat);
}
fn animal_speak<T: Speak>(animal: T) {
animal.Speak();
}
trait Speak {
fn Speak(&self);
}
struct Dog;
impl Speak for Dog {
fn Speak(&self) {
println!("멍멍");
}
}
struct Cat;
impl Speak for Cat {
fn Speak(&self) {
println!("야옹");
}
}
Generic
과 마찬가지로 컴파일 타임에 타입이 결정되면서 타입에 맞게 별도의 함수가 생성된다.
Iterator
는 consume되기전까지 아무 동작을 하지 않는다. Lazy Evaluation
가 적용이 된다.
가장 손쉽게 실행할 수 있는 방법은 next()
를 호출하는 것이다.
let nums = vec![1,2,3,4,5];
let iter = nums.iter().map(|x| {
println!("{}을 2배로 만듦", x);
x * 2
});
println!("아직 아무일도 일어나지 않음 iteraotr의 특징");
for val in iter {
println!("최종 값 : {}", val);
}
위 코드를 실행하면 아래와 같이 결과가 나온다.
#output
아직 아무일도 일어나지 않음 iteraotr의 특징
1을 2배로 만듦
최종 값 : 2
2을 2배로 만듦
최종 값 : 4
3을 2배로 만듦
최종 값 : 6
4을 2배로 만듦
최종 값 : 8
5을 2배로 만듦
최종 값 : 10
Iterator
를 다룰 때 크게 2가지로 나뉜다.
1. 설정 단계
map()
, filter()
같은 메서드로 반복자의 동작을 정의Lazy Evaluation
의 장점
rust
는 높은 추상화를 사용하더라도 런타임 코스트가 발생하지 않는다.
그러나 다이나믹 디스패치
를 활용할 경우에는 런타임 코스트가 발생한다.
왜 rust의 성능이 좋은지 구체적으로 알 수 있는 시간이었다.
대략적인 개념에 대해 이해했기에 코드를 작성하며 더 깊은 이해를 하도록 노력해야겠다.