C#과 같은 일반적인 프로그래밍 언어에서는 class, struct(구조체) 안에 property(속성)와 method(메서드)를 정의하여 활용할 수 있다. rust는 구조체를 지원하지만 해당 구조체 안에 속성만을 기술할 수 있지 메서드는 정의할 수 없고 impl 키워드를 사용하여 외부에 정의한다.
impl 키워드와 더불어 사용할 수 있는 trait는 타입에 대해 공통된 동작을 표시한다. 약간의 차이는 있지만 다른 프로그래밍 언어에서 말하는 interface와 비슷한 개념이다. 아래의 소스는 C#에서의 interface 기능을 간략하게 살펴보고 이를 rust를 이용하여 구현하고 비교해 본 것이다.
using System;
namespace ConsoleTest;
internal class Program
{
private static void Main()
{
Test1();
Console.WriteLine("------------------------------");
Test2();
/*
Truck can drive : 1111 : 1111
Truck can drive : 2222 : 1111
Truck can drive : 2222
Sedan can drive : 3333
Sedan can drive : 4444 : 1111
------------------------------
Truck can drive : 1111 : 1111
Truck can drive : 2222 : 2222
Truck can drive : 2222
Sedan can drive : 3333
Sedan can drive : 4444 : 2222
*/
}
콜백은 일반적으로 delegate를 통하여 구현하나 인터페이스를 사용하여 콜백을 구현할 수도 있다. 또한 모델 클래스를 만들 때 클래스에서 지원하지 않는 다중상속을 구현가능하게 해 준다. 아래는 전체 소스이다.
Example Interface(C#)
using System;
namespace ConsoleTest;
internal class Program
{
private static void Main()
{
Test1();
Console.WriteLine("------------------------------");
Test2();
}
private static void Test1()
{
Truck truck = new()
{
Name = "1111"
};
truck.Do(truck);
ICar iCar = new Truck(); // truck 비교
iCar.Name = "2222";
truck.Do(iCar);
Console.WriteLine($"{iCar.Drive()}");
iCar = new Sedan();
iCar.Name = "3333";
Console.WriteLine($"{iCar.Drive()}");
Sedan sedan = new()
{
Name = "4444"
};
truck.Do(sedan);
}
private static void Test2()
{
Truck truck = new()
{
Name = "1111"
};
truck.Do(truck);
ICar iCar = truck; // new Truck() 비교
iCar.Name = "2222";
truck.Do(iCar);
Console.WriteLine($"{iCar.Drive()}");
iCar = new Sedan();
iCar.Name = "3333";
Console.WriteLine($"{iCar.Drive()}");
Sedan sedan = new()
{
Name = "4444"
};
truck.Do(sedan);
}
}
public interface ICar
{
public string Name { get; set; }
public string Drive();
}
public class Truck : ICar
{
public string Name { get; set; } = string.Empty;
public string Drive()
{
return $"Truck can drive : {Name}";
}
public void Do(ICar iCar)
{
Console.WriteLine($"{iCar.Drive()} : {Name} ");
}
}
public class Sedan : ICar
{
public string Name { get; set; } = string.Empty;
public string Drive()
{
return $"Sedan can drive : {Name}";
}
}
Example Impl Trait(rust)
use std::fmt::Debug;
trait Car: Debug {
fn drive(&self, s: &str);
}
#[derive(Debug)]
struct Truck;
impl Car for Truck {
fn drive(&self, s: &str) {
println!("{:?} can drive {}", &self, s);
}
}
#[derive(Debug)]
struct Sedan;
impl Car for Sedan {
fn drive(&self, s: &str) {
println!("{:?} can drive {}", &self, s);
}
}
// fn trait_bound<T: Car + Debug>(car: T) {
// fn trait_bound<T>(car: T) where T: Car, T: Debug
fn trait_bound<T>(car: T, s: &str)
where
T: Car + Debug,
{
println!("T({:?}) can drive {}", car, s);
}
fn drive_car(car: impl Car) {
car.drive("1111");
}
fn get_car(is_sedan: bool) -> Box<dyn Car> {
if is_sedan {
Box::new(Sedan)
} else {
Box::new(Truck)
}
}
fn get_car2(car: impl Car + 'static) -> Box<dyn Car> {
car.drive("2222");
Box::new(car)
}
fn main() {
let truck = Truck {};
Car::drive(&truck, "3333"); // truck.drive();
trait_bound(truck, "4444");
// let sedan = Sedan {};
// Car::drive(&sedan); // sedan.drive();
Car::drive(&Sedan, "5555");
trait_bound(Sedan, "6666");
drive_car(Truck);
let car = get_car(false);
car.drive("7777");
let car2 = get_car2(Sedan);
car2.drive("8888");
println!("Car : {:?}", car2);
}
/*
Truck can drive 3333
T(Truck) can drive 4444
Sedan can drive 5555
T(Sedan) can drive 6666
Truck can drive 1111
Truck can drive 7777
Sedan can drive 2222
Sedan can drive 8888
Car : Sedan
*/
객체지향 언어에서는 추상화와 코드 재사용을 위해 interface 또는 abstract class를 제공하는데 rust에서는 이와 유사한 trait를 제공하여 일부 이를 구현한다. 정적다형성(컴파일 타임 다형성)을 동적 디스패치로 구현하기 위해 Box<dyn Trait> 사용한다.
참고할 만한 강좌(링크)