Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upBan private items in public APIs #136
Conversation
This comment has been minimized.
This comment has been minimized.
|
FWIW, this is actually partially implemented in the form of the struct Private;
pub type Public = Private;trait Private {}
pub fn public<T: Private>() {}Both of these were explicit decisions, since they essentially allow exposing a public interface around private types. The trait one I regard as particularly important, since it is the only way (that I know) of exposing a polymorphic interface while disallowing it to be overridden (or methods called) by others, e.g. the trait bound may be used by function doing ridiculously I'm in favour of extending the lint's rules to include Also, this is closely related to rust-lang/rust#10573. |
This comment has been minimized.
This comment has been minimized.
Yeah I know, probably should've mentioned it.
I know about this trick, but is it actually used anywhere? It feels kind of hacky to me, and I would rather have some kind of formal mechanism to accomplish this (like maybe
Ah yes, thanks for unearthing the evidence for my point in the Motivation :)
|
This comment has been minimized.
This comment has been minimized.
SiegeLord
commented
Jun 25, 2014
Multiple people have asked about this on Reddit. I've used it in my library. In my case, I didn't care about the external implementations, rather I wanted to implement default methods of a public trait in terms of the methods provided by the private super-trait. |
This comment has been minimized.
This comment has been minimized.
|
I added some of the mentioned examples to the Motivation and Drawbacks sections. |
This comment has been minimized.
This comment has been minimized.
SiegeLord
commented
Jun 26, 2014
|
I don't think the drawbacks section accurately portrays the practical use of the private super-trait idiom. In my case, the pattern like looks like this: struct PrivateData
{
data: int,
}
trait PrivateTrait
{
fn get_data<'l>(&'l self) -> PrivateData;
}
pub trait PublicTrait : PrivateTrait
{
fn public_function(&self) -> int
{
self.get_data().data
}
}Making everything public as required by this RFC would leak those internal implementation details. In principle I could mark those API entries as permanently unstable, but surely that's what the privacy system should be used for. |
This comment has been minimized.
This comment has been minimized.
AbigailBuccaneer
commented
Jul 1, 2014
|
The D language has this - under the name Voldemort types - and considers them a feature rather than a drawback. |
This comment has been minimized.
This comment has been minimized.
|
@SiegeLord I'm fairly certain your use case could be satisfactorally reformulated using In any case, you've offered a quasi-objection but not a proposed resolution. What would be your preference? To do nothing? To carve out a special exception for private supertraits? Would it be intolerable if this functionality was lost even temporarily? Would adopting this rule only be acceptable to you if the @AbigailBuccaneer Our (better designed, more principled) equivalent to that functionality would be RFC PR #105. From a philosophical perspective (which is important!), semantic invariants should be upheld using semantic means whenever possible. (To put it another way, in the typechecking and other semantic analysis phases, rather than in the name resolution phase.) If the safety of your abstraction rests on the fact that external code can't write the name of a thing, that's brittle. C++ libraries had a technique of relying on private classes returned by public functions to prevent them being used except as temporary rvalues, because their name couldn't be written, which suddenly broke when C++11 added In this particular case, it's admittedly unlikely that we would gain something like EDIT: @SiegeLord: Just to clarify, forbidding private supertraits is not a hill I would choose to die on, and I'd be mostly fine with carving out an exception. If we can outlaw the other 95% of cases, I'll be 95% satisfied. And the process could just as well be structured to first outlaw the other 95% in the first phase, and then remove private supertraits together with introducing the replacement functionality in the second phase (as long as that happens before 1.0, of course). I just wanted to make clear why I think consistency is the better option on the merits, and to try to suss out what you think. |
This comment has been minimized.
This comment has been minimized.
SiegeLord
commented
Jul 1, 2014
|
I think an exception to private super-traits it something that I would be content with. To me, private super-traits of public traits are closely related to private fields in public types. I.e. having a private field shows up in the documentation (in the sense that its presence is indicated by a comment) and it prevents you from instantiating an instance of the type manually. Private super-traits seem to act the same way except in type space: they (could) show up in documentation as a comment, and they prevent you from 'instantiating' a new trait/type that implements it. I've never studied type theory, so I don't know if this analogy is valid. Either way, to me, private super-traits are not members of the public API any more than private fields are. In this sense, banning all the other cases of private items while allowing these is perfectly consistent, at least in my view. In particular, a private super trait does not have to show up in function signatures etc to be useful, e.g.: trait A
{
fn private_method(&self) {}
}
pub trait B : A
{
}
pub fn public_function<T: B>(a: T)
{
a.private_method();
} |
This comment has been minimized.
This comment has been minimized.
|
I'd still be curious about further details of your use case mentioned in the previous comment. |
This comment has been minimized.
This comment has been minimized.
SiegeLord
commented
Jul 3, 2014
|
There is no |
This comment has been minimized.
This comment has been minimized.
|
Servo also uses this. |
This comment has been minimized.
This comment has been minimized.
As noted above, we could simply carve out an exception for private supertraits for now, which seems to be the only controversial part of the proposal, to keep that kind of code working. We could then revisit it later with potentially a more proper solution if we want to. |
This comment has been minimized.
This comment has been minimized.
|
I wouldn't really want this to be removed until we have actual abstract types in modules. I'm fine with it being feature-gated forever, though, and that might actually be the right thing to do, since we wouldn't want to support this if we did have abstract types. Since Rust has recursive modules, adding abstract types might be nontrivial. |
This comment has been minimized.
This comment has been minimized.
Which "this"? The whole enchilada? Just private supertraits? Some other specific part of it? (If it's not just private supertraits you're referring to, could you provide an example of what you would use the functionality for which wouldn't otherwise be possible?)
Abstract types in what sense? To put it another way, what difference are you thinking of relative to structs with private fields?
I'd be OK with feature gating it, but I also don't see why it's desirable to keep it. From my perspective it's just tying up loose ends and removing the possibility of weirdness. |
This comment has been minimized.
This comment has been minimized.
This seems like a mistake. I would think there's a definite use for being able to use a // private struct
struct MyStruct<T> { ... }
// private marker types
struct MarkerOne;
struct MarkerTwo;
impl MyStruct<MarkerOne> { ... }
impl MyStruct<MarkerTwo> { ... }
// publicize those two variants
pub type StructOne = MyStruct<MarkerOne>;
pub type StructTwo = MyStruct<MarkerTwo>;This would allow me to vend the two types I think the better solution is to merely consider that a |
This comment has been minimized.
This comment has been minimized.
|
Currently I think the primary problem here is that private types can leak in public signatures and bounds - and would prefer to prevent only that. |
This comment has been minimized.
This comment has been minimized.
tupshin
commented
Jul 27, 2014
|
I'd love to see this implemented, but on the other hand, there really has to be a way to share private members between modules. In implementing a wrapper for a somewhat large C API, I tried to chuck it up into modules (https://github.com/tupshin/rust-cassandra-wrapper/tree/master/src/cassandra). Unfortunately, there is no adequate way right now to have structs wrapping unsafe private structs, and still adequately modularize the implementation. I don't know if there's a better way, but "protected" visibility, so it can be seen by sibling modules, is what comes to mind. |
This comment has been minimized.
This comment has been minimized.
|
@tupshin I take it that you were unable to structure your code in a manner like this? Example playpen code illustrating sharing a private type among child modules. (Note that this works even when you break the nested submodules |
This comment has been minimized.
This comment has been minimized.
haberman
commented
Jul 27, 2014
|
Would this prohibit this pattern, which was suggested to me and I find quite useful?
Rationale and motivation for this (as well as a link to sample code) are in this thread: https://mail.mozilla.org/pipermail/rust-dev/2014-January/008033.html |
This comment has been minimized.
This comment has been minimized.
|
The pattern of having a private field in a struct? No. |
This comment has been minimized.
This comment has been minimized.
tupshin
commented
Jul 27, 2014
|
@pnkfelix I did an experimental rework to use that approach in a simple way by moving my structs into mod.rs. https://github.com/tupshin/rust-cassandra-wrapper/blob/master/src/cassandra/mod.rs#L28
|
This comment has been minimized.
This comment has been minimized.
|
I feel like this RFC is a good idea, but I've been trying to think of a crisper pitch than "allowing this feels weird, and we should be conservative." One concrete problem with allowing private items to leak is that you lose some local reasoning. You might expect that if an item is marked private, you can refactor at will without breaking clients. But with leakage, you can't make this determination based on the item alone: you have to look at the entire API to spot leakages (or, I guess, have the lint do so for you). Perhaps not a huge deal in practice, but worrying nonetheless. Put another way, it seems useful to have privacy as a strict and simple way of knowing what downstream clients can depend on, and what they can't. Allowing private supertraits seems OK because, assuming all the other leakage checks are in place, clients have no way to depend on or use the supertrait. It would effectively be a marker that the child trait is unimplementable by the client, and could be shown as such in rustdoc. It also seems like the use of Assuming these particular cases can be cleanly handled, what's the argument for not doing this? |
This comment has been minimized.
This comment has been minimized.
That's the wrong way to think about it. We don't put everything in the language and then argue to remove things. We keep the language as simple as possible and argue what to add. Your points about local reasoning seem like they should be satisfied by the Given that we already have a lint to flag this sort of thing, what do we gain by turning it into a language rule? |
This comment has been minimized.
This comment has been minimized.
This doesn't seem particularly helpful. For one, this RFC can as easily be viewed as taking away a feature as adding one. But I think that's not really relevant: the real question is about the meaning of privacy in Rust. I was surprised that leakage of private items is allowed, and I'm trying to understand the rationale of the current design, assuming it was intentional. If you do want to make this kind of general point, I think @glaebhoerl's argument in the RFC is the stronger one: absent a clear reason and coherent design for exposing private items, the conservative thing to do is disallow them.
Not exactly. Since lints can be turned off, safely refactoring private items an existing code base requires auditing places where the lint is disabled. If leakage was disallowed by the language, though, you could reason about privacy truly locally. |
This comment has been minimized.
This comment has been minimized.
Are you concerned about your own code, or are you trying to force what you consider to be good coding practices upon other people? Because if you're concerned about your own code, don't turn off the lint. Personally, I think it's not a language's place to try to force what the author considers good design upon the users. It should encourage good design, but it should also be able to get out of the way and let people do what they really want to, if they really want. This is basically the same reason why I think the RFC about enforcing capitalization rules was a bad idea. We've already shown that there are cases where exposing private types allows you to do things you couldn't otherwise (such as have a public trait nobody outside your crate can implement). The problem with defaulting to being conservative and adding exceptions for things like private supertraits is you can only add exceptions for the things you can think of doing right now. Sticking with a warning instead of a hard rule lets people disable it if they come up with valid reasons to violate the rule. As an example of a valid reason that hasn't been suggested yet, not too long ago (I think in another PR but I forget) I wrote some code for someone that used I believe the only alternative, if this RFC were to be implemented, would be to have to create a brand new type that implemented For what it's worth, my earlier suggestion that a |
This comment has been minimized.
This comment has been minimized.
This seems like a good way to have our cake and eat it too, in a disciplined manner. . . and I think there is precedent for it in ML, no? (It is possible I misremember.) @aturon in response to your point about using a newtype struct instead of this, do note @huonw 's point that:
I often overlook the latter cost myself. @glaebhoerl do you have any thoughts on this detail (on making an exception to the blanket rule for |
This comment has been minimized.
This comment has been minimized.
|
I suppose a problem with making an exception for e.g. as has been said above, what one really wants here is an existential type (or maybe "abstract type", though I guess the two are the same under some mental models), where all you can do is pass around the instance but do not know what its internal structure is. But currently that is not what happens when you mix |
This comment has been minimized.
This comment has been minimized.
|
@pnkfelix I gave up trying to relate Rust's decisions to ML modules earlier in the discussion, but ML solves this by separating modules from their signatures and providing a rich signature language. Ascribing a signature to a module may hide type components of the module entirely, make them abstract (in the sense of existential types), leave them manifest. It gets more complex than that (e.g. sharing constraints between distinct modules so that you can control external knowledge of implementation details), but there is a distinction made between namespace visibility and type abstraction. In Rust that distinction is missing, although field privacy can be used to emulate it in some ways. |
This comment has been minimized.
This comment has been minimized.
|
@pnkfelix Yes, existential types is what I really want in that example. But as long as we don't have them, I'll settle for a |
This comment has been minimized.
This comment has been minimized.
|
What you want is either existential types, "abstract return types" (which are "module-level" existential types), or a newtype.
An issue here is that the semantics of "private items" leaking is unclear (if you impl a private struct with a public trait, can you access it?) - the best way to avoid this kind of mess is to ban these "unprincipled" leaks and support only principled ones. The "allow this - someone may have a use for this you haven't thought of" viewpoint isn't the way the Rust type-system is designed - if the Rust designers haven't thought of a special use for a feature (especially a feature designed as syntactic sugar), it might have weird interactions with other Rust features (see GeneralizedNewtypeDeriving[http://www.haskell.org/pipermail/haskell-cafe/2010-March/074305.html] for such an example in Haskell). This is a distinction between Rust's typesystem and C++ templates, with the latter caring much less about these edge-case (but used-by-clever-developers) interactions. |
This comment has been minimized.
This comment has been minimized.
I'm, likewise, not nearly as bothered by them as by the other things. But if we're doing it for everything else, and given that it's just a feature gate instead of outright removal, even just for completeness and consistency's sake, I'd prefer to have it apply equally, at least initially, to private supertraits as well. I'd like to at least give it a run whether "no exceptions" might be workable, and if it's really not, then (oh well) add the exception.
I think one difference we have relative to Haskell which makes a big difference here is that in Haskell, you can have private Given this line of reasoning I would like to have private 1 Think of a |
This comment has been minimized.
This comment has been minimized.
As far as that goes, GNTD is very closely related to the |
This comment has been minimized.
This comment has been minimized.
|
@glaebhoerl Just to make sure you saw it: this RFC was discussed in yesterday's meeting, with no definite decision yet. |
glaebhoerl
added some commits
Jun 24, 2014
This comment has been minimized.
This comment has been minimized.
|
@aturon Thanks! I saw. And gah, it looks like I've royally screwed up the rebasing. :( What to do? But I've significantly updated the RFC based on the discussion here. |
This comment has been minimized.
This comment has been minimized.
|
You should be able to use (Also, theoretically one should never rebase or amend an RFC; just new commits.) |
This comment has been minimized.
This comment has been minimized.
|
That seems to have worked. Thanks. (For some reason I had internalized the rule as "don't squash commits", instead of "do not rebase at all".) (And all this time, I had been interpreting |
nikomatsakis
merged commit 20c5714
into
rust-lang:master
Aug 12, 2014
lilyball
referenced this pull request
Aug 12, 2014
Closed
RFC 48 not updated to reflect meeting decision #196
japaric
referenced this pull request
Nov 2, 2014
Closed
Public methods on private types are not exported symbols #11685
alexcrichton
referenced this pull request
Nov 2, 2014
Closed
Refine the story of externally referenced private structures. #10573
This comment has been minimized.
This comment has been minimized.
|
Hey, privacy experts: Is this actually a problem: rust-lang/rust#17697 ? (I'm trying to figure out if it should be cloned into a fresh issue, as I am doing with others that were closed around the same time by that owner.) |
This comment has been minimized.
This comment has been minimized.
|
My first guess is that this is just useless - you can't expose a private type, so there's no way its field being public could have any relevance - and so it's worth warning about, but not imperative to forbid. But it's possible that I'm missing something. |
glaebhoerl
referenced this pull request
Aug 12, 2015
Closed
RFC: Add unboxed, abstract return types #105
aturon
referenced this pull request
Nov 5, 2015
Closed
Tracking issue for `visible_private_types` feature #29627
This comment has been minimized.
This comment has been minimized.
|
I have an issue with this RFC: it does not explain what is meant by the word public within the RFC text. What does it mean?
The meaning of this sentence, which is key to interpreting the RFC, is thus unclear:
From the comments at the bottom, I'm guessing "public" means 2. Unfortunately, this still leaves the issue of how to document the "unnameable" type, but I guess that can be dealt with (though it means using the name of a private module, most likely). |
This comment has been minimized.
This comment has been minimized.
Let's say a function return a value of unnameable type. Then the docs on this function can describe what a user of this function can do with that value. For example, C++ docs do this when return type of something is unspecified. FWIW, I tweaked the privacy pass to apply more strict rules (variant 3 from your list) and run it on rustc and standard library. Most of broken code contained actual deficiencies - types that should have been nameable, but weren't reexported by accident. |
This comment has been minimized.
This comment has been minimized.
|
@dhardy |
This comment has been minimized.
This comment has been minimized.
|
@petrochenkov I realise the RFC has been modified since this PR, but this is still the place for comments. Also, I am not surprised that variant 3 works for most code. If you read this PR, it reads two uses for exporting "private" (unnameable) items, and in both cases better solutions (type aliases/deriving new-types, and private trait members). IMO that would be the better solution than the current RFC, but it's not really important. What I don't like about current RFC (aside from the contradiction in allowing |
glaebhoerl commentedJun 24, 2014
Pretty