Trait有很多種形式,Scala也有Trait。不過這邊講的是Rust的Trait。Scala的Trait觀念不太一樣。
一開始是看到這篇How are Rust Traits different from Go Interfaces?,後來發現跟C++觀念有點相近,於是一起比較。
Rust trait
trait長得很像Interface,不過方式不太一樣,會在編譯時期檢查資料型態。
1 2 3 4 5 6 7 8 9 10 11
| trait Foo { fn bar(&self); }
impl Foo for int { fn bar(&self) {} } impl Foo for String { fn bar(&self) {} }
fn call_bar<T: Foo>(value: T) { value.bar() }
fn main() { call_bar(1i); call_bar("foo".to_string()); }
|
Rust的作法是Static dispatch,因此在編譯前就能決定如何最佳化了。
翻成C的作法大概就是這樣
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void bar_int(...) { ... } void bar_string(...) { ... }
/* the monomorphised `call_bar` function */ void call_bar_int(int value) { bar_int(value); } void call_bar_string(string value) { bar_string(value); }
int main() { call_bar_int(1); call_bar_string("foo"); // pretend that is the (hypothetical) `string` type, not a `char*` return 1; }
|
Go interface
而Go的Interface可是Runtime的,會在Runtime才決定真正的執行路徑。就類似傳統OOP的Polymophism。
1 2 3 4 5 6 7 8 9 10 11 12 13
| type Foo interface { bar() }
func call_bar(value Foo) { value.bar() }
type X int; type Y string; func (X) bar() {} func (Y) bar() {}
func main() { call_bar(X(1)) call_bar(Y("foo")) }
|
C的作法就是這樣,也就是C++處理VTABLE的手法。不過GO沒有Static dispatch的方式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| /* implementing the interface */ void bar_int(...) { ... } void bar_string(...) { ... }
// the Foo interface type struct Foo { void* data; struct FooVTable* vtable; } struct FooVTable { void (*bar)(void*); }
void call_bar(struct Foo value) { value.vtable.bar(value.data); }
static struct FooVTable int_vtable = { bar_int }; static struct FooVTable string_vtable = { bar_string };
int main() { int* i = malloc(sizeof *i); *i = 1; struct Foo int_data = { i, &int_vtable }; call_bar(int_data);
string* s = malloc(sizeof *s); *s = "foo"; // again, pretend the types work struct Foo string_data = { s, &string_vtable }; call_bar(string_data); }
|
C++
Static Dispathc的方式透過Template來實行。
1 2 3 4 5 6 7 8 9 10 11 12 13
| struct MyInt { void bar() {} }; struct MyString { void bar() {} }; template <typename T> void call_bar(T& obj) { obj.bar(); } int main() { call_bar(MyInt()); call_bar(MyString()); }
|
Dynamic Dispatch的方式就是傳統的OOP。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| struct IFoo { virtual ~IFoo() {} virtual void bar() = 0; }; class MyInt : public IFoo { public: void bar() {} }; class MyString : public IFoo { public: void bar() {} }; void call_bar(const unique_ptr<IFoo> &value) { return value->bar(); } int main() { call_bar(make_unique<MyInt>()); call_bar(make_unique<MyString>()); }
|
C++同時具備兩種Dispatch方法,難怪大家都說C++特別難學,選擇太多之後,反而不知道從核選起。