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

时间:2014-09-09 09:18:32

标签: rust

理论上,动态大小类型(DST)已经着陆,我们现在应该能够使用动态大小的类型实例。实际上,我既不能使它工作,也不能理解围绕它的测试。

所有内容似乎都围绕着Sized?关键字......但你究竟是如何使用它的呢?

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

// 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

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

<anon>:28:17: 30:4 error: trying to initialise a dynamically sized struct
<anon>:28   let has_foo = &HasFoo {

我广义地说,你不能拥有一个动态大小的裸体类型;你只能通过一个指针与一个接口,但我无法弄清楚如何做到这一点。

4 个答案:

答案 0 :(得分:22)

免责声明:这些只是我做过的一些实验的结果,再加上reading Niko Matsakis's blog

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

在DST之前

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

结构可能如下所示:

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

或者像这样:

// & 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],
}

但不是这样的:

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

对于枚举和元组也是如此。

使用DST

您可以声明一个结构(或枚举或元组),如上面的Foo,包含一个未大小的类型。包含unsized类型的类型也将被取消。

虽然定义Foo很容易,但创建Foo的实例仍然很难并且可能会发生变化。由于您无法在技术上按定义创建未定义类型,因此您必须创建Foo大小对应项。例如,Foo { f: [1, 2, 3] },一个Foo<[i32; 3]>,它具有静态已知的大小和代码一些管道,让编译器知道它如何将其强制转换为其静态未分区对应Foo<[i32]>。从Rust 1.5开始,在安全稳定的Rust中执行此操作的方法仍然有效(此处为RFC for DST coercions以获取更多信息)。

幸运的是,定义一个新的DST不是你可能会做的事情,除非你正在创建一种新类型的智能指针(如Rc),这应该是很少见的。

想象一下,Rc的定义与上面的Foo相同。由于它具有从大小到非大小的强制执行的所有管道,因此可以用它来执行此操作:

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();
}

playground example

?Sized绑定

由于您不太可能创建新的DST,DST对您日常的Rust编码有用吗?最常见的是,它们允许您编写通用代码,这些代码既适用于大小类型,也适用于现有未大小的对应类型。最常见的是Vec / []切片或String / str

你表达这种方式的方式是通过?Sized&#34;绑定&#34;。 ?Sized在某种程度上与约束相反;它实际上说T可以是大小也可以是未大小的,因此它扩展了我们可以使用的可能类型,而不是像通常那样限制它们。

示例时间!让我们说我们有一个FooSized结构,它只包含一个引用和一个我们想要为它实现的简单Print特征。

struct FooSized<'a, T>(&'a T)
where
    T: 'a;

trait Print {
    fn print(&self);
}

我们希望为实施T的所有已包装的Display定义一个覆盖impl。

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

让我们尝试让它发挥作用:

// 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

//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)
    }
}

现在可行:

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

playground

对于一个不那么做作(但很简单)的实际例子,你可以查看标准库中的Borrow特征。

回到你的问题

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

for ?Sized语法现已过时。它曾用于引用Self的类型,声明`Foo可以通过unsized类型实现,但现在这是默认值。现在可以针对未大小的类型实现任何特征,即您现在可以:

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界限:

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

答案 1 :(得分:2)

要修改the example that Paolo Falabella has given,这是使用属性查看它的另一种方法。

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();
}

答案 2 :(得分:0)

目前,要创建一个存储类型已删除HasFoo的{​​{1}},您需要首先创建一个具有固定具体类型的类型,然后将指向它的指针强制转换为DST表单,即

Foo

调用let has_too: &HasFoo<Foo> = &HasFoo { f: Bar }; 然后做你期望的事。

将来,has_foo.f.foo()几乎可以肯定这些DST演员阵容,但目前需要通过明确的类型提示进行强制演绎。

答案 3 :(得分:0)

这是一个基于 huon's answer 的完整示例。重要的技巧是使您想要包含 DST 的类型成为泛型类型,不需要调整泛型的大小(通过 ?Sized)。然后您可以使用 Bar1Bar2 构造一个具体的值,然后立即将其转换。

struct HasFoo<F: ?Sized = dyn Foo>(F);

impl HasFoo<dyn Foo> {
    fn use_it(&self) {
        println!("{}", self.0.foo())
    }
}

fn main() {
    // Could likewise use `&HasFoo` or `Rc<HasFoo>`, etc.
    let ex1: Box<HasFoo> = Box::new(HasFoo(Bar1));
    let ex2: Box<HasFoo> = Box::new(HasFoo(Bar2));

    ex1.use_it();
    ex2.use_it();
}

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

struct Bar1;
impl Foo for Bar1 {
    fn foo(&self) -> u32 {
        9
    }
}

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