Как вы на самом деле используете динамически изменяемые типы в 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, чтобы иметь либо Bar или же Bar2?

Попытка сделать это всегда, кажется, приводит к:

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

В общем, я понимаю, что у вас не может быть голого динамического типа; Вы можете взаимодействовать только с одним указателем, но я не могу понять, как это сделать.

4 ответа

Решение

Отказ от ответственности: это только результаты нескольких экспериментов, которые я провел в сочетании с чтением блога Нико Мацакиса.

DST - это типы, размер которых не обязательно известен во время компиляции.

До ДСТ

Ломтик как [i32] или голая черта, как IntoIterator не были допустимыми типами объектов, потому что они не имеют известного размера.

Структура может выглядеть так:

// [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 все еще трудно и может быть изменено. Поскольку технически вы не можете создать тип без размера по определению, вы должны создать аналог размера Foo, Например, Foo { f: [1, 2, 3] }, Foo<[i32; 3]>, который имеет статически известный размер и код некоторого слежения, чтобы компилятор знал, как он может привести это в свой статически нестандартный аналог Foo<[i32]>, Способ сделать это в безопасном и стабильном Rust все еще разрабатывается, начиная с Rust 1.5 (здесь приведен RFC для приведений DST для получения дополнительной информации).

К счастью, определение нового 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();
}

пример детской площадки

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

Мы хотим определить общее одеяние для всех завернутых T это реализовать Display,

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

детская площадка

Для менее надуманного (но простого) фактического примера вы можете посмотреть на 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
    }
}

Если вы не хотите, чтобы ваша черта была реализована для нестандартных типов, вы можете использовать Sized обязательность:

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

Чтобы исправить пример, приведенный Паоло Фалабеллой, мы рассмотрим его по-другому с использованием свойства.

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

Вот полный пример, основанный на ответе huon . Важная хитрость заключается в том, чтобы сделать тип, который вы хотите содержать DST, универсальным типом, где размер универсального не требуется (через ?Sized). Затем вы можете построить конкретное значение, используя Bar1или же Bar2а затем сразу конвертировать его.

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

На данный момент, чтобы создать HasFoo хранение стертых типов Foo вам нужно сначала создать один с фиксированным конкретным типом, а затем привести указатель на него к форме DST, то есть

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

призвание has_foo.f.foo() затем делает то, что вы ожидаете.

В будущем эти DST-броски почти наверняка будут возможны с as, но на данный момент приведение с помощью явной подсказки типа не требуется.

Другие вопросы по тегам