我有一个具有两个相关功能的特性:
trait WithConstructor: Sized {
fn new_with_param(param: usize) -> Self;
fn new() -> Self {
Self::new_with_param(0)
}
}
为什么第二种方法(new()
)的默认实现迫使我将Sized
绑定在类型上?我认为这是因为堆栈指针操作,但我不确定。
如果编译器需要知道在堆栈上分配内存的大小,
为什么以下示例不需要Sized
T
?
struct SimpleStruct<T> {
field: T,
}
fn main() {
let s = SimpleStruct { field: 0u32 };
}
答案 0 :(得分:43)
正如您可能已经知道的那样,Rust中的类型可以调整大小和不确定大小。顾名思义,Unsized类型没有存储编译器已知的此类型值所需的大小。例如,[u32]
是u32
s的未分级数组;因为没有在任何地方指定元素的数量,编译器不知道它的大小。另一个例子是裸特质对象类型,例如,Display
,当它直接用作类型时:
let x: Display = ...;
在这种情况下,编译器不知道这里实际使用哪种类型,它被擦除,因此它不知道这些类型的值的大小。以上行无效 - 您无法在不知道其大小的情况下创建局部变量(在堆栈上分配足够的字节),并且您无法通过将unsized类型的值作为参数添加到函数中或从一个返回。
可以通过指针使用未分类的类型,但它可以携带其他信息 - 片的可用数据长度(&[u32]
)或指向虚拟表的指针(Box<SomeTrait>
)。因为指针总是具有固定且已知的大小,所以它们可以存储在局部变量中并传递给函数或从函数返回。
鉴于任何具体类型,您总是可以说它是大小还是未大小。但是,对于泛型,出现了一个问题 - 某些类型参数是否大小?
fn generic_fn<T>(x: T) -> T { ... }
如果T
未标注,则此类函数定义不正确,因为您无法直接传递未经过计算的值。如果它的大小,则一切正常。
在Rust中,所有泛型类型参数都默认在任何位置调整大小 - 在函数,结构和特征中。他们有一个隐含的Sized
界限; Sized
是标记大小类型的特征:
fn generic_fn<T: Sized>(x: T) -> T { ... }
这是因为在绝大多数情况下,您希望调整通用参数的大小。但是,有时候,您希望选择退出大小,这可以通过?Sized
绑定来完成:
fn generic_fn<T: ?Sized>(x: &T) -> u32 { ... }
现在generic_fn
可以像generic_fn("abcde")
一样调用,T
将使用str
进行实例化,这是未确定的,但是没关系 - 此功能接受了引用T
,所以没有任何不好的事情发生。
然而,还有另一个地方,大小问题很重要。 Rust中的特征总是针对某种类型实现:
trait A {
fn do_something(&self);
}
struct X;
impl A for X {
fn do_something(&self) {}
}
然而,这仅是为了方便和实用目的所必需的。可以定义traits以始终采用一个类型参数,并且不指定实现trait的类型:
// this is not actual Rust but some Rust-like language
trait A<T> {
fn do_something(t: &T);
}
struct X;
impl A<X> {
fn do_something(t: &X) {}
}
这就是Haskell类型类的工作原理,事实上,在较低级别的Rust中实际上是如何实现特征的。
Rust中的每个特征都有一个隐式类型参数,称为Self
,它指定实现此特征的类型。它总是在特质的主体中可用:
trait A {
fn do_something(t: &Self);
}
这是尺寸问题出现在图片中的地方。 Self
参数是否已调整大小?
事实证明,没有,Self
在Rust中默认不是大小。每个特征在?Sized
上都有一个隐含的Self
界限。这是需要的原因之一,因为有许多特征可以实现未分类的类型并且仍然有效。例如,任何仅包含仅通过引用获取和返回Self
的方法的特征可以针对未大小的类型实现。您可以在RFC 546中了解有关动机的更多信息。
当您仅定义特征及其方法的签名时,大小不是问题。因为这些定义中没有实际的代码,所以编译器不能假设任何东西。但是,当您开始编写使用此特征的通用代码(包括默认方法,因为它们采用隐式Self
参数)时,您应该考虑大小。由于Self
默认情况下未调整大小,因此默认特征方法无法按值返回Self
或将其作为参数值。因此,您需要指定默认情况下必须调整Self
的大小:
trait A: Sized { ... }
或者您可以指定只有在Self
大小的情况下才能调用方法:
trait WithConstructor {
fn new_with_param(param: usize) -> Self;
fn new() -> Self
where
Self: Sized,
{
Self::new_with_param(0)
}
}
答案 1 :(得分:5)
让我们看看如果您使用未大小的类型会发生什么。
new()
将new_with_param(_)
方法的结果移动给调用者。但除非类型大小,否则应移动多少字节?我们根本无法知道。这就是移动语义需要Sized
类型的原因。
注意:各种Box
es旨在为此问题提供运行时服务。