Rust Trait 정리

Rust Trait

Rust Trait 소개

  • Trait는 Rust에서 공유되는 기능을 정의하는 방법입니다.
  • 객체 지향 언어에서의 인터페이스와 유사

간단한 Trait 예제

// Speak trait은 say_hello 메소드를 가지고 있습니다. (인터페이스만 정의) trait Speak { fn say_hello(&self); } struct Human; struct Robot; // impl로 trait 내부 동작을 구현 합니다. impl Speak for Human { fn say_hello(&self) { println!("안녕하세요!"); } } impl Speak for Robot { fn say_hello(&self) { println!("로봇 인사!"); } }
 

Trait Bound

  • 함수나 구조체에 특정 trait를 구현한 타입만 허용하고 싶을 때 사용합니다.
  • 예제
fn introduce<T: Speak>(entity: T) { entity.say_hello(); }
→ entity는 T 으로 지정된 Speak trait을 구현(impl) 한 타입을 받을 수 있습니다. (Ex> 첫 번째 예제의 HumanRobot )
Tour of Rust동적 vs 정적 디스패치에서 설명하는 dyn 키워드 (동적 디스패치)와 Trait Bound는 비슷해보이지만 차이점이 있습니다. Rust 포럼의 아래 쓰레드를 참고하세요. - What’s the difference between T: Trait and dyn Trait?
 

여러 Trait Bound

  • 여러 trait를 한 타입에 적용하려면 +를 사용합니다.
  • 예제
trait Walk { fn walk(&self); } impl Walk for Human { fn walk(&self) { println!("사람이 걷습니다!"); } } impl Walk for Robot { fn walk(&self) { println!("로봇이 걷습니다!"); } } fn action<T: Speak + Walk>(entity: T) { entity.say_hello(); entity.walk(); }
→ entiry는 Speak trait과 Walk trait를 모두 구현한 타입을 인자로 받습니다.
 

Trait 기본 구현

  • Trait는 메서드에 기본 구현을 제공할 수 있습니다.
  • 타입은 기본 구현을 재정의 할 수 있습니다.
  • 예제
trait Speak { fn say_hello(&self); fn say_goodbye(&self) { // 이 메소드는 기본 구현을 제공 합니다. println!("안녕히 계세요~"); } } impl Speak for Human { fn say_hello(&self) { println!("안녕하세요!"); } } impl Speak for Robot { fn say_hello(&self) { println!("로봇 인사!"); } fn say_goodbye(&self) { // say_goodbye를 재정의 println!("로봇 잘가!"); } }
 

Trait 상속

  • 하나의 trait는 다른 trait를 상속하여 메서드를 사용할 수 있습니다.
trait Dance { fn dance(&self); } trait Party: Speak + Dance { fn start_party(&self) { self.say_hello(); self.dance(); } }
 

where 절로 Trait Bound 정리하기

  • Trait Bound가 너무 많아지면 가독성을 해침
  • Trait Bound를 함수 정의 뒤의 where 조항에 명시하는 대안을 제공
  • 기존
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
  • where 절 활용
fn some_function<T, U>(t: &T, u: &U) -> i32 where T: Display + Clone, U: Clone + Debug, {
→ T와 U에 바운드된 Trait들을 where 절에 작성하여 가독성을 높임
 

Trait 구현한 타입을 반환하기

fn returns_party() -> impl Party { Human {} // 리턴할 Party trait을 구현한 타입 객체를 정의 합니다. } fn main() { let human = returns_party(); human.start_party(); }
 

Actix Web의 Trait 사용 예시

응답 메세지 - Responder trait

Actix Web은 응답 메세지를 Responder trait을 구현한 객체를 반환합니다. Responder trait 아래와 같이 정의되어 있습니다:
pub trait Responder { type Body: MessageBody + 'static; // Required method fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body>; // Provided method fn customize(self) -> CustomizeResponder<Self> where Self: Sized { ... } }
→ 각 구현체는 respond_to 메소드를 구현해야 합니다.
대표적으로 많이 사용하는 타입은 HttpResponseBuilder 입니다.
#[get("/")] async fn hello() -> impl Responder { HttpResponse::Ok().body("Hello world!") }
아래와 같이 사용자 지정 타입에서 Responder trait을 구현해서 사용 가능 합니다.
use actix_web::{ body::BoxBody, http::header::ContentType, HttpRequest, HttpResponse, Responder, }; use serde::Serialize; #[derive(Serialize)] struct MyResponse { name: &'static str, } // Responder trait 구현 impl Responder for MyResponse { type Body = BoxBody; // 필수로 정의해야 하는 메소드 fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> { let body = serde_json::to_string(&self).unwrap(); // 응답 생성 및 콘텐츠 타입 설정 HttpResponse::Ok() .content_type(ContentType::json()) .body(body) } } async fn index() -> impl Responder { MyResponse { name: "user" } } // '{"name" : "user"}'
 

에러 처리 - ResponseError trait

Actix Web은 웹 핸들러의 에러 처리를 위해 actix_web::error::ResponseError trait을 사용합니다.
ResponseError trait을 구현하는 Result에서 오류를 반환하는 경우, actix-web은 해당 오류를 해당 actix_web::http::StatusCode가 포함된 HTTP 응답으로 렌더링합니다. 기본 내부 서버 오류가 생성됩니다:
pub trait ResponseError { fn error_response(&self) -> Response<Body>; fn status_code(&self) -> StatusCode; }
 
몇 가지 일반적인 오류에 대해 ResponseError 구현을 제공합니다. io::Error로 응답하는 경우 해당 오류는 HttpInternalServerError로 변환됩니다.
use std::io; use actix_files::NamedFile; fn index(_req: HttpRequest) -> io::Result<NamedFile> { Ok(NamedFile::open("static/index.html")?) }
? 연산자로 에러 발생시 io::Error 가 리턴됩니다. Actix Web에서 이것을 HttpInternalServerError 으로 변환합니다.
 
아래와 같이 사용자 지정 에러 타입에서 ResponseError trait을 구현해서 사용 할 수 있습니다:
use actix_web::{ error, get, http::{header::ContentType, StatusCode}, App, HttpResponse, HttpServer }; use derive_more::{Display, Error}; // 사용자 지정 에러 #[derive(Debug, Display, Error)] enum MyError { #[display(fmt = "internal error")] InternalError, #[display(fmt = "bad request")] BadClientData, #[display(fmt = "timeout")] Timeout, } // 사용자 지정 에러 타입에 ResponseError trait 구현 impl error::ResponseError for MyError { fn error_response(&self) -> HttpResponse { HttpResponse::build(self.status_code()) .insert_header(ContentType::html()) .body(self.to_string()) // 각 enum 값에 #[display(...)] 어노테이션으로 지정된 문자열로 설정 } fn status_code(&self) -> StatusCode { // 상태 코드를 설정 합니다. (기존 상태 코드와 동일하게 설정) match *self { MyError::InternalError => StatusCode::INTERNAL_SERVER_ERROR, MyError::BadClientData => StatusCode::BAD_REQUEST, MyError::Timeout => StatusCode::GATEWAY_TIMEOUT, } } } #[get("/")] async fn index() -> Result<&'static str, MyError> { Err(MyError::BadClientData) // MyError 타입의 BadClientData 에러를 반환합니다. } #[actix_web::main] async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() .service(index) }) .bind(("127.0.0.1", 8080))? .run() .await }