rust 中 trait 的本质

62 阅读3分钟

rust 没有像其他编程语言一样提供 interface{} 接口,但是提供了类似用于定于行为的 trait。trait 到底是什么?跟函数有什么不一样?

比如定义一个 Animal:

trait Animal {
    fn show(&self);
    fn grow(&mut self);
    fn eat(self);
}

fn main() {}

转为汇编代码的时候, Animal trait 没有生成相关的代码

添加 Fish 实现


trait Animal {
    fn show(&self);
    fn grow(&mut self);
    fn eat(self);
}

struct Fish;

impl Animal for Fish {
    fn show(&self) {}
    fn grow(&mut self) {}
    fn eat(self) {}
}

fn main() {
    let mut f = Fish;
    f.show();
    f.grow();
    f.eat();
}

再看生成的汇编代码,可以看到生成了三个在 <playground::Fish as playground::Animal> 下的方法

<playground::Fish as playground::Animal>::show:
	movq	%rdi, -8(%rsp)
	retq

<playground::Fish as playground::Animal>::grow:
	movq	%rdi, -8(%rsp)
	retq

<playground::Fish as playground::Animal>::eat:
	retq

playground::main:
	pushq	%rax
	leaq	7(%rsp), %rdi
	callq	<playground::Fish as playground::Animal>::show
	leaq	7(%rsp), %rdi
	callq	<playground::Fish as playground::Animal>::grow
	callq	<playground::Fish as playground::Animal>::eat
	popq	%rax
	retq

main:
	pushq	%rax
	movq	%rsi, %rdx
	movq	__rustc_debug_gdb_scripts_section__@GOTPCREL(%rip), %rax
	movb	(%rax), %al
	movslq	%edi, %rsi
	leaq	playground::main(%rip), %rdi
	xorl	%ecx, %ecx
	callq	std::rt::lang_start
	popq	%rcx
	retq

从 show 方法的定义如下:

<playground::Fish as playground::Animal>::show:
	movq	%rdi, -8(%rsp)
	retq

show 的方法域为 <playground::Fish as playground::Animal>

调用代码为 <playground::Fish as playground::Animal>::show

trait Animal {
    fn show(&self);
    fn grow(&mut self);
    fn eat(self);
}

struct Fish;

impl Animal for Fish {
    fn show(&self) {}
    fn grow(&mut self) {}
    fn eat(self) {}
}

fn main() {
    let mut f = Fish;
    f.show();
    Animal::show(&f); // 通过 Animal::show 方式调用 方法
    f.grow();
    f.eat();
}
playground::main:
	pushq	%rax
	leaq	7(%rsp), %rdi
	callq	<playground::Fish as playground::Animal>::show
	leaq	7(%rsp), %rdi
	callq	<playground::Fish as playground::Animal>::grow
	callq	<playground::Fish as playground::Animal>::eat
	popq	%rax
	retq

main:
	pushq	%rax
	movq	%rsi, %rdx
	movq	__rustc_debug_gdb_scripts_section__@GOTPCREL(%rip), %rax
	movb	(%rax), %al
	movslq	%edi, %rsi
	leaq	playground::main(%rip), %rdi
	xorl	%ecx, %ecx
	callq	std::rt::lang_start
	popq	%rcx
	retq

通过 main 方法的定义还可以知道, 在 rust 中, fn main(){} 只是我们业务定义的 main 方法, 并且 rust 还会生成一个 真正的 main 方法

修改一下 show 的调用方式

trait Animal {
    fn show(&self);
    fn grow(&mut self);
    fn eat(self);
}

struct Fish;

impl Animal for Fish {
    fn show(&self) {}
    fn grow(&mut self) {}
    fn eat(self) {}
}

fn main() {
    let mut f = Fish;
    f.show();
    Animal::show(&f); // 通过 Animal::show 方式调用 方法
    <Fish as Animal>::show(&f); // 通过 Fish 来调用 show 
    Fish::show(&f);
    f.grow();
    f.eat();
}
playground::main:
	pushq	%rax
	leaq	7(%rsp), %rdi
	callq	<playground::Fish as playground::Animal>::show
	leaq	7(%rsp), %rdi
	callq	<playground::Fish as playground::Animal>::show
	leaq	7(%rsp), %rdi
	callq	<playground::Fish as playground::Animal>::show
	leaq	7(%rsp), %rdi
	callq	<playground::Fish as playground::Animal>::show
	leaq	7(%rsp), %rdi
	callq	<playground::Fish as playground::Animal>::grow
	callq	<playground::Fish as playground::Animal>::eat
	popq	%rax
	retq

通过生成的代码可以知道, 这 4 种 show 调用方式生成的代码都是一样的。

trait Animal {
    fn show(&self);
    fn grow(&mut self);
    fn eat(self);
}

struct Fish;

impl Fish {
    // 实现 show 方法
    fn show(&self) {}
}

impl Animal for Fish {
    fn show(&self) {}
    fn grow(&mut self) {}
    fn eat(self) {}
}

fn main() {
    let mut f = Fish;
    f.show();
    Animal::show(&f); // 通过 Animal::show 方式调用 方法
    <Fish as Animal>::show(&f); // 通过 Fish 来调用 show
    Fish::show(&f);
    f.grow();
    f.eat();
}

然后代码的调用方式

# Fish 新增的 show 方法
playground::Fish::show:
	movq	%rdi, -8(%rsp)
	retq
# .... 省略了一部分的代码
playground::main:
	pushq	%rax
	leaq	7(%rsp), %rdi
	callq	playground::Fish::show
	leaq	7(%rsp), %rdi
	callq	<playground::Fish as playground::Animal>::show
	leaq	7(%rsp), %rdi
	callq	<playground::Fish as playground::Animal>::show
	leaq	7(%rsp), %rdi
	callq	playground::Fish::show
	leaq	7(%rsp), %rdi
	callq	<playground::Fish as playground::Animal>::grow
	callq	<playground::Fish as playground::Animal>::eat
	popq	%rax
	retq

对比上述的代码, 调用 show 方法不一样了

f.show() => Fish::show

Animal::show(&f) => <playground::Fish as playground::Animal>::show

<Fish as Animal>::show(&f) => <playground::Fish as playground::Animal>::show

Fish::show(&f) => Fish::show

总结

  1. trait 可以看作是一个定义一些列方法和其他属性的 mod
  2. 实现一个 trait 就相当于为 类型添加 trait 中定义的方法
  3. 如果实现多个同名的方法时,优先在当前的struct 作用域下的方法