Using only priv, pub, pub(crate)?

I'm trying to develop better Rust "visibility / privacy" habits. I've found I only use private, pub, and pub(crate).

Thus, never using pub(self) or pub(in path).

Is this sane? Is anyone else using these habits, or is pub (crate) considered too crude and not fine grained enough in idiomatic Rust style?

1 Like

I don't even use pub(crate). I try to develop every module, even crate-private ones, so that they have a well-defined interface, outside of which nothing is exposed. It's easier for me to keep abstraction boundaries sound (and my code bug-free) in my mind when dependencies form a strict hierarchy rather than an arbitrary graph.

A great side effect of this approach is that making a module public after the fact usually doesn't require additional thought, coding, verification, tests, etc. It is sufficient to add pub and voilà, a new, correctly-functioning public module is born. (In order for this to work, I also like to document private items with the same scrutiny as public ones.)

5 Likes

I try to put every Struct/Enum in it's own separate *.rs file.

Sometimes, the impls of these Structs/Enums have functions that other modules (in the same crate) need (but should never be called outside the crate), and thus my usage of pub(crate).

I see; that does seem like a legitimate use case. I just don't think it is important to put every user-defined type in its own file.

As far as I can tell, that practice is mainly prevalent in object-oriented languages, but Rust's types are much more self-contained, more modular, and simpler, so I usually find it advantageous to keep closely-related types together in the same file, so that I don't have to context (and file) switch too often while writing or modifying their implementation.

Having many files and/or very deep hierarchies – and thus the need of jumping around across several different files when changing them – tends to annoy me and makes me lose track of what I'm about to do.

(Also, if two types need to be able to rip each others' guts apart, they probably do belong in the same household. :stuck_out_tongue:)

6 Likes

I'm curious -- what Editor/IDE do you use, and what is median lines-of-code in a file for you?

I'm using IntelliJ. As a result, I find:

  • jumping to struct/enum/function to be easy, so high # of files doesn't matter
  • long files seems to make IDE lag (this is opinion; never measured this statistically), so shorter files tend to work better

I'm using Emacs.

Line counts per file, based on my 6 most recent Rust repositories:

  • mean = 290
  • median = 221
  • min = 24
  • max = 1235
1 Like

I've never used pub(in …) or pub(self), and wasn't even close to needing them.

I assume they seemed important for large monoliths like libstd, but regular crates (and workspaces!) never reach this level of module complexity.

2 Likes

I do the same. I use pub(crate) sparingly. But I otherwise have never used any other visibility modifier. I don't think I've ever needed that kind of fine grained visibility. Otherwise, it just seems like extra complexity for little gain unless you're in a case where it's critical to have. (I can't immediately think of any such case though.)

4 Likes

I personally like using pub(self) as a way to explicit that something is private / not public, especially for functions that are usually pub. For instance, in this post, I show a code pattern whereby the "classic" new constructor must not be visible, so I annotate it with
pub(self) // private !
to make it resiliant to future changes.

1 Like