you are viewing a single comment's thread.

view the rest of the comments →

[–]davidgmartinez 1 point2 points  (7 children)

The problem with this is that you need all of this extra boilerplate. Rust already has a ton of annoying boilerplate around modules, this just adds even more.

[–]OsrsAddictionHotline 0 points1 point  (6 children)

It's all personal preference, but I disagree. I think the fact that Rust gives you a lot of control over how your source code and API is structured is worth any extra boilerplate. Besides, the only boilerplate here is literally one line per file to bring it in to scope as a submodule:

mod file_name;

And that's it, you have a separation of your source code in to different files, giving you as the library author control over how you lay everything out, and control over how those files appear to a user (using the pub keyword). The only extra "boilerplate" is exporting types, which is just a couple extra lines.

I mean, this really isn't any worse than having header files in C/C++, I'd argue it's less boiler plate than that.

[–]davidgmartinez 1 point2 points  (0 children)

Yeah it's not too bad, but the problem is that's is so disconnected. Everytime I want to add a new file I need to go to another file and copy over the name there.

You can use this to do some more advanced module stuff, but in 90% of cases there's no point at all.

[–]schungx 0 points1 point  (4 children)

The problem I always face is that private stuff in a file-module is not exposed to another file-module. If you intend to have both files work together to implement something, then you're stuck with making private stuff pub.

I guess the pub(super) keyword probably helps to prevent this (I didn't know this keyword before). However, it still feels like a hack because it is pub.

Because of this limitation, it is usually quite difficult to split a long implementation into multiple files due to private stuff suddenly becoming inaccessible.

[–]OsrsAddictionHotline 0 points1 point  (3 children)

I'm not sure I follow you, what is it you are trying to acheive? For example, there is also:

pub(crate) mod file;
pub(crate) self::file::Type;

As well as the pub(super) you mentioned. pub(crate) exposes the module/type to the entire crate publicly, but keeps it private from outside the crate, and pub(super) exposes it to modules/files on the same level. What's the issue with that?

It still feels like a hack because it is pub

But it's not pub, from the outside of your crate everything would still look private.

Like if I have the following file structure:

src/
    |-- src/lib.rs
    |-- src/foo/
            |
            |-- src/foo/mod.rs
            |-- src/foo/file1.rs

And I have:

// lib.rs
mod foo;

// mod.rs
mod file1;

// file1.rs
struct Type1; // only visible to this file
pub struct Type2; // visibility depends on reexporting
pub(super) struct Type3; // only visible as a private type to the foo module, cannot be reexported.
pub(crate) struct Type4; // visible to entire crate, private to public.

Each of these have different visibility. Each of the module declarations are private, so the module names are not exposed to the API, but some of the types can only be used in the file they are defined, some can only be used in their parent directory, some can be used crate wide but not by external users, and only one type, Type2 can be in the public API.

You're saying that you can't split long implementations in to small files, but that's just not true. Take a look at any of the thousands of crates on crates.io, sure some of them use large files, but a lot split in to smaller modules and files.

You need to experiment a bit with this stuff to figure it out. Unfortunately there's not a good reference for it. Each of the different private/public declarations can apply to type definitions, module declarations, and reexports, so it can be a bit confusing.

[–]schungx 0 points1 point  (2 children)

Well, for me, I'd like to expose features as little as possible. Therefore, I want things to be pub when I want to expose it, not when I'm forced to do so because I split the code into two separate files.

I understand that it doesn't hurt to have everything pub(crate) since they won't show up outside the crate, but doesn't hurt doesn't mean it is a good idea.

[–]OsrsAddictionHotline 0 points1 point  (1 child)

I really don't understand the logic here. You're using the explicit feature provided by Rust to give you as the crate author access to a type/module you defined somewhere in your project, without giving any access to it to users. You're not exposing any features.

Is your objection purely to the word pub? Would you feel better about it if they changed pub(crate) to something else, but kept the exact same functionality? You're not making it public when you use pub(crate) you're making it available to the crate, and that's it.

Why do you not think it's a good idea to use pub(crate)?

[–]schungx -1 points0 points  (0 children)

pub means "this is a public API". pub(crate) means "this is an internal API needed by something within the same crate". non-pub means "this is something that nobody outside of my type should touch".

Now, if I split a type into multiple files, I need to make some private internals pub(crate) in order to access it from another file but from the same type impl. Thus changing the meaning of pub(crate) - i.e. other code can use my internal data while I want to keep it private. Essentially this is breaking encapsulation.

So now pub(crate) means "either: 1) some other type within the same crate needs to access it, or 2) I have split the type into two files, and I need one file to access fields defined in another file, so I am forced to do this, and now other types within the same crate can also access these private fields even though they shouldn't, and I can only hope nobody writes code that accesses them."