Traits

part1 Traits

  • trait 는 함수의 집합이라고 볼 수 있다. 또한 선언된 함수들은 함수들 끼리 서로 호출할 수 있다.

  • 선언된 함수의 집합들은 어떤 데이터 타입에서도 implement 할 수 있다.

part2 Derive

  • 러스트 컴파일러는 #[derive] attribute 라는 선언을 이용해서 trait 을 이용할 수 있다.
  • 또한 직접 #[derive] 말고 trait 을 직접 구현할 수 있다.

종류

  • 비교 traits : Eq, PartialEq, Ord, PartialOrd
  • Clone, T 라는 객체를 &T 객체를 통해 Copy.
  • Copy, Move 대신에 해당 type 을 Copy 한다.
  • Hash, to compute a hash from &T.
  • Default, to create an empty instance of a data type.
  • Debug, to format a value using the {:?} formatter.

part2 Returning Traits with dyn

함수에서 return 타입을 지정할 때에는 명확한 타입이 필요하다. 그렇지 않으면 컴파일 타임에 return 타입에 사이즈를 알 수 없기 때문이다. 그렇다면 trait 타입은 어떻게 리턴할 수 있는가? 박스 타입을 이용하면 가능하다. 박스 타입은 레퍼런스이며 힙에 저장된 메모리이다. 레퍼런스는 사이즈를 알 수 있기 때문에 박스로 감싸면 return 이 가능하다.


use std::fmt::Debug;
trait Human {
    fn get_name(&self) -> &str;
 }

#[derive(Debug)]
struct Tony {
     name: String,
 }

 #[derive(Debug)]
 struct Hee {
     name: String,
 }

 impl Human for Tony {
     fn get_name(&self) -> &str {
         &self.name
     }
 }

 impl Human for Hee {
     fn get_name(&self) -> &str {
         &self.name
     }
 }

 fn get_struct_name<T>(n: T) -> Box<dyn Human>
 where
     T: Human,
 {
     if n.get_name() == "gwano" {
         Box::new(Hee {
             name: "hee".to_string(),
         })
     } else {
         Box::new(Tony {
             name: "gwano".to_string(),
         })
     }
 }


 fn main() {
     let t = Tony {
         name: "gwano".to_string(),
     };
     let h = Hee {
         name: "hee".to_string(),
     };

     println!("{:?}", get_struct_name(t).get_name());
     println!("{:?}", get_struct_name(h).get_name());
 }

part3 Operator Overloading

연산자trait 을 통해 Overloading이 가능하다.

use std::ops::Add;

#[derive(Debug)]
struct Bar;
#[derive(Debug)]
struct Foo;
#[derive(Debug)]
struct FooBar;
#[derive(Debug)]
struct BarFoo;

impl Add<Foo> for Bar {
     type Output = BarFoo;

     fn add(self, rhs: Foo) -> BarFoo {
         BarFoo
     }
 }

 impl Add<Bar> for Foo {
     type Output = FooBar;

     fn add(self, rhs: Bar) -> Self::Output {
         FooBar
     }
 }
 fn main() {
     let t = Bar.add(Foo);

     let tt = Foo + Bar;
     println!("{:?}", tt);
     println!("{:?}", t);
 }

part4 Drop

Drop trait 는 drop 함수만을 가지고 있으며 해당 객체가 scope 에서 벗어날 때 자동으로 실행되는 함수이다. Drop 을 사용하는 이유는 해당 자원을 Free 시키기 위함이다. Box, Vec, String, File, and Process 타입들이 대표적으로 Drop Trait 을 implement 한 것이다. 또한 Drop trait 은 수동적으로도 implement 하는 것이 가능하다.


struct Droppable {
     name : &'static str,
 }

 impl Droppable {
     fn new(name : &'static str) -> Self  {
         Droppable { name }
     }
 }

 impl Drop for Droppable {
     fn drop(&mut self) {
        println!("will drop this name of struct : {:?}", self.name);
     }
 }


 fn main() {
     let a = Droppable::new("A");

     {
         let b = Droppable::new("B");
         {
             let c = Droppable::new("C");
         }
     }

     drop(a); // 직접 드랍하기.
 }

part5 Iterators

iter Trait 를 구현하기 위해서는 next 요소만 구현하면 된다. 또는 arrays 나 ranges 의 경우는 자동으로 구현됨.


struct Fibonacci {
     curr: u32,
     next: u32,
 }

impl Iterator for Fibonacci {
     type Item = u32;

     fn next(&mut self) -> Option<Self::Item> {
         let new_next = self.curr + self.next;

         self.curr = self.next;
         self.next = new_next;

         Some(self.curr)
     }
 }

 fn fibonacci() -> Fibonacci {
     Fibonacci { curr: 0, next: 1 }
 }

 fn main() {
     for  i in fibonacci().take(4) {
         println!("> {}", i);
     }

 }

part6 impl Trait

impl Trait 은 보통 아래 두 가지 방법으로 쓰일 수 있다.

  • 파라미터
  • 리턴 타입

파라미터 타입으로 쓰일 경우


fn parse_csv_documnet<R: std::io::BufRead>(src : R) {}

fn parse_csv_documnet_using_impl(src : impl std::io::BufRead) {}

리턴 타입으로 쓰일 경우


use std::iter;
use std::vec::IntoIter;

// This function combines two `Vec<i32>` and returns an iterator over it.
// Look how complicated its return type is!
 fn combine_vecs_explicit_return_type(
     v: Vec<i32>,
     u: Vec<i32>,
 ) -> iter::Cycle<iter::Chain<IntoIter<i32>, IntoIter<i32>>> {
     v.into_iter().chain(u.into_iter()).cycle()
 }

// This is the exact same function, but its return type uses `impl Trait`.
// Look how much simpler it is!
 fn combine_vecs(
     v: Vec<i32>,
     u: Vec<i32>,
 ) -> impl Iterator<Item=i32> {
     v.into_iter().chain(u.into_iter()).cycle()
 }

// Returns a function that adds `y` to its input
// clouse 는 이름이 없는 콘크리트 타입을 가진다. impl Fn(타입) 으로 지정.
 fn make_adder_function(y: i32) -> impl Fn(i32) -> i32 {
     let closure = move |x: i32| { x + y };
     closure
 }

//you can also use impl Trait to return an iterator that uses map or filter closures. This makes
//using map and filter easier. Because closure types don't have names, you can't write out an
//explicit return type if your function returns iterators with closures. But with impl Trait you
//can do this easily

 fn double_positives<'a>(numbers: &'a Vec<i32>) -> impl Iterator<Item = i32> + 'a {
     numbers
         .iter()
         .filter(|x| x > &&0)
         .map(|x| x * 2)
 }

 fn main() {
     let plus_one = make_adder_function(1);
     assert_eq!(plus_one(2), 3);

     let singles = vec![-3, -2, 2, 3];
     let doubles = double_positives(&singles);
     assert_eq!(doubles.collect::<Vec<i32>>(), vec![4, 6]);
 }

part6 Supertraits

러스트에는 "inheritance" 가 존재하지 않지만 trait 에 또 다른 trait 을 구현할 수 있다.



 trait Person {
     fn name(&self) -> String;
 }

 trait Student: Person {
     fn university(&self) -> String;
 }

 trait Programmer {
     fn fav_language(&self) -> String;
 }

 trait ComSciStudent: Programmer + Student {
     fn git_username(&self) -> String;
 }

 struct A;

 impl Person for A {
     fn name(&self) -> String {
         "gwano".to_string()
     }
 }

 impl Student for A {
     fn university(&self) -> String {
         "kyunghee_uni".to_string()
     }
 }

 impl Programmer for A {
     fn fav_language(&self) -> String {
         "rust".to_string()
     }
 }

 impl ComSciStudent for A {
     fn git_username(&self) -> String {
         "leepl37".to_string()
     }
 }

 fn comp_sci_student_greeting(student: &dyn ComSciStudent) -> String {
     format!(
         "My name is {} and I attend {}. My favorite language is {}. My Git username is {}",
         student.name(),
         student.university(),
         student.fav_language(),
         student.git_username()
     )
 }

 fn main() {
     
     let who = comp_sci_student_greeting(&A);
     println!("{}", who);
 }

part7 Disambiguating overlapping traits

만일 두 개의 trait 을 impl 하였는데 동일한 이름이 존재한다면 어떻게 할 것인가? impl 블락이 다르기 때문에 구현 시에는 문제가 없지만 해당 객체를 호출할 때 문제가 된다. 이럴 땐 full syntax 을 사용한다.


trait UsernameWidget {
    fn get(&self) -> String;
}

trait AgeWidget {
    fn get(&self) -> u8;
}

struct Form {
    username : String,
    age : u8,
}

impl UsernameWidget for Form {

    fn get(&self) -> String {
        self.username.clone()
    }
}

impl AgeWidget for Form {
    fn get(&self) -> u8 {
       self.age 
    }
}


fn main() {
    
    let form: Form = Form {
        username: "rustacean".to_owned(),
        age: 35,
    };
    
    let t = UsernameWidget::get(&form);

}