你如何在 Rust 中实际使用动态大小的类型? [英] How do you actually use dynamically sized types in Rust?

查看:44
本文介绍了你如何在 Rust 中实际使用动态大小的类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In theory, Dynamically-Sized Types (DST) have landed and we should now be able to use dynamically sized type instances. Practically speaking, I can neither make it work, nor understand the tests around it.

一切似乎都围绕着 Sized? 关键字……但你究竟是如何使用它的?

Everything seems to revolve around the Sized? keyword... but how exactly do you use it?

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

I can put some types together:

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

...but how do I create an instance of HasFoo, which is DST, to have either a Bar or Bar2?

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

Attempting to do so always seems to result in:

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

从广义上讲,我理解您不能拥有动态大小的裸露类型;您只能通过指针与一个接口进行交互,但我不知道该怎么做.

I understand broadly speaking that you can't have a bare dynamically sized type; you can only interface with one through a pointer, but I can't figure out how to do that.

推荐答案

免责声明:这些只是我做的几个实验的结果,结合 阅读 Niko Matsakis 的博客.

Disclaimer: these are just the results of a few experiments I did, combined with reading Niko Matsakis's blog.

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

DSTs are types where the size is not necessarily known at compile time.

[i32] 这样的 slice 或像 IntoIterator 这样的 bare trait 不是有效的对象类型,因为它们没有已知的尺寸.

A slice like [i32] or a bare trait like IntoIterator were not valid object types because they do not have a known size.

结构可能如下所示:

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

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

This was true for enums and tuples too.

您可以像上面的 Foo 一样声明一个结构体(或枚举或元组),其中包含一个未定义大小的类型.包含 unsized 类型的 type 也将 unsized.

You can declare a struct (or enum or tuple) like Foo above, containing an unsized type. A type containing an unsized type will be unsized too.

虽然定义 Foo 很容易,但创建 Foo 的实例仍然很困难,并且可能会发生变化.由于您无法根据定义在技术上创建无大小的类型,因此您必须创建 Foosized 对应物.例如,Foo { f: [1, 2, 3] }, a Foo<[i32;3]>,它有一个静态已知的大小和一些管道,让编译器知道它如何将它强制转换为它的静态无大小对应Foo<[i32]>.从 Rust 1.5 开始,在安全和稳定的 Rust 中执行此操作的方法仍在研究中(这里是 用于 DST 强制转换的 RFC 了解更多信息).

While defining Foo was easy, creating an instance of Foo is still hard and subject to change. Since you can't technically create an unsized type by definition, you have to create a sized counterpart of Foo. For example, Foo { f: [1, 2, 3] }, a Foo<[i32; 3]>, which has a statically known size and code some plumbing to let the compiler know how it can coerce this into its statically unsized counterpart Foo<[i32]>. The way to do this in safe and stable Rust is still being worked on as of Rust 1.5 (here is the RFC for DST coercions for more info).

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

Luckily, defining a new DST is not something you will be likely to do, unless you are creating a new type of smart pointer (like Rc), which should be a rare enough occurrence.

想象一下 Rc 的定义就像我们上面的 Foo 一样.由于它具有从大小到非大小的强制转换的所有管道,因此可用于执行此操作:

Imagine Rc is defined like our Foo above. Since it has all the plumbing to do the coercion from sized to unsized, it can be used to do this:

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

游乐场示例

既然你不太可能创建一个新的 DST,那么 DST 在你的日常 Rust 编码中有什么用?最常见的是,它们让您可以编写适用于大小类型及其现有 未大小类型的通用代码.大多数情况下,这些将是 Vec/[] 切片或 String/str.

Since you're unlikely to create a new DST, what are DSTs useful for in your everyday Rust coding? Most frequently, they let you write generic code that works both on sized types and on their existing unsized counterparts. Most often these will be Vec/[] slices or String/str.

您表达这一点的方式是通过?Sized绑定".?Sized 在某些方面与边界相反;它实际上表示 T 可以是有大小的或无大小的,因此它拓宽了我们可以使用的可能类型,而不是像边界通常那样限制它们.

The way you express this is through the ?Sized "bound". ?Sized is in some ways the opposite of a bound; it actually says that T can be either sized or unsized, so it widens the possible types we can use, instead of restricting them the way bounds typically do.

人为的示例时间!假设我们有一个 FooSized 结构,它只包装了一个引用和一个我们想要为其实现的简单的 Print trait.

Contrived example time! Let's say that we have a FooSized struct that just wraps a reference and a simple Print trait that we want to implement for it.

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

trait Print {
    fn print(&self);
}

我们想为所有实现 Display 的包装 T 定义一个全面的实现.

We want to define a blanket impl for all the wrapped T's that implement Display.

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

让我们努力让它发挥作用:

Let's try to make it work:

// 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(以及一般的 unsized 类型): ?Sized

Eh... this is awkward... Luckily we have a way to generalize the struct to work directly with str (and unsized types in general): ?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();

游乐场

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

For a less contrived (but simple) actual example, you can look at the Borrow trait in the standard library.

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

for ?Sized 语法现已过时.它曾经指代 Self 的类型,声明 `Foo 可以通过 unsized 类型实现,但现在这是默认值.现在可以为未调整大小的类型实现任何特征,即您现在可以拥有:

the for ?Sized syntax is now obsolete. It used to refer to the type of Self, declaring that `Foo can be implemented by an unsized type, but this is now the default. Any trait can now be implemented for an unsized type, i.e. you can now have:

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

如果你不希望你的 trait 可用于 unsized 类型,你可以使用 Sized 绑定:

If you don't want your trait to be implementable for unsized types, you can use the Sized bound:

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

这篇关于你如何在 Rust 中实际使用动态大小的类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆