10. Generic Types, and Lifetimes

코와->코어·2022년 5월 29일
0

제네릭은 구체적인 타입이나 다른 특성들의 추상적인 대역이다.
코드를 컴파일하고 실행할 때 그 안에 무엇이 들어있는지 몰라도 동작하는 제네릭의 원리에 대해 알아볼 것이다.
라이프타임은 컴파일러에게 참조값들이 서로 어떻게 연관되어있는지 알려 주는 다양한 제네릭들이다.

Generic Data types

타입 이름을 <> 안에 쓰고 인자를 써주면 됨

fn largest<T>(list: &[T]) -> T {}

여러 타입을 사용하고 싶으면 두 개 써주면 됨

struct Point<T, U> {
	x: T,
    y: U,
}

struct나 enum에 메소드를 선언하는 법

struct Point<X1, Y1> {
    x: X1,
    y: Y1,
}

impl<X1, Y1> Point<X1, Y1> {
    fn mixup<X2, Y2>(self, other: Point<X2, Y2>) -> Point<X1, Y2> {
        Point {
            x: self.x,
            y: other.y,
        }
    }
}

컴파일러가 알아서 실제 인스턴스로 바꿔주기 때문에 런타임에선 굉장히 빨라짐

Traits: Defining Shared Behavior

trait은 어떤 특정 타입이 가질 수 있고 다른 타입과 공유 가능한 기능이다.
trait 선언하기

pub trait Summary {
	fn summarize(&self) -> String;
    fn summarize_default(&self) -> String {
    	String::from("(Read more...)")
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

impl Summary for Tweet {} // default 사용할 때

이 trait을 구현하는 각 타입이 이 메소드의 바디를 구현해야 함
trait은 외부 타입에는 구현할 수 없음 orphan rule

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
} // 함수에 제네릭 사용하기


//여러 trait 바운드
pub fn notify(item: &(impl Summary + Display)) {}
pub fn notify<T: Summary + Display>(item: &T) {}


// trait 너무 많으면 where안에 쓰도록
fn some_function<T, U>(t: &T, u: &U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{

item은 Summary trait을 구현하는 어떤 타입이든지 가ㅡㅇ

// 해당 trait을 구현하는 타입으로 리턴
fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        retweet: false,
    }
}

Validating References with Lifetimes

&i32        // a reference
&'a i32     // a reference with an explicit lifetime
&'a mut i32 // a mutable reference with an explicit lifetime

the returned reference will be valid as long as both the parameters are valid. This is the relationship between lifetimes of the parameters and the return value.

fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
   if x.len() > y.len() {
       x
   } else {
       y
   }
}

it means that the lifetime of the reference returned by the longest function is the same as the smaller of the lifetimes of the references passed in. These relationships are what we want Rust to use when analyzing this code.

we’re specifying that the borrow checker should reject any values that don’t adhere to these constraints. Note that the longest function doesn’t need to know exactly how long x and y will live, only that some scope can be substituted for 'a that will satisfy this signature.


struct ImportantExcerpt<'a> {
    part: &'a str,
}

fn main() {
    let novel = String::from("Call me Ishmael. Some years ago...");
    let first_sentence = novel.split('.').next().expect("Could not find a '.'");
    let i = ImportantExcerpt {
        part: first_sentence,
    };
}

lifetime elision rules : if your code fits these cases, you don’t need to write the lifetimes explicitly

Lifetimes on function or method parameters are called input lifetimes, and lifetimes on return values are called output lifetimes.

The compiler uses three rules to figure out the lifetimes of the references when there aren’t explicit annotations. The first rule applies to input lifetimes, and the second and third rules apply to output lifetimes. If the compiler gets to the end of the three rules and there are still references for which it can’t figure out lifetimes, the compiler will stop with an error. These rules apply to fn definitions as well as impl blocks.

The first rule is that the compiler assigns a lifetime parameter to each parameter that’s a reference. In other words, a function with one parameter gets one lifetime parameter: fn foo<'a>(x: &'a i32); a function with two parameters gets two separate lifetime parameters: fn foo<'a, 'b>(x: &'a i32, y: &'b i32); and so on.

The second rule is that, if there is exactly one input lifetime parameter, that lifetime is assigned to all output lifetime parameters: fn foo<'a>(x: &'a i32) -> &'a i32.

The third rule is that, if there are multiple input lifetime parameters, but one of them is &self or &mut self because this is a method, the lifetime of self is assigned to all output lifetime parameters. This third rule makes methods much nicer to read and write because fewer symbols are necessary.

profile
풀스택 웹개발자👩‍💻✨️

0개의 댓글