When wanting to provide automatic implementations based on whether a type can provide a certain raw pointer (as defined in a private module), I came across the following pattern(s):
Case 1:
/// Some module provided by a crate (this is public!)
pub mod module {
/// Some internals (this is supposed to be private!)
mod internal {
pub struct State {}
impl State {
pub fn hello_world(&self) {
println!("Hello world!")
}
}
pub trait HasState {
fn state(&self) -> &State;
}
}
/// A public trait
pub trait FooTrait {
fn foo(&self);
}
/// Anything that has a state (i.e. implements internal::HasState)
/// will automatically get an implementation of FooTrait
impl<T: internal::HasState> FooTrait for T {
fn foo(&self) {
self.state().hello_world();
}
}
/// Just one particular struct, we could have more like
/// S2, S3, S3, …
pub struct S1 {
state: internal::State,
}
impl S1 {
pub fn new() -> Self {
Self {
state: internal::State {},
}
}
}
impl internal::HasState for S1 {
fn state(&self) -> &internal::State {
&self.state
}
}
}
fn main() {
use module::{FooTrait, S1};
let v1 = S1::new();
v1.foo();
}
This code works. However, when I generate the documentation (assuming I create a library instead of a binary crate), then I get:
Trait mycrate::module::FooTrait
Implementors
impl<T: HasState> FooTrait for T`
Anything that has a state (i.e. implements internal::HasState) will automatically get an implementation of FooTrait
Note that HasState
is in a private module (mod internal
). It will not be linked by rustdoc but its name (and documentation comment) leaked as above.
Case 2:
I move my trait HasState
one level up and declare it private (it really doesn't matter for the public whether a type has a state or not; the important trait for the public is FooTrait
). Now I get a warning that my code will soon fail to work:
/// Some module provided by a crate (this is public!)
pub mod module {
/// Some internals (this is supposed to be private!)
mod internal {
pub struct State {}
impl State {
pub fn hello_world(&self) {
println!("Hello world!")
}
}
}
trait HasState {
fn state(&self) -> &internal::State;
}
/// A public trait
pub trait FooTrait {
fn foo(&self);
}
/// Anything that has a state (i.e. implements internal::HasState)
/// will automatically get an implementation of FooTrait
impl<T: HasState> FooTrait for T {
fn foo(&self) {
self.state().hello_world();
}
}
/// Just one particular struct, we could have more like
/// S2, S3, S3, …
pub struct S1 {
state: internal::State,
}
impl S1 {
pub fn new() -> Self {
Self {
state: internal::State {},
}
}
}
impl HasState for S1 {
fn state(&self) -> &internal::State {
&self.state
}
}
}
fn main() {
use module::{FooTrait, S1};
let v1 = S1::new();
v1.foo();
}
The warning I get is:
Compiling playground v0.0.1 (/playground)
warning: private trait `HasState` in public interface (error E0445)
--> src/main.rs:25:5
|
25 | / impl<T: HasState> FooTrait for T {
26 | | fn foo(&self) {
27 | | self.state().hello_world();
28 | | }
29 | | }
| |_____^
|
= note: `#[warn(private_in_public)]` on by default
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
= note: for more information, see issue #34537 <https://github.com/rust-lang/rust/issues/34537>
warning: `playground` (bin "playground") generated 1 warning
Finished dev [unoptimized + debuginfo] target(s) in 1.10s
Running `target/debug/playground`
Case 3:
I keep HasState
one level up (i.e. directly in module
) and make it public (which I don't really want as it's an implementation detail).
/// Some module provided by a crate (this is public!)
pub mod module {
/// Some internals (this is supposed to be private!)
mod internal {
pub struct State {}
impl State {
pub fn hello_world(&self) {
println!("Hello world!")
}
}
}
pub trait HasState {
fn state(&self) -> &internal::State;
}
/// A public trait
pub trait FooTrait {
fn foo(&self);
}
/// Anything that has a state (i.e. implements internal::HasState)
/// will automatically get an implementation of FooTrait
impl<T: HasState> FooTrait for T {
fn foo(&self) {
self.state().hello_world();
}
}
/// Just one particular struct, we could have more like
/// S2, S3, S3, …
pub struct S1 {
state: internal::State,
}
impl S1 {
pub fn new() -> Self {
Self {
state: internal::State {},
}
}
}
impl HasState for S1 {
fn state(&self) -> &internal::State {
&self.state
}
}
}
fn main() {
use module::{FooTrait, S1};
let v1 = S1::new();
v1.foo();
}
This works again without problems, but now I leak a type from a private module through the publicly available module::HasState
, whose method state
returns &internal::State
.
What do do?
I figured out a complicated way to avoid any of this by providing a wrapper type for State
, but it's very verbose. My question is:
Should I make the effort to avoid any of the three patterns?
Taking the warning seriously, Case 2 will be disallowed soon. But what about Case 1 and Case 3? Are these sound to work with or would I risk running into problems in future as these seem to be very similar? Also, leaking the trait internal::HasState
(in Case 1) or the type internal::State
(in Case 3) seems to be a bit ugly at least and could confuse readers of the documentation.
What's usually done here?
Potentially related threads: