Reduced test case:
struct HostInternal;
mod parser {
use super::HostInternal;
pub fn parse() -> HostInternal {
unimplemented!();
}
}
Output with rustc 1.7.0-nightly (1447ce78f 2016-01-13)
a.rs:5:23: 5:35 error: private type in public interface [E0446]
a.rs:5 pub fn parse() -> HostInternal {
^~~~~~~~~~~~
a.rs:5:23: 5:35 help: run `rustc --explain E0446` to see a detailed explanation
error: aborting due to previous error
This is incorrect: the parse function is not visible anywhere HostInternal isn鈥檛 since the parser module is not public.
In my non-reduced crate I get a warning rather than an error but I haven鈥檛 managed to reproduce that in the reduced case. (Regardless, that warning is also incorrect.)
src/parser.rs:520:43: 520:55 warning: private type in public interface (error E0446), #[warn(private_in_public)] on by default
src/parser.rs:520 -> ParseResult<(HostInternal, &'i str)> {
^~~~~~~~~~~~
src/parser.rs:520:43: 520:55 note: this lint will become a HARD ERROR in a future release!
src/parser.rs:520 -> ParseResult<(HostInternal, &'i str)> {
This is expected, because the outer module could re-export that function.
I couldn鈥檛 but it doesn鈥檛. And the compiler should know that. It鈥檚 all in the same crate, a re-export can not be added without recompiling that crate.
see #22261 for the discussion, the point is that the privacy checks don't need to reason about the entire crate, but just about the module
I understand that fixing this bug may require doing a more complex analysis than is currently done, but i maintain that it's a bug. https://github.com/rust-lang/rust/issues/22261#issuecomment-78378267 says the same.
Conversely, this compile fine but _should_ be an error:
pub fn parse() -> parser::HostInternal {
unimplemented!();
}
mod parser {
pub struct HostInternal;
}
@SimonSapin
fixing this bug may require doing a more complex analysis than is currently done
Conversely, this compile fine but should be an error
More complex analysis (reachability by reexports, "nameability") was done before and the example with HostInternal was an error (based on https://github.com/rust-lang/rfcs/pull/136), but the rules were changed by the newer edition of RFC 136 to be local, module-level and based exclusively on pub annotations and not reachability (it wasn't thoroughly implemented though until recently).
Edit: some reasons are outlined by @nikomatsakis in https://github.com/rust-lang/rust/issues/28450#issuecomment-154465544
This sounds like a regression. Some git archaeology points to https://github.com/rust-lang/rfcs/pull/200, I鈥檒l read up on motivations.
The new rules are not an improvement in any way except to make the code in rustc simpler. There's still a lot of false negatives, still a lot of false positives as well, and it will cause breaking changes to a huge amount of existing Rust code. If there's going to be breaking changes, the new system should at least have significant improvements to make the breakage worth it.
except to make the code in rustc simpler
Nah, the compiler still tracks reachability through reexports for other purposes.
@petrochenkov So you're saying it isn't even an improvement at all and we're just breaking the world to satisfy some RFC that has been sitting around for over a year?
@retep998
The recent changes are an improvement because the implementation was a hardly predictable mix of the new rules (mostly), old rules (to less extent) and outright missing cases. Now it's at least consistent.
@retep998 Let me just say that I'm sorry your code stopped compiling -- I know that's always a pain, whatever the cause. In this case, as @petrochenkov said, there really aren't any new rules: it's just that rustc was (very) buggy and failed to enforce the rules consistently. But I know that doesn't make it any less annoying when you have to update your code.
The changes are still only warnings for me so far, although the warnings claim they will become errors, so thankfully it isn't the end of the world yet.
@retep998 I'd actually be interested to see the specific cases that are failing for you. In particular, I'd like to know if they fall into the category of type aliases that appear in public interfaces, or something else?
@nikomatsakis They're all basically a private struct, and then a public typedef to a raw pointer to it. The biggest example is https://github.com/retep998/winapi-rs/blob/bb182accc1cee10ea1a6e926ca51e8ddb63154c1/src/macros.rs#L4-L9 . It's easy enough for me to just make the internal opaque type public, I just don't like knowing that all existing versions of winapi will break in the future, forcing people to cargo update.
@retep998 OK. That particular case would not be addressed by any of the mitigating measures we considered, I think. (It's sort of the motivating example that the rules were trying to prevent, from what I understand, in that one ought to be able to assume, because it is declared priv, that the private inner struct is not accessible from outside the module.)
This bit me as well. I defined an internal struct, and that sub modules would accept that struct as an argument to their methods.
The "solution" was to trick the privacy visitor, by putting the struct into its own submodule, make the struct pub there, and then import from the sub module.
mod foo {
struct Buf {
inner: Vec<u8>
}
mod bar {
use super::Buf;
pub struct Encoder;
impl Encoder {
pub fn encode(&mut self, buf: &mut Buf) {
buf.inner.push(b'!');
}
}
}
}
Oh, that鈥檚 a nice trick, thanks @seanmonstar
(Through it鈥檚 still unfortunate that we have to do this to work around the lint鈥檚 incorrect algorithm.)
Is that the right way to do it? It would be good to know, so that we don't use something that will be disallowed in the future.
@bluss
Yes, the current rules have the "universal workaround":
/*priv*/ item Item;
=>
/*priv*/ mod m {
pub item Item;
}
It is even mentioned in the detailed error message.
I don't see how this can't be considered a bug:
mod bar {
use super::Foo;
pub fn foo_bar(_: Foo) {}
}
struct Foo;
It's trivial to see that this code doesn't actually export any private types. I strongly disagree with this being the intended behavior: it just seems wrong.
Closing as working according to the rules.
Some discussion about possibly changing the rules happens in https://internals.rust-lang.org/t/lang-team-minutes-private-in-public-rules/4504/43
Most helpful comment
I don't see how this can't be considered a bug:
It's trivial to see that this code doesn't actually export any private types. I strongly disagree with this being the intended behavior: it just seems wrong.