reference icon indicating copy to clipboard operation
reference copied to clipboard

Visibility: Non-importable types in public function signature?

Open sgasse opened this issue 1 year ago • 4 comments

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?

sgasse avatar Apr 19 '23 08:04 sgasse

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 {}

bjorn3 avatar Apr 19 '23 12:04 bjorn3

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?

sgasse avatar May 09 '23 13:05 sgasse

Do you think it would be helpful to mention this in the respective chapter of the reference?

I think so.

bjorn3 avatar May 09 '23 13:05 bjorn3

Alright, then I will draft a PR for this :)

sgasse avatar May 10 '23 08:05 sgasse