如何在Rust中实际使用动态大小类型?

How do you actually use dynamically sized types in Rust?

从理论上讲,动态大小类型(DST)已经着陆,我们现在应该能够使用动态大小的类型实例。 实际上,我既无法使其正常运行,也无法理解其周围的测试。

一切似乎都围绕着Sized?关键字...但是您如何使用它呢?

我可以将一些类型放在一起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Note that this code example predates Rust 1.0
// and is no longer syntactically valid

trait Foo for Sized? {
    fn foo(&self) -> u32;
}

struct Bar;
struct Bar2;

impl Foo for Bar { fn foo(&self) -> u32 { return 9u32; }}
impl Foo for Bar2 { fn foo(&self) -> u32 { return 10u32; }}

struct HasFoo<Sized? X> {
    pub f:X
}

...但是如何创建HasFoo的实例(DST)以具有BarBar2

尝试这样做似乎总是会导致:

1
2
:28:17: 30:4 error: trying to initialise a dynamically sized struct
:28   let has_foo = &HasFoo {

从广义上讲,我不能理解动态大小的类型。 您只能通过一个指针与一个接口交互,但是我不知道如何做到这一点。


免责声明:这些只是我做过的一些实验的结果,结合阅读Niko Matsakis的博客可以得出。

DST是在编译时不一定知道大小的类型。

夏令时之前

[i32]这样的切片或像IntoIterator这样的裸特征不是有效的对象类型,因为它们的大小未知。

一个结构可能看起来像这样:

1
2
3
4
// [i32; 2] is a fixed-sized vector with 2 i32 elements
struct Foo {
    f: [i32; 2],
}

或像这样:

1
2
3
4
5
6
7
8
// & is basically a pointer.
// The compiler always knows the size of a
// pointer on a specific architecture, so whatever
// size the [i32] has, its address (the pointer) is
// a statically-sized type too
struct Foo2<'a> {
    f: &'a [i32],
}

但不是这样的:

1
2
3
4
// f is (statically) unsized, so Foo is unsized too
struct Foo {
    f: [i32],
}

枚举和元组也是如此。

使用DST

您可以像上面的Foo那样声明一个结构(或枚举或元组),其中包含一个无尺寸的类型。包含未调整大小类型的类型也将被调整大小。

虽然定义Foo很容易,但是创建Foo的实例仍然很困难,并且可能会发生变化。由于从技术上讲无法通过定义创建未调整大小的类型,因此必须创建Foo的已调整大小的副本。例如,Foo { f: [1, 2, 3] }(Foo<[i32; 3]>)的大小是静态已知的,并且对某些管道进行了编码,以使编译器知道如何将其强制转换为其静态未调整大小的副本Foo<[i32]>。从Rust 1.5开始,在安全稳定的Rust中执行此操作的方法仍在研究中(有关更多信息,这里是DST强制的RFC)。

幸运的是,除非创建了新型的智能指针(如Rc),否则定义新的DST并不是一件容易的事,这应该很少发生。

想象Rc是像上面的Foo那样定义的。由于它具有从大小到未大小的所有强制性管道,因此可以用来执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use std::rc::Rc;

trait Foo {
    fn foo(&self) {
        println!("foo")
    }
}
struct Bar;

impl Foo for Bar {}

fn main() {
    let data: Rc<Foo> = Rc::new(Bar);
    // we're creating a statically typed version of Bar
    // and coercing it (the :Rc<Foo> on the left-end side)
    // to as unsized bare trait counterpart.
    // Rc<Foo> is a trait object, so it has no statically
    // known size
    data.foo();
}

游乐场的例子

?Sized绑定

由于您不太可能创建新的DST,因此在您的日常Rust编码中,DST有什么用?大多数情况下,它们使您可以编写适用于大小类型及其现有未大小对应对象的通用代码。最常见的是Vec / []切片或String / str

表达方式是通过?Sized" bound"。 ?Sized在某些方面与界限相反;它实际上说T可以是大小,也可以是无大小,因此它扩大了我们可以使用的可能类型,而不是像通常那样限制它们。

人为的例子时间!假设我们有一个FooSized结构,该结构只包装了引用和要为其实现的简单Print特征。

1
2
3
4
5
6
7
struct FooSized<'a, T>(&'a T)
where
    T: 'a;

trait Print {
    fn print(&self);
}

我们要为实现Display的所有包装的T定义一个通用的隐式。

1
2
3
4
5
6
7
8
impl<'a, T> Print for FooSized<'a, T>
where
    T: 'a + fmt::Display,
{
    fn print(&self) {
        println!("{}", self.0)
    }
}

让我们尝试使其工作:

1
2
3
4
5
6
7
8
9
// Does not compile."hello" is a &'static str, so self print is str
// (which is not sized)
let h_s = FooSized("hello");
h_s.print();

// to make it work we need a &&str or a &String
let s ="hello"; // &'static str
let h_s = &s; // & &str
h_s.print(); // now self is a &str

嗯...这很尴尬...幸运的是,我们有一种方法可以推广该结构以直接与str(通常是未调整大小的类型)一起使用:?Sized

1
2
3
4
5
6
7
8
9
10
11
12
13
//same as before, only added the ?Sized bound
struct Foo<'a, T: ?Sized>(&'a T)
where
    T: 'a;

impl<'a, T: ?Sized> Print for Foo<'a, T>
where
    T: 'a + fmt::Display,
{
    fn print(&self) {
        println!("{}", self.0)
    }
}

现在这有效:

1
2
let h = Foo("hello");
h.print();

操场

对于不太人为(但简单)的实际示例,您可以查看标准库中的Borrow特性。

回到您的问题

1
2
3
trait Foo for ?Sized {
    fn foo(&self) -> i32;
}

for ?Sized语法现在已过时。它曾经引用Self的类型,声明`Foo可以由无大小的类型实现,但是现在是默认设置。现在可以为无大小类型实现任何特征,即,您现在可以拥有:

1
2
3
4
5
6
7
8
9
10
trait Foo {
    fn foo(&self) -> i32;
}

//[i32] is unsized, but the compiler does not complain for this impl
impl Foo for [i32] {
    fn foo(&self) -> i32 {
        5
    }
}

如果您不希望自己的特征可用于非大小类型,则可以使用Sized绑定:

1
2
3
4
// now the impl Foo for [i32] is illegal
trait Foo: Sized {
    fn foo(&self) -> i32;
}


为了修改Paolo Falabella给出的示例,这里是使用属性的另一种查看方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Foo<'a, T>
where
    T: 'a + ?Sized,
{
    printable_object: &'a T,
}

impl<'a, T> Print for Foo<'a, T>
where
    T: 'a + ?Sized + fmt::Display,
{
    fn print(&self) {
        println!("{}", self.printable_object);
    }
}

fn main() {
    let h = Foo {
        printable_object:"hello",
    };
    h.print();
}

目前,要创建一个存储有类型擦除的FooHasFoo,您需要首先创建一个具有固定具体类型的HasFoo,然后将其强制转换为DST形式的指针,即

1
let has_too: &HasFoo<Foo> = &HasFoo { f: Bar };

调用has_foo.f.foo()然后会执行您期望的操作。

将来,使用as几乎可以肯定会进行这些DST强制转换,但是目前需要通过显式类型提示进行强制转换。