Rust 的特征对象如何处理移动实例的方法?
How do Rust's Trait Objects handle methods which moves the instance?
以下是我对 Rust 方法工作原理的基本假设:
foo.method();
其中 method
定义为 method(&self)
,foo
是 Foo,
的实例,与
相同
Foo::method(&foo);
我对 Trait Objects 的理解是一个有两个空指针的结构,一个指向数据,另一个指向函数指针(vtable)
接受一个 Trait 对象并调用该 Trait 对象上的方法的多态函数将编译为查看该方法在 Trait 对象中的偏移量并传入数据指针
但是如果方法移动了实例呢?如果我错了,请纠正我,但要调用虚拟移动方法,该函数必须将存储在 Trait 对象中的实际数据推入堆栈,而不仅仅是数据指针。数据大小显然无法在编译时获知,那么这里发生了什么?这是 VLA 的情况,还是我误解了一步的运作方式?
答案很简单——调用trait对象的自消费方法是不可能的。
关键词是object safety。本质上,这个 属性 结合了两个要求:
- 每个方法都必须通过某种间接方式与
self
一起使用;
- 每个方法都必须可以通过原始类型完全识别(即没有泛型)。
为了更详细地了解这一点,让我们实际尝试编写一些代码并询问编译器的意见。首先,只是试图定义特征:
trait Consumer {
fn consume(self);
}
编译器已经不高兴了:
error[E0277]: the size for values of type `Self` cannot be known at compilation time
--> src/lib.rs:2:16
|
2 | fn consume(self);
| ^^^^ doesn't have a size known at compile-time
|
help: consider further restricting `Self`
|
2 | fn consume(self) where Self: Sized;
| ^^^^^^^^^^^^^^^^^
help: function arguments must have a statically known size, borrowed types always have a known size
|
2 | fn consume(&self);
| ^
OK,我们可以比编译器的建议更保守,加上对trait的限制。然后,为特征对象创建添加一个存根:
trait Consumer where Self: Sized {
fn consume(self);
}
fn main() {
let _: Box<dyn Consumer> = todo!();
}
现在,错误稍微复杂一些:
error[E0038]: the trait `Consumer` cannot be made into an object
--> src/main.rs:6:12
|
6 | let _: Box<dyn Consumer> = todo!();
| ^^^^^^^^^^^^^^^^^ `Consumer` cannot be made into an object
|
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
--> src/main.rs:1:28
|
1 | trait Consumer where Self: Sized {
| -------- ^^^^^ ...because it requires `Self: Sized`
| |
| this trait cannot be made into an object...
然而,有一个解决方法:没有必要限制整个特征 - 只是有问题的方法,正如我们从一开始就被告知的那样。移动 where
子句,如下所示:
trait Consumer {
fn consume(self) where Self: Sized;
}
...编译上面的代码。
现在,使用 这个特征对象怎么样?让我们实现它,例如,用于单位类型,并从 main
:
使用它
trait Consumer {
fn consume(self) where Self: Sized;
}
impl Consumer for () {
fn consume(self) {}
}
fn main() {
let consumer: Box<dyn Consumer> = Box::new(());
consumer.consume();
}
另一个编译器错误!
error: the `consume` method cannot be invoked on a trait object
--> src/main.rs:11:14
|
2 | fn consume(self) where Self: Sized;
| ----- this has a `Sized` requirement
...
11 | consumer.consume();
| ^^^^^^^
同样,我们对方法的限制禁止了编译后毫无意义的代码。
以下是我对 Rust 方法工作原理的基本假设:
foo.method();
其中 method
定义为 method(&self)
,foo
是 Foo,
的实例,与
Foo::method(&foo);
我对 Trait Objects 的理解是一个有两个空指针的结构,一个指向数据,另一个指向函数指针(vtable)
接受一个 Trait 对象并调用该 Trait 对象上的方法的多态函数将编译为查看该方法在 Trait 对象中的偏移量并传入数据指针
但是如果方法移动了实例呢?如果我错了,请纠正我,但要调用虚拟移动方法,该函数必须将存储在 Trait 对象中的实际数据推入堆栈,而不仅仅是数据指针。数据大小显然无法在编译时获知,那么这里发生了什么?这是 VLA 的情况,还是我误解了一步的运作方式?
答案很简单——调用trait对象的自消费方法是不可能的。
关键词是object safety。本质上,这个 属性 结合了两个要求:
- 每个方法都必须通过某种间接方式与
self
一起使用; - 每个方法都必须可以通过原始类型完全识别(即没有泛型)。
为了更详细地了解这一点,让我们实际尝试编写一些代码并询问编译器的意见。首先,只是试图定义特征:
trait Consumer {
fn consume(self);
}
编译器已经不高兴了:
error[E0277]: the size for values of type `Self` cannot be known at compilation time
--> src/lib.rs:2:16
|
2 | fn consume(self);
| ^^^^ doesn't have a size known at compile-time
|
help: consider further restricting `Self`
|
2 | fn consume(self) where Self: Sized;
| ^^^^^^^^^^^^^^^^^
help: function arguments must have a statically known size, borrowed types always have a known size
|
2 | fn consume(&self);
| ^
OK,我们可以比编译器的建议更保守,加上对trait的限制。然后,为特征对象创建添加一个存根:
trait Consumer where Self: Sized {
fn consume(self);
}
fn main() {
let _: Box<dyn Consumer> = todo!();
}
现在,错误稍微复杂一些:
error[E0038]: the trait `Consumer` cannot be made into an object
--> src/main.rs:6:12
|
6 | let _: Box<dyn Consumer> = todo!();
| ^^^^^^^^^^^^^^^^^ `Consumer` cannot be made into an object
|
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
--> src/main.rs:1:28
|
1 | trait Consumer where Self: Sized {
| -------- ^^^^^ ...because it requires `Self: Sized`
| |
| this trait cannot be made into an object...
然而,有一个解决方法:没有必要限制整个特征 - 只是有问题的方法,正如我们从一开始就被告知的那样。移动 where
子句,如下所示:
trait Consumer {
fn consume(self) where Self: Sized;
}
...编译上面的代码。
现在,使用 这个特征对象怎么样?让我们实现它,例如,用于单位类型,并从 main
:
trait Consumer {
fn consume(self) where Self: Sized;
}
impl Consumer for () {
fn consume(self) {}
}
fn main() {
let consumer: Box<dyn Consumer> = Box::new(());
consumer.consume();
}
另一个编译器错误!
error: the `consume` method cannot be invoked on a trait object
--> src/main.rs:11:14
|
2 | fn consume(self) where Self: Sized;
| ----- this has a `Sized` requirement
...
11 | consumer.consume();
| ^^^^^^^
同样,我们对方法的限制禁止了编译后毫无意义的代码。