Подтвердить что ты не робот

Как вы на самом деле используете типы с динамическим размером в Rust?

В теории, Dynamically-Sized Types (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 {

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

4b9b3361

Ответ 1

EDIT обновлен до Rust 1.5

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

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

До DST

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

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

struct Foo { f: [i32; 2] } // [i32; 2] is a fixed-sized vector with 2 i32 elements
struct Foo2<'a> { f: &'a [i32] } // & 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 Foo { f: [i32] } // f is (statically) unsized, so Foo is unsized too

Это верно и для перечислений и кортежей.

С DST

Вы можете объявить struct (/enum/tuple), например, Foo выше, содержащий нестандартный тип. Как было сказано, тип, содержащий нестандартный тип, также будет несинтетическим.

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

пример игровой площадки

? Размерная граница

Итак, так как вы вряд ли создадите новый DST, какие DST полезны для повседневной кодировки Rust? Чаще всего вы можете писать общий код, который работает как по размерным типам, так и по их существующим нестандартным аналогам. Чаще всего это будут Vec/[] slices или String/str. То, как вы выражаете это, связано с ?Sized "bound" (в кавычках, потому что ?Sized в некотором роде противоположно привязке, на самом деле говорит, что T может быть как размерами, так и нестандартными, поэтому он расширяет возможные типы, которые мы могут использовать вместо того, чтобы ограничивать их, как правило, имеют ограничения).

Продуманное примерное время! Скажем, что у нас есть структура FooSized, которая просто обертывает ссылку и простую черту Print, которую мы хотим реализовать для нее.

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

trait Print {
    fn print(&self);
}

Теперь мы хотим определить оболочку impl для всех обернутых 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;
}

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

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

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

Ответ 2

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

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, вам нужно сначала создать файл с фиксированным конкретным типом, а затем (atm) принудить его указатель к форме DST, то есть

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

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

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