I would like to make a trait some items private and some public. I have considered to use sealed super trait, but in my case the private items depend on public ones, so it didn't work (also, I think it would not make private items inaccessible).
I would like something like this:
// The trait should be visible by user and usable in bounds.
// It cannot be implemented by user, but that's fine.
pub trait MyTrait {
// This type should be public
type Item;
// This type should be private, it is an implementation detail,
// completely invisible to user, it should not be a breaking
// change to change or remove this.
type Iter: Iterator<Item = Self::Item>;
// This function should be private, it is an implementation detail,
// completely invisible to user, it should not be a breaking
// change to change or remove this.
fn get_item(&mut self) -> Self::Item;
}
// There are functions like this in my library
pub fn do_something<T: MyTrait>(value: T) -> T::Item {
todo!()
}
// ... and some implementations of MyTrait
struct Foo;
impl MyTrait for Foo { /* ... */ }
I am currently considering two approaches:
Just #[doc(hidden)] the private items (and maybe add extra unnameable-typed argument to functions).
Keep them public and document them (maybe adding a "but pretend you do not see this" note).
I don't like any of them. Is there anything better?
That's sealing for methods, basically. std::error::Error does it.
For types you could seal a supertrait and make e.g. Item a parameter of the trait. Note that (whatever the approach) a private associated type clashes with being dyn compatible. It might work better to put bounds on the sealed methods instead of using a supertrait.
Nice, that would solve the dependency issue, thanks.
... but now that I play with it, it seems that the private associated types are easily accessible:
pub mod lib {
mod inner {
pub trait MyTraitInner<Item> {
type Iter: Iterator<Item = Item>;
fn get_item(&mut self) -> Item;
}
}
pub trait MyTrait: inner::MyTraitInner<Self::Item> {
type Item;
}
}
// This compiles fine, not even a warning,
// but T::Iter should be private
fn test<T: lib::MyTrait>(iter: T::Iter) {
todo!()
}
So how is that any better than putting #[doc(hidden)] there? Or am I doing it wrong?
That's fine in my case. The trait is already dyn incompatible anyway (not shown in OP example).
No, the private parts are not easily (if at all) expressible as an extension trait.
In my library I have a function and a set of types that can be used as an argument of that function. But how exactly do those types interact with that function is an internal implementation detail, which I do not want to expose to users.
Making separate function for each type seems infeasible in my case (but I may rethink it).
Using a private supertrait started being allowed in Rust 1.74 with this "type privacy" RFC. private_bounds is now just a warning, not an error. If you don't allow it, the code will still compile with a warning. I believe the "wrap a public trait in a private module" hack is no longer necessary.
You used to do exactly the same thing by putting a pub trait in a non-pub module. It's been a classic hack for ages; it's just now easier to do by not needing the bonus module.
I think this issue would benefit from a clearer motivation; if the ::Iter associated type really needs to be that private, then why does it need to appear in the public trait? I really do not think that an actually-private super-trait is the way to go, here. In other words, the private_bounds warning does hint at there being something smelly.
I suspect that what you want is mere convenience, locally, as in specifying in one go both your public trait, and the private version thereof.
If so, this post from quinedot gets there in one way:
I'd add to this / adjust it a bit further, though:
given your current setup, your private trait need not be generic over an Item, but, instead, be a subtrait of the public trait.
you could then either blanket-implement it, if/when applicable, or have manual impls thereof.
if blanket-implemented, you should be able to, locally, keep using the public trait as a bound and yet have access to the private subtrait associated type (you'll just need to use fully qualified syntax rather than the shorthand): given <T : PubSuperTrait>, Rust will know that T : SubTrait also holds thanks to the blanket impl.
if manually/case-by-case implemented, then, in order to get that assumedly desired local convenience, you will need to be using the private subtrait as the bound in your generic APIs (<T : SubTrait>).
Keep in mind that even if it is a subtrait, you will still be capable of defining logic of the super/public trait in terms of the subtrait for local implementors or whatnot.
Example
pub
trait Pub {
type Item;
fn get_item(&mut self) -> Option<Self::Item>;
}
trait Private : Pub {
type Iter : Iterator<Item = Self::Item>;
fn get_iter(&mut self) -> &mut Self::Iter;
}
pub
struct Example<I : Iterator>(
I,
);
impl<I : Iterator> Private for Example<I> {
type Iter = I;
fn get_iter(&mut self) -> &mut I {
&mut self.0
}
}
impl<I : Iterator> Pub for Example<I> {
type Item = I::Item;
fn get_item(&mut self) -> Option<I::Item> {
self.get_iter().next()
}
}
The case where I could see the private subtrait as legitimate is when, on top of the aforementioned things:
you wanted to expose some dedicated <T : PubTrait> API, such as a generic fn, which, in the implementation, would get to assume that T : PrivateTrait holds, whilst there being no blanket implementation of PrivateTrait, and instead, a manual/case-by-case list of these.
you'd be fine with, or even find it desirable, for downstream users not to be able to add their own impls of PubTrait.
In such a case, the PubTrait : PrivateTrait hierarchy makes sense, and if so, an actually (type-)private super trait shall be more honest, and thus, work better in edge cases and diagnostics, than a path-private/unnameable SealedTraitHack⢠trait.
But that conclusion can only be reached with these extra requirements and assumptions, which should be spelled out in this thread, if anything, for others stumbling into it