While working on a Rust library, I encountered this thing I didn't realize before:
A pub type Foo can be accessed by external code even when Foo was not exported in lib.rs.
For example, lib.rs has exported a public function like:
pub fn my_fn() -> Foo
The external user can call my_fn() to get a Foo and even call Foo.foo_func as long as foo_func is pub.
It means the external code can use Foo for quite a bit use cases by Rust's type inference. But this creates an interesting scenario: in cargo doc output, the user can see this my_fn returns a Foo, but they cannot get the doc of Foo as it's not exported.
I guess basically Rust does not force an external public type to be exported in a lib. (Or, should I call such type half-pub type?) But I'm curious if that is a design choice or by accident?
Have you actually tried calling my_fn from an external crate? You should get a compilation failure; and if not, then I believe you have found a bug. While you can define a pub fn like you have, that doesn't mean the function is in fact callable by external code.
Ignore this post. I apologize.
foo crate:
/// Hi.
struct Foo;
/// I'll show up in documentation, but external code
/// won't be able to call me since at least one part of
/// my signature (i.e., the return type) is not accessible.
pub fn not_callable() -> Foo {
Foo
}
bar crate:
/// This won't compile since `bar` does not have access
/// to all parts of the signature of `foo::not_callable`.
fn wont_compile() {
foo::not_callable();
}
You should pay heed to compiler warnings, and I'd even recommend elevating the private_interfaces lint from a warning to a compilation failure (e.g., #![deny(private_interfaces)]).
The reason there is no documentation for Occurrences is that type constructor lacks documentation. It is pub though.
I'm pretty sure this is not a bug but instead just how the language works. This has always been the case and also known for quite a long time. A similar situation happens with traits (you can use as supertrait a pub trait defined in a private module) and it has been used as a "feature" to make public traits that can only be implemented by the defining crate (due to requiring a supertrait that can only be named and thus implemented by that crate).
I know about "sealed" traits—and even sparingly use them—but what's described here seems to be different. I didn't try much, but I was unable to make a pub fn in one crate that returns a private type and have it be callable from an external crate. Can you show an example where this is possible?
Ignore everything I said. I see what @SkiFire13 was talking about. I've only experienced "sealed" traits, but the same "technique" applies to normal types too which makes sense. Sorry for the brain fart.
As @SkiFire13 stated, this is known behavior and can be even useful especially when one wants a trait to be callable but not implementable from external code.
The trick is to define a private mod that has a pub trait and a separate sub-trait of this "sealed" trait that is pub. A library author will then control exactly what types implement the trait. External code cannot implement this trait since it's a sub-trait of an inaccessible trait.
The same thing can be used for types:
/// Prevent external code from "naming" `Foo`.
mod sealed {
/// Foo.
pub struct Foo;
impl Foo {
/// External code can still call me even though they
/// can't name the type I belong to.
pub fn can_call_me(&self) {}
}
}
use sealed::Foo;
/// Perfectly fine to call.
pub fn callable() -> Foo {
Foo
}
If you want to prevent this from happening, you might be able to #![deny(private_interfaces)] or look at the compiler warnings; however if you intentionally opt-into this behavior like above, then the lint won't fire.
You can also simply not make Foopub at all; but at most, make it pub(crate).
I don't have real issues with this behavior. I just felt it's somewhat weird that in cargo doc, we will see such type in a public interface, but won't have its document.
It wasn't meant to be allowed originally, but Rust didn't fix it before 1.0 (it's not easy to remove it without losing flexibility of per-module visibility within a crate, ability to re-export items, and having complex generic types).
There is a #![deny(private_in_public)] lint for this particular case, but there are still some more complex combinations that can create inaccessible types: