Как сделать объект Rust открытым в ящике, но закрытым за его пределами?

У меня есть ящик с большим количеством кода, поэтому я разделил его на несколько файлов/модулей. Тем не менее, некоторые модули имеют внутренние небезопасные вещи (например, raw-указатели), которые мне нужно публиковать для разных модулей, но я не хочу показывать пользователям своего ящика. Как я могу это сделать?

Единственный способ, по которому я могу думать, - это на самом деле иметь мой ящик только как один большой модуль, но тогда нет возможности разбить его на разные файлы, кроме этого решения, который кажется немного взломанным.

Обычно, когда я сталкиваюсь с проблемой реального мира, что простые примеры в документах Rust недостаточно адекватно объясняют, я просто копирую популярный ящик реального мира, например. git2-rs, но это, похоже, эффективно делает все общедоступным, включая исходные указатели.

Ответ 1

Для того, чтобы item был экспортирован из библиотечного ящика, должен быть хотя бы один путь, ведущий к нему, в котором каждый компонент является общедоступным. Это означает, что все, что вам нужно, чтобы сделать объект общедоступным в вашем ящике, но не экспортироваться из ящика (я буду называть это "внутренним" с этого момента, чтобы подражать терминологии С#) заключается в том, чтобы поместить его в частный модуль под корневым ящиком.

Однако это решение является довольно ограничительным. Что делать, если вы хотите иметь модуль с экспортированными функциями и внутренними функциями? Чтобы экспортировать некоторые функции, нам нужно сделать модуль общедоступным, и это означает, что все публичные элементы в этом модуле также будут экспортированы.

Так как Rust 1.18, есть решение, адаптированное к такому сценарию: pub(restricted). Эта функция позволяет указать "как публичный" элемент. Синтаксис довольно гибкий (вы можете сделать элемент видимым для конкретного дерева модулей, а не для всего контейнера), но если вы хотите сохранить его простым, pub(crate) сделает элемент доступным в любом месте ящика, но не к другому ящики (эквивалентные internal в С#).

Например, предположим, что мы хотим иметь модуль util, в котором foo экспортируется (как mycrate::util::foo), bar является внутренним, а baz является приватным для модуля. Код может выглядеть так:

pub mod util {
    pub fn foo() {
        unimplemented!()
    }

    pub(crate) fn bar() {
        unimplemented!()
    }

    fn baz() {
        unimplemented!()
    }
}

Если вы застряли на до-1.18 ржавчины, обходной путь, но это немного неуклюже. Он включает определение всех ваших элементов в частных модулях и реэкспорт только тех, которые вы хотите опубликовать (с pub use) в общедоступных модулях, которые содержат только реэкспорт. Вот как выглядит пример выше:

pub mod util {
    pub use util_impl::foo;
}

mod util_impl {
    pub fn foo() {
        unimplemented!()
    }

    pub fn bar() {
        unimplemented!()
    }

    fn baz() {
        unimplemented!()
    }
}

Это не просто читать и понимать не просто, но не охватывает все ситуации, в которых можно использовать pub. Например, как вы могли бы сделать некоторые поля экспортируемой структуры доступными в других модулях в одном ящике без их экспорта? Единственным вариантом было бы выставить оболочку с одним частным полем, тип которого является структурой, имеющей общедоступные поля; это прекрасно работает, если вы хотите скрыть все поля из других ящиков, но не для того, чтобы выставить некоторые поля и сделать некоторые другие поля внутри одной и той же структуры.

Ответ 2

Как упоминал Фрэнсис Гане, в Rust 1.18 вы можете использовать синтаксис pub(crate). Имеются следующие работы:

pub struct User {
    pub(crate) name: String,
    pub(crate) age: u32
}

При этом поля name и age могут быть доступны из любого места в вашем ящике, но они не подвергаются внешнему миру.