【问题标题】:How do you actually use dynamically sized types in Rust?你如何在 Rust 中实际使用动态大小的类型?
【发布时间】:2014-09-09 09:18:32
【问题描述】:

理论上,动态大小的类型 (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 的实例,即夏令时,以拥有BarBar2

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

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

我广义上理解你不能有一个简单的动态大小的类型;您只能通过指针与其中一个接口,但我不知道该怎么做。

【问题讨论】:

  • 您应该包含与您发布的任何错误消息相对应的代码。
  • 你确定夏令时真的登陆了吗?我想在这种情况下会发布适当的公告。而DST issue 还没有关闭。
  • @VladimirMatveev:IIRC,它的实现是不完整的,并且在功能门之后。
  • 我在测试中没有看到功能门,DST 似乎有点工作,但还没有像它们当前未完成状态那样有用(如@VladimirMatveev 的链接中所述假如)。使用示例见is.gd/4IEW99

标签: rust


【解决方案1】:

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

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

夏令时之前

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

结构可能如下所示:

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

枚举和元组也是如此。

使用夏令时

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

虽然定义Foo 很容易,但创建Foo 的实例 仍然很困难,并且可能会发生变化。由于从技术上讲,您无法根据定义创建未调整大小的类型,因此您必须创建 Foosized 对应项。例如,Foo { f: [1, 2, 3] },一个Foo&lt;[i32; 3]&gt;,它有一个静态已知的大小和一些代码,让编译器知道它如何将它强制转换为它的静态无大小对应Foo&lt;[i32]&gt;。从 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“绑定”。 ?Sized 在某些方面与界限相反;它实际上是说T 可以调整大小或不调整大小,因此它扩大了我们可以使用的可能类型,而不是像通常的边界那样限制它们。

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

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

trait Print {
    fn print(&self);
}

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

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 可以由一个未定义大小的类型实现,但现在这是默认值。现在可以为未调整大小的类型实现任何特征,即您现在可以拥有:

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 可用于未调整大小的类型,您可以使用 Sized 绑定:

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

【讨论】:

  • 嗨@Paolo,谢谢你的例子,有一点我跟不上// Does not compile. "hello" is a &amp;'static str, so self print is str // (which is not sized) let h_s = FooSized("hello"); 因为FooSized 被定义为:struct FooSized(&'a T) 其中 T:'a;我们有Tstr,因此 FooSized 将持有对str 的引用(在这种情况下是&amp;'a str),并且它具有指针的大小,那么为什么编译器会抱怨它是 unsized 呢?
  • @RyanLe 因为T 没有大小(正如你所说,它是str)。在 DST 之前,您无法表达该界限,因为 str 本身不能用作类型。即使在夏令时之后你也不能直接使用str,它总是需要在一个指针后面,所以这不会改变。
  • @PaoloFalabella 为避免混淆,我建议在这里更正您所说的话:“您表达这一点的方式是通过 ?Sized “绑定”。 ?Sized 在某些方面与 a 相反bound;它实际上说 T 可以是有大小的或无大小的,因此它扩大了我们可以使用的可能类型,而不是像 bounds 通常那样限制它们。”并更改“ ...它实际上说 T 可能或可能不会实现 ?Sized 特征,因此它扩大了我们可以使用的可能类型...”
【解决方案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();
}

【讨论】:

    【解决方案3】:

    目前,要创建一个HasFoo 存储一个类型擦除的Foo,您需要先创建一个具有固定具体类型的HasFoo,然后将指向它的指针强制指向 DST 表单,即

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

    调用 has_foo.f.foo() 之后会如你所愿。

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

    【讨论】:

      【解决方案4】:

      这是一个基于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
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2011-06-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-05-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-07-23
        相关资源
        最近更新 更多