Why are private traits in public interfaces disallowed in this setting?


#1

In general I understand why the “private traits in public interfaces” thing is going away, but I’ve run into a case in which it seems like it should be allowed. I’m wondering whether there’s an obvious way to do this differently that I’m missing?

The problem is the following: I’ve defined a data structure in a non-public sub-module. Since it’s in a sub-module, the type itself needs to be public so that code from my top-level module can use it. It can only be parametrized on types that implement a particular trait which is defined in the top-level module, but is not public. Thus, I run into a conundrum:

  • I can’t make the type non-public because I need to be able to access it from the top-level module. Note that this doesn’t break the spirit behind a “private trait in a public interface” - the module it’s in is not public, so nobody outside of my top-level module could see the private trait.
  • I can’t move the trait into the sub-module because other sub-modules and code in the top-level module need to use it.

I’ve reproduced a toy example of this issue here. I was able to get my code to compile by moving the trait bound out of the type definition and into the impl (see here for an equivalent toy example), but as the warning on the code suggests, this will soon be illegal too.


#2

You could make the trait public, defined in a submodule, and then use it in the top-level module.


#3

I suppose I could, but doesn’t feel like I hack? To be fair, I think this question is about half way between “help” and “I think this feature should be changed a bit”.

While I’m on the subject: I think that a more reasonable way to approach this would be to say that it’s only an error if the type could be visible to modules for whom the trait is not visible. I think that would make this case legal and would still preserve the core idea that you don’t want people to be able to see types with parameters with traits that they can’t satisfy.


#4

The new pub(restricted) might change the situation a bit, but the current solution is a cat and mouse game with hidden modules.


#5

“Hidden modules” as in sub-modules that are not public? E.g.:

pub mod foo {
    // hidden module
    mod bar {
    }
}

#6
mod private {
    pub trait Foo {}
}

Makes it public for the crate, but not part of the crate’s public API.


#7

Gotcha. And by “cat and mouse game,” you just mean that it’s’ easy to make a programming mistake that makes a type that you wanted to be public (outside of the parent module) not actually public?


#8

Sorry, I was imprecise. I just meant it’s a bit unusual, since the path to access the item decides whether it’s actually public API of the crate.

Some people dislike it, since when you see a “pub” it’s not clear how much public it is - whether it’s pub in the module or outside of the crate (which is the motivation for pub(restricted) feature that makes it explicit), but in practice it’s workable.

The strategy I use is to make all modules private (so nobody from outside the crate can peek in them) and then add

pub use module::Foo; 

In lib.rs to re-export all things I want to expose as my crate’s public API. So then lib.rs defines the external API, and pub elsewhere the internal API.


#9

Ah, I gotcha. I certainly sympathize with wanting “pub” to never be ambiguous. Out of curiosity, though, how does the re-exporting strategy fix that? That is, if I have:

mod foo {
    mod bar {
        struct Baz;
    }
}

Isn’t it the case that code in foo can’t access Baz?


#10

Right, but it could access pub struct Baz, without Baz being visible outside of the crate. So effectively Baz is still private.

(I never worry about privacy within a crate. If that’s a problem, I’d say your crate is too big and monolithic, and you should split it into multiple crates instead.)