트레잇 (trait)
트레잇은 공통으로 사용할 수 있는 타입, 상수, 메서드를 정의합니다.
trait ExTrait {
type TypeNoDefault; //공통으로 사용할 수 있는 타입 정의. 구현측에서 타입지정 해야함
const CONST_DEFAULT: i32 = 100; //상수값 정의. 구현하는 측에서 재할당 할 수 있음
const CONST_NO_DEFAULT: i32; //상수 정의만 수행. 구현하는 측에서 값 할당 해야 함
fn method_default(&self) {
println!("default method");
} //함수의 본체까지 정의. 구현 측에서 다시 구현 할 수 있음
fn method_without_default(&self); //공통 메서드 정의. 구현하는 측에서 함수 바디 구현해야 함
}
트레잇 구현
trait Car {
fn drive(&self);
}
struct Truck {}
impl Car for Truck{
fn drive(&self) { println!("Truck is driving."); }
}
- 사용은 이런 식으로 합니다. 어떻게 보면 인터페이스와 비슷하기도 합니다.
struct Point {
x:i32, y:i32
}
impl std::ops::Add for Point {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
impl std::ops::Sub for Point {
type Output = Self;
fn sub(self, rhs: Self) -> Self::Output {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
}
}
}
impl Clone for Point {
fn clone(&self) -> Self {
Self { x: self.x.clone(), y: self.y.clone() }
}
}
fn main(){
let p1 = Point { x:2, y:3 }; let p2 = Point { x:4, y:5 };
let p3 = p1.clone() + p2.clone();
let p4 = p1 - p2;
println!("addition=({},{})",p3.x, p3.y);
println!("subtraction=({},{})",p4.x, p4.y);
}
- Add, Sub, Clone 트레잇을 구현하는 방법입니다.
트레잇 자동 구현
#[derive(Clone)]
struct Point {
x:i32, y:i32
}
fn main(){
let p1 = Point { x:2, y:3 }; let p2 = Point { x:4, y:5 };
let p3 = p1.clone();
}
#derive어트리뷰트를 이용하면 쉽게 트레잇을 구현할 수 있습니다.
#[derive(Clone, Copy)]
- 여러 트레잇을 사용하는 경우는 이런 식으로 사용하면 됩니다.
자동 구현이 가능한 트레잇
- 비교관련 트레잇들: Eq, PartialEq, Ord, PartialOrd.
- 복제 : Clone
- 항목값의 복사(이동이 아닌): Copy
- 해시 계산: Hash
- 어떤 타입에 대해 비어있는 인스턴스 생성: Default
{:?}를 사용한 String 생성: Debug
트레잇 바운드
fn print_distance<T: PointOp>(p:&T) {
println!("Distance={}", p.len());
}
- 제네릭 타입을 정의하는 부분에
<T: PointOp> - 제네릭 타입 T인데, PointOp 트레잇을 구현한 것만이라는 의미합니다.
- 이러한 것을 트레잇 바운드라고 부릅니다.
fn add_sub<T>(a:T, b:T) -> (T,T)
where T: std::ops::Add<Output = T> + std::ops::Sub<Output = T> + Copy
{
return (a+b, a-b);
}
where키워드를 이용하면 가독성이 더 좋아집니다.
트레잇 연관타입
type Item;
type Output = Self;
- 트레잇 안에 정의된 타입을 말합니다.
- 트레이트 정의 시점에는 알 수 없기에 "나중에 구현할 때 정해줘" 라는 의미로
type Item;이라는 자리표시자를 만들어둡니다.
정적 바인딩
struct Human;
struct Dog;
trait Moving {
fn run(&self);
}
impl Moving for Human{
fn run(&self) { println!("A human is running."); }
}
impl Moving for Dog {
fn run(&self) { println!("A dog is running")}
}
fn run(x: impl Moving) { // 정적 바인딩!
x.run();
}
/* 제네릭으로 구현한다면 이런 형태
fn run<T: Moving>(x: T) {
x.run();
}
*/
fn main() {
let tom = Human;
let hodu = Dog;
run(tom);
run(hodu);
}
impl Trait기법으로 정적 바인딩- 함수의 파라미터, 함수의 리턴에서 사용 가능합니다.
fn who_moved_there() -> impl Moving {
let jeff = Human;
return jeff;
}
- 이건 함수의 리턴에서 사용하는 모습입니다.
동적 바인딩
fn find_runner(is_human: bool) -> Box<dyn Moving> {
if is_human {
Box::new(Human)
}else {
Box::new(Dog)
}
}
- 함수의 리턴 타입에 대해 어떤 트레잇을 구현한 여러 객체가 가능하도록 하려면
Box<dyn Trait>형태로 작성해야 합니다.
let x = find_runner(true);
run(x); // 에러
- 주의사항이 하나 있습니다.
- 파라미터로
Moving을 받을 수는 있으나, 현재 x의 타입인Box<dyn Moving>을 받을 수가 없습니다.x.run()처럼 Moving 트레잇의 메서드를 직접 호출해야 합니다.
제네릭 (Generic)
구조체, 열거형, 함수, 메서드에서 하나의 코드로 여러 타입을 처리할 수 있게 도와줍니다.
struct Point <T> {
x:T, y:T
}
제네릭 타입은 꺽쇠 안에 있는 어떤 문자로 표시합니다.
- 주로 한 개의 대문자 알파벳으로 표현
- 이 문자는 그냥 동일한 타입임을 나타내는 구분자입니다.
fn main() {
let p1 = Point { x: 2, y: 3 };
let p2 = Point { x: 2.5, y: 3.5 };
println!("p1=({},{})",p1.x,p1.y); //p1=(2,3)
println!("p2=({},{})",p2.x,p2.y); //p2=(2.5,3.5)
}
- 예시입니다. 정수만이 아닌 실수도 사용 가능합니다.
struct Point<T> {
x:T, y:T
}
impl<T> Point<T> {
fn add(&self, rhs:&Point<T>) -> Point<T>
where T: std::ops::Add<Output = T> + Copy
{
let x_val = self.x + rhs.x;
let y_val = self.y + rhs.y;
return Point {x:x_val, y:y_val};
}
}
- 구조체에서의 예시입니다.
use std::fs::File;
fn main() {
let f = File::open("hello.txt");
let f = match f {
Ok(file) => file, //T 타입
Err(error) => { //E 타입
panic!("There was a problem opening the file: {:?}", error)
},
};
}
- 열거형에서의 예시입니다.
라이프타임
fn main(){
let r; // (1)
{
let x = 5; // (2)
r = &x; // (3)
}
println!("r: {}", r); // (4)
}
- (4)에서 에러가 발생하게 됩니다.
- 수명이 다한 변수를 참조하는 상황!
- 이를 '댕글링 참조', 혹은 '댕글링 포인터'라고 합니다.
fn main(){
let x = String::from("123"); // (1)
let y = String::from("45678");
let s = longest(x.as_str(), y.as_str()); // (2)
println!("longest is : {}",s);
}
fn longest(s1:&str, s2:&str) -> &str { // error: 라이프타임 파라미터가 필요
if s1.len() > s2.len() { s1 }
else { s2 }
}
- 이런 상황에서 라이프타임을 지정해야합니다.
- 컴파일 시점에서
s1,s2중에서 뭐가 리턴될지 모르기 때문에 발생하는 문제입니다.
fn longest<'a>(s1:&'a str, s2:&'a str) -> &'a str {
if s1.len() > s2.len() { s1 }
else { s2 }
}
- 이런 식으로 수동으로 라이프타임을 지정하면 에러가 해결됩니다.
&'a i32
&'a mut i32
&'a str
- 이프타임 매개변수의 이름을 어퍼스트로피(')로 시작한다는 점만 따릅니다.
- 일반 제네릭 변수와 구별하기 위함입니다.
스마트 포인터
스마트 포인터의 구조체는 Deref와 Drop 트레잇을 구현합니다.
Deref는*x와 같이 스마트 포인터 객체에*가 붙여졌을 때의 동작을 정의Drop은 스마트 포인터 객체가 스코프를 벗어날 때 자동으로 수행 (스마트 포인터 객체가 사용하고 있는 힙 메모리를 해제하기 위함)
암묵적 "강제 역참조"
함수나 메서드의 파라미터로 참조형 변수가 전달되면 "강제로 deref가 수행"됩니다.
fn print_array(arr:&[i32]) {
println!("{:?}", arr);
}
fn main(){
let v = vec![1,2,3];
print_array(&v); // [1, 2, 3]
}
- 강제 역참조가 일어나서
&Vec<i32>가&[i32]로 변경됩니다.