盒装特质创建背后的机制如何发挥作用? [英] How does the mechanism behind the creation of boxed traits work?
问题描述
我很难理解盒装特征的价值是如何形成的.考虑以下代码:
trait Fooer {
fn foo(&self);
}
impl Fooer for i32 {
fn foo(&self) { println!("Fooer on i32!"); }
}
fn main() {
let a = Box::new(32); // works, creates a Box<i32>
let b = Box::<i32>::new(32); // works, creates a Box<i32>
let c = Box::<Fooer>::new(32); // doesn't work
let d: Box<Fooer> = Box::new(32); // works, creates a Box<Fooer>
let e: Box<Fooer> = Box::<i32>::new(32); // works, creates a Box<Fooer>
}
很明显,变体a和b非常有用.但是,变体c没有,可能是因为new
函数仅采用相同类型的值,而Fooer != i32
以来就没有这种情况. d和e的变体,使我怀疑正在执行某种从Box<i32>
到Box<Fooer>
的自动转换.
所以我的问题是:
- 这里会发生某种转换吗?
- 如果是这样,它背后的机制是什么以及如何起作用? (我也对低级细节感兴趣,即引擎盖下的东西如何表示)
- 是否可以直接从
i32
创建Box<Fooer>
?如果没有:为什么不呢?
但是,变体c却没有,这可能是因为
new
函数仅采用相同类型的值,而Fooer != i32
以来却不是这种情况.
不,这是因为
impl<T> Box<T>
pub fn new(x: T) -> Box<T>
Box<T>
上的大多数方法都允许T: ?Sized
,但是new
是在impl
中定义的,而没有 范围.表示手段,您只能在T
为具有已知大小的类型. dyn Fooer
没有大小,因此根本没有要调用的new
方法.
实际上,该方法不存在.为了装箱,您需要知道它的大小.为了将其传递给函数,您需要知道其大小.为了使变量包含某些内容,它必须具有大小.像dyn Fooer
之类的未定义大小的类型只能存在于胖指针"之后,即指向对象的指针和指向该对象的Fooer
实现的指针.
您如何获得胖指针?您从细指针开始,然后强制.这就是这两行中发生的事情:
let d: Box<Fooer> = Box::new(32); // works, creates a Box<Fooer>
let e: Box<Fooer> = Box::<i32>::new(32); // works, creates a Box<Fooer>
Box::new
返回Box<i32>
,然后将其强制转换为Box<Fooer>
.您可以认为这是一次转换,但Box
不变.编译器所做的全部工作就是在其上附加一个指针,而忘记了其原始类型. rodrigo的答案详细介绍了这种强制的语言级机制.
希望所有这些都能解释为什么答案
是否可以直接从
i32
创建Box<Fooer>
?
是否":i32
必须在框内装箱,然后才能删除其类型.这是您不能写let x: Fooer = 10i32
的相同原因.
相关
I'm having trouble understanding how values of boxed traits come into existence. Consider the following code:
trait Fooer {
fn foo(&self);
}
impl Fooer for i32 {
fn foo(&self) { println!("Fooer on i32!"); }
}
fn main() {
let a = Box::new(32); // works, creates a Box<i32>
let b = Box::<i32>::new(32); // works, creates a Box<i32>
let c = Box::<Fooer>::new(32); // doesn't work
let d: Box<Fooer> = Box::new(32); // works, creates a Box<Fooer>
let e: Box<Fooer> = Box::<i32>::new(32); // works, creates a Box<Fooer>
}
Obviously, variant a and b work, trivially. However, variant c does not, probably because the new
function takes only values of the same type which is not the case since Fooer != i32
. Variant d and e work, which lets me suspect that some kind of automatic conversion from Box<i32>
to Box<Fooer>
is being performed.
So my questions are:
- Does some kind of conversion happen here?
- If so, what the mechanism behind it and how does it work? (I'm also interested in the low level details, i.e. how stuff is represented under the hood)
- Is there a way to create a
Box<Fooer>
directly from ani32
? If not: why not?
However, variant c does not, probably because the
new
function takes only values of the same type which is not the case sinceFooer != i32
.
No, it's because there is no new
function for Box<dyn Fooer>
. In the documentation:
impl<T> Box<T>
pub fn new(x: T) -> Box<T>
Most methods on Box<T>
allow T: ?Sized
, but new
is defined in an impl
without a T: ?Sized
bound. That means you can only call Box::<T>::new
when T
is a type with a known size. dyn Fooer
is unsized, so there simply isn't a new
method to call.
In fact, that method can't exist. In order to box something, you need to know its size. In order to pass it to a function, you need to know its size. In order to even have a variable containing something, it must have a size. Unsized types like dyn Fooer
can only exist behind a "fat pointer", that is, a pointer to the object and a pointer to the implementation of Fooer
for that object.
How do you get a fat pointer? You start with a thin pointer and coerce it. That's what's happening in these two lines:
let d: Box<Fooer> = Box::new(32); // works, creates a Box<Fooer>
let e: Box<Fooer> = Box::<i32>::new(32); // works, creates a Box<Fooer>
Box::new
returns a Box<i32>
, which is then coerced to Box<Fooer>
. You could consider this a conversion, but the Box
isn't changed; all the compiler does is stick an extra pointer on it and forget its original type. rodrigo's answer goes into more detail about the language-level mechanics of this coercion.
Hopefully all of this goes to explain why the answer to
Is there a way to create a
Box<Fooer>
directly from ani32
?
is "no": the i32
has to be boxed before you can erase its type. It's the same reason you can't write let x: Fooer = 10i32
.
Related
- Why can't I write a function with the same type as Box::new?
- Are polymorphic variables allowed?
- How do you actually use dynamically sized types in Rust?
- Why is `let ref a: Trait = Struct` forbidden?
这篇关于盒装特质创建背后的机制如何发挥作用?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!