I am working on a Rust module that has a small external interface, but its internal implementation is large and complex.
Is there a sane way to avoid having a huge file with all the code for the module while still exposing a small interface to external module consumers?
AFAIK, Rust does not allow you to use several files for the same module, as for instance Go allows for package directories. It does not matter that the Rust module is defined in a directory, it still expects one single file.
That way, to split code up you are forced to use sub-modules for sub-types or implementation details that you would want to split apart. I understand that should not matter much for compiled code, all overhead of the module organization is removed.
But there is a design problem, if I split things up in modules, those will need to export things so that I can used them from my other modules... but then those "exported internals" can ALSO be used by external consumers, right? Is there a way to avoid exposing those internal interfaces?
But there is a design problem, if I split things up in modules, those will need to export things so that I can used them from my other modules... but then those "exported internals" can ALSO be used by external consumers, right?
No, they can not.
Making a symbol public in a sub-module only makes it available to its immediate parent. That parent module would then have to re-export that symbol via pub use
for the visibility to "bubble up".
mod foo {
mod bar {
pub fn visible_in_foo() {}
pub fn visible_in_root() {}
fn private() {}
}
// Re-export to make visible at the next outer level
pub use bar::visible_in_root;
pub fn foo() {
// We are in the immediate parent, so both `pub` functions are visible
bar::visible_in_foo();
bar::visible_in_root();
// bar::private(); - Won't compile
}
}
// Make it public to consumers of the library, aka. external API
pub use foo::foo;
fn main() {
// Notice that it's just `foo`. Not `foo::bar` or `bar`.
// Re-exporting makes the symbol a part of module `foo`.
foo::visible_in_root();
// foo::visible_in_foo(); - Won't compile
// `foo::foo` does have access to the inner module, though,
// so we can call `foo::bar::visible_in_foo` indirectly here.
foo::foo();
}
Additionally, you can mark symbols as pub(crate)
, which works like pub
, except it prevents the symbol from becoming public outside the crate, even if it were re-exported by all of its parent modules.
Further reading: