트레이트(Traits)
트레이트(Traits)
타입들이 구현할 수 있는 추상 인터페이스를 트레이트라고 불러요. 같은 트레이트에 대해 여러 타입이 각자 다른 방식으로 동작을 정의할 수 있게 해주는 장치죠. 이 인터페이스는 연관 항목(associated item)들로 구성되는데, 그 종류는 세 가지예요.
- 함수
- 타입
- 상수
이 세 가지 항목이 모여 트레이트가 제공하는 계약을 이루는 거예요.
출처: Rust Reference
문법
Trait → unsafe? trait IDENTIFIER GenericParams? ( : Bounds? )?
WhereClause? { InnerAttribute* AssociatedItem* }
트레이트 선언은 그것이 위치한 모듈 또는 블록의 타입 네임스페이스에 트레이트를 정의해요. 연관 항목은 각자의 네임스페이스에 트레이트의 멤버로 정의돼요. 연관 타입은 타입 네임스페이스에, 연관 상수와 연관 함수는 값 네임스페이스에 정의돼요.
모든 트레이트는 "이 인터페이스를 구현하는 타입"을 가리키는 암묵적 타입 파라미터 Self를 정의해요. 트레이트는 추가 타입 파라미터도 가질 수 있는데, Self를 포함한 이 타입 파라미터들은 평소처럼 다른 트레이트 등으로 제약될 수 있어요. 트레이트는 특정 타입에 대해 별도의 구현(implementation)을 통해 구현돼요.
트레이트 함수는 함수 본문을 세미콜론으로 대체해서 생략할 수 있어요. 이는 구현이 그 함수를 반드시 정의해야 함을 나타내요. 트레이트 함수가 본문을 정의하면, 그 정의는 그것을 재정의하지 않는 어떤 구현에 대해서도 기본값으로 작동해요. 마찬가지로 연관 상수는 등호와 표현식을 생략해서 구현이 상수 값을 정의해야 함을 나타낼 수 있어요. 연관 타입은 절대 타입을 정의할 수 없고, 타입은 구현에서만 지정할 수 있어요.
#![allow(unused)]
fn main() {
// Examples of associated trait items with and without definitions.
trait Example {
const CONST_NO_DEFAULT: i32;
const CONST_WITH_DEFAULT: i32 = 99;
type TypeNoDefault;
fn method_without_default(&self);
fn method_with_default(&self) {}
}
}
트레이트 함수는 const일 수 없어요.
트레이트 바운드
일반(제네릭) 항목은 타입 파라미터에 대한 바운드로 트레이트를 쓸 수 있어요.
제네릭 트레이트
트레이트에 타입 파라미터를 지정해서 제네릭으로 만들 수 있어요. 이 파라미터들은 트레이트 이름 뒤에, 제네릭 함수에서 쓰는 것과 같은 문법으로 나타나요.
#![allow(unused)]
fn main() {
trait Seq<T> {
fn len(&self) -> u32;
fn elt_at(&self, n: u32) -> T;
fn iter<F>(&self, f: F) where F: Fn(T);
}
}
Dyn 호환성(Dyn compatibility)
dyn 호환 트레이트는 트레이트 객체의 기반 트레이트가 될 수 있어요. 다음 성질을 모두 갖추면 트레이트는 dyn 호환성이 있어요.
- 모든 슈퍼트레이트도 dyn 호환이어야 해요.
Sized가 슈퍼트레이트이면 안 돼요. 다시 말해Self: Sized를 요구하면 안 돼요.- 연관 상수를 가져선 안 돼요.
- 제네릭을 가진 연관 타입을 가져선 안 돼요.
- 모든 연관 함수는 트레이트 객체에서 디스패치 가능하거나 명시적으로 비디스패치 가능이어야 해요.
디스패치 가능한 함수는 다음을 만족해야 해요.
- 타입 파라미터를 가지면 안 되고(수명 파라미터는 허용),
Self를 리시버 타입 외에 사용하면 안 되는 메서드여야 해요. - 리시버가 다음 타입 중 하나여야 해요:
&Self(즉&self),&mut Self(즉&mut self),Box<Self>,Rc<Self>,Arc<Self>,Pin<P>(여기서P는 위 타입 중 하나) - 불투명 반환 타입을 가지면 안 돼요. 즉
async fn이면 안 되고(숨은Future타입이 있으므로), 반환 위치의impl Trait타입(fn example(&self) -> impl Trait)이면 안 돼요. where Self: Sized바운드를 가지면 안 돼요(Self리시버 타입, 즉self는 이를 함의해요).
명시적으로 비디스패치 가능한 함수는 where Self: Sized 바운드가 필요해요(Self 리시버 타입, 즉 self는 이를 함의해요). AsyncFn, AsyncFnMut, AsyncFnOnce 트레이트는 dyn 호환이 아니에요. 이 개념은 예전에 **객체 안전성(object safety)**이라고 불렸어요.
#![allow(unused)]
fn main() {
use std::rc::Rc;
use std::sync::Arc;
use std::pin::Pin;
// Examples of dyn compatible methods.
trait TraitMethods {
fn by_ref(self: &Self) {}
fn by_ref_mut(self: &mut Self) {}
fn by_box(self: Box<Self>) {}
fn by_rc(self: Rc<Self>) {}
fn by_arc(self: Arc<Self>) {}
fn by_pin(self: Pin<&Self>) {}
fn with_lifetime<'a>(self: &'a Self) {}
fn nested_pin(self: Pin<Arc<Self>>) {}
}
struct S;
impl TraitMethods for S {}
let t: Box<dyn TraitMethods> = Box::new(S);
}
#![allow(unused)]
fn main() {
// This trait is dyn compatible, but these methods cannot be dispatched on a trait object.
trait NonDispatchable {
// Non-methods cannot be dispatched.
fn foo() where Self: Sized {}
// Self type isn't known until runtime.
fn returns(&self) -> Self where Self: Sized;
// `other` may be a different concrete type of the receiver.
fn param(&self, other: Self) where Self: Sized {}
// Generics are not compatible with vtables.
fn typed<T>(&self, x: T) where Self: Sized {}
}
struct S;
impl NonDispatchable for S {
fn returns(&self) -> Self where Self: Sized { S }
}
let obj: Box<dyn NonDispatchable> = Box::new(S);
obj.returns(); // ERROR: cannot call with Self return
obj.param(S); // ERROR: cannot call with Self parameter
obj.typed(1); // ERROR: cannot call with generic type
}
#![allow(unused)]
fn main() {
use std::rc::Rc;
// Examples of dyn-incompatible traits.
trait DynIncompatible {
const CONST: i32 = 1; // ERROR: cannot have associated const
fn foo() {} // ERROR: associated function without Sized
fn returns(&self) -> Self; // ERROR: Self in return type
fn typed<T>(&self, x: T) {} // ERROR: has generic type parameters
fn nested(self: Rc<Box<Self>>) {} // ERROR: nested receiver cannot be dispatched on
}
struct S;
impl DynIncompatible for S {
fn returns(&self) -> Self { S }
}
let obj: Box<dyn DynIncompatible> = Box::new(S); // ERROR
}
#![allow(unused)]
fn main() {
// `Self: Sized` traits are dyn-incompatible.
trait TraitWithSize where Self: Sized {}
struct S;
impl TraitWithSize for S {}
let obj: Box<dyn TraitWithSize> = Box::new(S); // ERROR
}
#![allow(unused)]
fn main() {
// Dyn-incompatible if `Self` is a type argument.
trait Super<A> {}
trait WithSelf: Super<Self> where Self: Sized {}
struct S;
impl<A> Super<A> for S {}
impl WithSelf for S {}
let obj: Box<dyn WithSelf> = Box::new(S); // ERROR: cannot use `Self` type parameter
}
슈퍼트레이트(Supertraits)
슈퍼트레이트는 어떤 타입이 특정 트레이트를 구현하려면 반드시 구현돼 있어야 하는 트레이트예요. 나아가 제네릭이나 트레이트 객체가 트레이트로 바운드된 어느 곳에서든, 그 트레이트의 슈퍼트레이트의 연관 항목에도 접근할 수 있어요.
슈퍼트레이트는 트레이트의 Self 타입에 대한 트레이트 바운드로 선언되고, 그 트레이트 바운드에 선언된 트레이트들의 슈퍼트레이트로도 전이적으로 선언돼요. 트레이트가 자기 자신의 슈퍼트레이트가 되는 것은 오류예요. 슈퍼트레이트를 가진 트레이트를 슈퍼트레이트의 **서브트레이트(subtrait)**라고 불러요.
아래 예시는 Shape를 Circle의 슈퍼트레이트로 선언하는 예시예요.
#![allow(unused)]
fn main() {
trait Shape { fn area(&self) -> f64; }
trait Circle: Shape { fn radius(&self) -> f64; }
}
다음은 where 절을 쓴 같은 예시예요.
#![allow(unused)]
fn main() {
trait Shape { fn area(&self) -> f64; }
trait Circle where Self: Shape { fn radius(&self) -> f64; }
}
다음 예시는 Shape의 area 함수를 이용해서 radius에 기본 구현을 주는 예시예요.
#![allow(unused)]
fn main() {
trait Shape { fn area(&self) -> f64; }
trait Circle where Self: Shape {
fn radius(&self) -> f64 {
// A = pi * r^2
// so algebraically,
// r = sqrt(A / pi)
(self.area() / std::f64::consts::PI).sqrt()
}
}
}
다음 예시는 제네릭 파라미터에서 슈퍼트레이트 메서드를 호출해요.
#![allow(unused)]
fn main() {
trait Shape { fn area(&self) -> f64; }
trait Circle: Shape { fn radius(&self) -> f64; }
fn print_area_and_radius<C: Circle>(c: C) {
// Here we call the area method from the supertrait `Shape` of `Circle`.
println!("Area: {}", c.area());
println!("Radius: {}", c.radius());
}
}
마찬가지로 트레이트 객체에서 슈퍼트레이트 메서드를 호출하는 예시예요.
#![allow(unused)]
fn main() {
trait Shape { fn area(&self) -> f64; }
trait Circle: Shape { fn radius(&self) -> f64; }
struct UnitCircle;
impl Shape for UnitCircle { fn area(&self) -> f64 { std::f64::consts::PI } }
impl Circle for UnitCircle { fn radius(&self) -> f64 { 1.0 } }
let circle = UnitCircle;
let circle = Box::new(circle) as Box<dyn Circle>;
let nonsense = circle.radius() * circle.area();
}
안전하지 않은 트레이트(Unsafe traits)
unsafe 키워드로 시작하는 트레이트 항목은 그 트레이트를 구현하는 것이 안전하지 않을 수 있음을 나타내요. 올바르게 구현된 안전하지 않은 트레이트를 사용하는 것은 안전해요. 트레이트 구현도 unsafe 키워드로 시작해야 해요. Sync와 Send는 안전하지 않은 트레이트의 예시예요.
파라미터 패턴
본문이 없는 연관 함수의 파라미터는 IDENTIFIER 또는 _ 와일드카드 패턴, 그리고 SelfParam이 허용하는 형태만 허용해요. mut IDENTIFIER는 현재 허용되지만 더 이상 사용되지 않으며( deprecated) 앞으로 하드 오류가 될 거예요.
#![allow(unused)]
fn main() {
trait T {
fn f1(&self);
fn f2(x: Self, _: i32);
}
}
#![allow(unused)]
fn main() {
trait T {
fn f2(&x: &i32); // ERROR: patterns aren't allowed in functions without bodies
}
}
본문이 있는 연관 함수의 파라미터는 반박 불가능(irrefutable) 패턴만 허용해요.
#![allow(unused)]
fn main() {
trait T {
fn f1((a, b): (i32, i32)) {} // OK: is irrefutable
}
}
#![allow(unused)]
fn main() {
trait T {
fn f1(123: i32) {} // ERROR: pattern is refutable
fn f2(Some(x): Option<i32>) {} // ERROR: pattern is refutable
}
}
2018 에디션 차이점
2018 에디션 이전에는 연관 함수 파라미터의 패턴이 선택적이었어요.
#![allow(unused)]
fn main() {
// 2015 Edition
trait T {
fn f(i32); // OK: parameter identifiers are not required
}
}
2018 에디션부터 패턴은 더 이상 선택적이지 않아요.
또한 2018 에디션 이전에는 본문이 있는 연관 함수의 파라미터가 다음 종류의 패턴으로만 제한됐어요.
IDENTIFIERmut IDENTIFIER_& IDENTIFIER&& IDENTIFIER
#![allow(unused)]
fn main() {
// 2015 Edition
trait T {
fn f1((a, b): (i32, i32)) {} // ERROR: pattern not allowed
}
}
2018 에디션부터는 위에서 설명한 대로 모든 반박 불가능 패턴이 허용돼요.
항목 가시성(Item visibility)
트레이트 항목은 문법적으로 Visibility 주석을 허용하지만, 트레이트가 검증될 때는 거부돼요. 이렇게 함으로써 항목들이 사용되는 다양한 맥락에서 통일된 문법으로 파싱될 수 있어요. 예를 들어 비어 있는 vis 매크로 프래그먼트 지정자를 트레이트 항목에 쓸 수 있는데, 그 매크로 규칙은 가시성이 허용되는 다른 상황에서도 쓰일 수 있어요.
macro_rules! create_method {
($vis:vis $name:ident) => {
$vis fn $name(&self) {}
};
}
trait T1 {
// Empty `vis` is allowed.
create_method! { method_of_t1 }
}
struct S;
impl S {
// Visibility is allowed here.
create_method! { pub method_of_s }
}
impl T1 for S {}
fn main() {
let s = S;
s.method_of_t1();
s.method_of_s();
}