-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Semantic "private in public" enforcement. #1671
Conversation
# Motivation | ||
[motivation]: #motivation | ||
|
||
The "private-in-public" rules ensure the transitivity of abstraction. That is, one must be able to name types and bounds used in public APIs, from anywhere else. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"able to name" is not entirely correct, "able to name after addition of arbitrary number of reexports" is closer to the truth. E.g.
mod m {
mod detail {
pub struct S;
}
pub fn f() -> detail::S { detail::S }
}
is allowed because S
is potentially nameable outside of m
(if reexported), but not actually nameable (private-in-public checker doesn't analyze reexport chains to determine actual nameability by design).
I'd like to avoid promising namebility in docs, including RFCs, because it's already one of the most common misconceptions about our privacy system.
EDIT: The definition of "less public" below also uses nameability ("can be referred to by any name").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I remember reading about this before, but I wasn't clear on what decisions were taken.
It does weaken the "transitivity of abstraction" argument in that case, sadly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How odd, link to rational for this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks so much!
I want to have this outside of diff comments too: @petrochenkov pointed out that name, type alias, UFCS and associated type resolution combined (i.e. everything the compiler does to go from the syntactic to the semantic type-system) are enough to reveal all possible private type leaks in public APIs. I agree this would greatly simplify the RFC and the implementation work and allow deriving to work even in dubious situations where the set of implementations available to a bound is completely hidden. Does anyone else (e.g. from @rust-lang/lang) have anything against that? |
@eddyb sounds great to me! I'd ideally like the docs to evaluate/resolve types as little as possible---just enough not leak. Maybe we can someday leverage incremental compilation for this. (Type language is pure and thus can be safely lazily evaluated.) |
The main argument against this change probably lies outside of the language - documentation. |
I should write this out in more detail. The guarantee is that entities with visibility
|
Does this aim to address rust-lang/rust#30905? // This type is inacessible outside of the crate, but "public" to the entire
// crate, since it's at the crate root.
struct CratePrivType;
mod private {
// This function is inacessible outside of the crate, so it should be okay
// to return a crate-private type
pub fn gimme_crate_priv() -> super::CratePrivType { unimplemented!() }
}
fn main() {}
To work around this without exposing the crate-private type, one can put it in a private module. mod crate_priv {
pub struct CratePrivType;
}
mod private {
pub fn gimme_crate_priv() -> ::crate_priv::CratePrivType { unimplemented!() }
}
fn main() {} But this once again shows the stupidity of the current analysis. mod crate_priv {
pub struct CratePrivType;
}
mod private {
pub fn gimme_crate_priv() -> ::crate_priv::CratePrivType { unimplemented!() }
}
pub fn pub_api_func() -> ::crate_priv::CratePrivType { unimplemented!() }
fn main() {} Even rustdoc gets confused by this, and the crate private type will not get documented, even though |
@crumblingstatue We could restrict the check to the same module, which would allow that code to compile. |
@crumblingstatue
Playpen: https://is.gd/TFqG8o |
@petrochenkov You're right, pub(restricted) does solve the issue I was having. Thanks, and apologies for invading this thread. |
Alternative proposal on discuss (also from @eddyb): https://internals.rust-lang.org/t/rfc-reduce-false-positives-for-private-type-in-public-interface/3678 |
cc @pnkfelix do you remember the ins and outs of this from the last RFC discussion? |
ping @eddyb status? |
@ubsan See the latest comments on rust-lang/rust#34537. We're more likely to switch to a different scheme, checking types as they appear after inference, as opposed to only checking direct uses. |
@eddyb sounds like a close to me. Or at least a rewrite. |
@ubsan Oh right, it's my PR so I can just do this. |
Rendered