Перемещение семантики в Rust

Я оборачиваю библиотеку C в Rust, и многие из ее функций принимают параметры в виде указателей на структуры, которые сами часто имеют указатели на другие структуры. В интересах сокращения накладных расходов я хотел бы предоставить возможность кэшировать результаты маршалинга данных Rust в структуры C.

Вот пример того, как библиотека C может ожидать некоторые параметры:

#[repr(C)]
struct Foo {
    x: i32,
    y: f32
}

#[repr(C)]
struct Bar {
    p_foo: *const Foo,
    z: bool
}

И как я себе представляю, как будет выглядеть «кэшированная» версия:

struct Cached {
    foo: Option<Foo>,
    bar: Bar
}

Поле p_foo в bar должно указывать на значение Some в пределах foo или нулевой указатель, если есть None.

Проблема здесь, конечно, в том, что если бы значение Cached нужно было переместить, то прямое memcpy было бы неуместным, а bar.p_foo нужно было бы дополнительно перенаправить. Это было бы легко обеспечить в C++ с его определяемой семантикой перемещения, но предлагает ли Rust решение, кроме «не устанавливать bar.p_foo, пока оно не будет использовано»? Хотя это, безусловно, будет работать таким образом, я не думаю, что эти кэшированные значения будут перемещаться чаще, чем (или даже близко к частоте) их повторного использования, и для настройки этих указатели, особенно если вложенность/цепочка глубокая/длинная. Я также предпочел бы не Box подструктуры в куче.


Чтобы уточнить, вот что я могу написать на C++, что я хотел бы воспроизвести в Rust:

struct Foo {
    int x;
    float y;
};

struct Bar {
    Foo const*pFoo;
    bool z;
};

// bear with me while I conjure up a Maybe for C++
class Cached {
public:
    // would have appropriate copy constructor/assignment

    Cached(Cached &&other) {
        m_foo = other.m_foo;
        m_bar = other.m_bar;

        if(m_foo.isJust()) {
            m_bar.pFoo = &m_foo.value();
        } // else already nullptr
    }

    // similar move assignment

private:
    Maybe<Foo> m_foo;
    Bar m_bar;
};

person jmegaffin    schedule 23.03.2016    source источник
comment
заключается в том, что если значение Cached должно быть перемещено - я чувствую, что вы не предоставляете достаточно информации. Что могло бы сохранить указатель на bar.p_foo, который станет недействительным при его перемещении? Что-то вроде это вообще кажется полезным?   -  person Shepmaster    schedule 24.03.2016
comment
bar.p_foo это указатель, который будет признан недействительным, если объект будет перемещен, поскольку он указывает на поле (часть a) самой структуры. Я поясню, написав эквивалент того, что я хочу на С++.   -  person jmegaffin    schedule 24.03.2016


Ответы (1)


Эквивалентом Rust было бы не использовать необработанные указатели, поскольку необработанные указатели предназначены для реализации наших безопасных структур данных, а не для реализации обычных структур данных.

#[repr(C)]
struct Foo {
    x: i32,
    y: f32
}

#[repr(C)]
struct Bar {
    p_foo: Option<Box<Foo>>,
    z: bool
}

Option<Box<T>> гарантированно будет точно эквивалентен (в битах в памяти) *const T, если T является типом, а не признаком. Единственная разница в том, что его безопасно использовать в Rust.

Таким образом, вам больше не нужна структура Cached, но вы можете напрямую передать объект Bar.


Я также предпочел бы не упаковывать подструктуры в кучу.

Тогда я предлагаю вам не хранить объект Bar, а вместо этого вызывать его в воображении всякий раз, когда вам нужно передать его в C:

#[repr(C)]
struct Foo {
    x: i32,
    y: f32
}

#[repr(C)]
struct Bar<'a> {
    p_foo: Option<&'a Foo>,
    z: bool
}

struct Cached {
    foo: Option<Foo>,
    z: bool,
}

impl Cached {
    fn bar<'a>(&'a self) -> Bar<'a> {
        Bar {
            p_foo: self.foo.as_ref(),
            z: self.z,
        }
    }
}

для настройки этих указателей требуется немного работы, особенно если вложенность/цепочка глубокая/длинная.

Это очень похоже на преждевременную оптимизацию. Не оптимизируйте там, где вы не проводили бенчмаркинг.

person oli_obk    schedule 24.03.2016