0%

Comparsion between rust trait / go interface / C++

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++特別難學,選擇太多之後,反而不知道從核選起。