reference
reference copied to clipboard
Visibility: Non-importable types in public function signature?
Recently, I ran into an unfamiliar visibility situation with a library that I used. I could not find any mentioning of this in the reference so I would like to know if this is just a (historic) artifact or actually by design. It concerns types in the signature of public functions which cannot be imported themselves.
Here is an example. Let's consider a workspace with the following layout:
.
├── app
│ ├── Cargo.toml
│ └── src
│ └── main.rs
├── Cargo.toml
└── helper
├── Cargo.toml
└── src
├── lib.rs
└── utils.rs
4 directories, 6 files
// helper/src/lib.rs
use utils::Tool;
mod utils;
pub fn get_tool() -> Tool {
Tool {}
}
// helper/src/utils.rs
#[derive(Debug)]
pub struct Tool;
// app/src/main.rs
use helper::get_tool;
fn main() {
// Getting a `Tool` and debug-printing it works
let tool = get_tool();
dbg!(tool);
}
// Not possible because `Tool` cannot be imported here.
// fn modify_tool(t: Tool) -> Tool {
// ...
// }
// error[E0603]: module `utils` is private
// use helper::utils::Tool;
// error[E0603]: struct `Tool` is private
// use helper::Tool;
# app/Cargo.toml
# ...
[dependencies]
helper = { path = "../helper" }
Described in words: We have a library helper
which has a private submodule utils
. The private submodule utils
contains a public struct Tool
which is used in the return type of the public function get_tool() -> Tool
of the library. When using the library in the other crate app
, we can call the public function get_tool()
and obtain the type Tool
which is public in a private module but leaked through the public function signature. However we cannot easily pass the type Tool
across function boundaries because we cannot import it itself (there is probably some trickery possible with implicitly capturing the type in a closure, but not sure if we could still return it).
In the library where I found it, this was not intentional and the type was subsequently made public. However do some libraries use this intentionally? Should the visibility chapter of the reference include this advanced situation and some information when it makes sense to do so?
However do some libraries use this intentionally?
Yes, sealed traits are often created by adding a super trait to the trait which can't be imported. This pattern is used in the standard library as well as in some other libraries.
mod sealed {
pub trait Sealed {}
}
pub trait SealedTrait: sealed::Sealed {}
impl sealed::Sealed for u8 {}
impl SealedTrait for u8 {}
Ah thank you @bjorn3 for pointing out the use as sealed traits :pray: Do you think it would be helpful to mention this in the respective chapter of the reference?
Do you think it would be helpful to mention this in the respective chapter of the reference?
I think so.
Alright, then I will draft a PR for this :)