Rust TraitRust Trait 소개간단한 Trait 예제Trait Bound여러 Trait BoundTrait 기본 구현Trait 상속where 절로 Trait Bound 정리하기Trait 구현한 타입을 반환하기Actix Web의 Trait 사용 예시응답 메세지 - Responder trait에러 처리 - ResponseError 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> 첫 번째 예제의 Human
과 Robot
)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 }