Skip to content

exported_private_dependencies lint only take effect in innermost dependency #119428

Description

@linyihai

View all comments

Problem

The exported_private_dependencies lint only take affect in the innermost dependency in a recursively dependent environment.

This inspired by #44663 (comment).

Steps

In order to prove this problem, I purposely contructed a code repository, here

in the repository, the crates folder has three crates,

  • grandparent_dep, the init crate provied the pub struct
  • parent_dep, the middle crate, add grandparent_dep as public and reexport pub struct from grandparent_dep
  • pub_dep, the outmost crate, add parent_dep as public and reexport pub struct from parent_dep

(the crates in crates can be treated as download from respority(like github, crates.io))

in src/lib.rs, add pub_dep as dependency but private.

After downloading the resposity,

1、 run cargo build, no lint warning message
2、 change the public = false in crates/pub_dep/Cargo.toml, then run cargo build, no lint warning message
3、 change the public = false in crates/parent_dep/Cargo.toml, run cargo build and a lint warning message comes up.

cargo build
   Compiling parent_dep v0.1.0 (/root/workspace/recursive_pub_reexport/crates/parent_dep)
   Compiling pub_dep v0.1.0 (/root/workspace/recursive_pub_reexport/crates/pub_dep)
   Compiling simple v0.1.0 (/root/workspace/recursive_pub_reexport)
warning: type `FromPriv` from private dependency 'grandparent_dep' in public interface
 --> src/lib.rs:3:1
  |
3 | pub fn use_pub(_: pub_dep::FromPriv) {}
  | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  |
  = note: `#[warn(exported_private_dependencies)]` on by default

Possible Solution(s)

The focus of this issue is to verify whether there is a problem with the current situation, so the solution will not be considered for the time being.

Notes

No response

Version

cargo 1.76.0-nightly (623b78849 2023-12-02)
release: 1.76.0-nightly
commit-hash: 623b788496b3e51dc2f9282373cf0f6971a229b5
commit-date: 2023-12-02
host: x86_64-unknown-linux-gnu
libgit2: 1.7.1 (sys:0.18.1 vendored)
libcurl: 8.4.0-DEV (sys:0.4.68+curl-8.4.0 vendored ssl:OpenSSL/1.1.1u)
ssl: OpenSSL 1.1.1u  30 May 2023
os: Ubuntu 22.04 (jammy) [64-bit]

Activity

  1. epage commented on Dec 27, 2023

    @epage
    Contributor

    I made a slight change to the repo, renaming use_pub to use_priv and added the function to each package along the way so you can more easily see what the whole tree was doing

    image

    image

    image

    image

  2. epage commented on Dec 27, 2023

    @epage
    Contributor

    I was curious what is done with diamonds: nothing unexpected.

    image

    image

    image

    image

  3. epage commented on Dec 27, 2023

    @epage
    Contributor

    Going back to main

    $ cargo +nightly clean && cargo +nightly check --all -vvv
         Removed 44 files, 183.6KiB total
        Checking grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
         Running `CARGO=/home/epage/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo CARGO_CRATE_NAME=grandpa
    rent_dep CARGO_MANIFEST_DIR=/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep CARGO_PKG_AUTH
    ORS='' CARGO_PKG_DESCRIPTION='' CARGO_PKG_HOMEPAGE='' CARGO_PKG_LICENSE='' CARGO_PKG_LICENSE_FILE='' CARGO_PKG_NAME=gr
    andparent_dep CARGO_PKG_README='' CARGO_PKG_REPOSITORY='' CARGO_PKG_RUST_VERSION='' CARGO_PKG_VERSION=0.1.0 CARGO_PKG_
    VERSION_MAJOR=0 CARGO_PKG_VERSION_MINOR=1 CARGO_PKG_VERSION_PATCH=0 CARGO_PKG_VERSION_PRE='' CARGO_RUSTC_CURRENT_DIR=/
    home/epage/src/personal/dump/recursive_pub_reexport LD_LIBRARY_PATH='/home/epage/src/personal/dump/recursive_pub_reexp
    ort/target/debug/deps:/home/epage/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib:/home/epage/.rustup/toolchai
    ns/nightly-x86_64-unknown-linux-gnu/lib' /home/epage/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustc --c
    rate-name grandparent_dep --edition=2021 crates/grandparent_dep/src/lib.rs --error-format=json --json=diagnostic-rende
    red-ansi,artifacts,future-incompat --diagnostic-width=118 --crate-type lib --emit=dep-info,metadata -C embed-bitcode=n
    o -C debuginfo=2 -C metadata=ec231377d3a2b1c4 -C extra-filename=-ec231377d3a2b1c4 --out-dir /home/epage/src/personal/d
    ump/recursive_pub_reexport/target/debug/deps -C incremental=/home/epage/src/personal/dump/recursive_pub_reexport/targe
    t/debug/incremental -L dependency=/home/epage/src/personal/dump/recursive_pub_reexport/target/debug/deps`
        Checking parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
         Running `CARGO=/home/epage/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo CARGO_CRATE_NAME=parent_
    dep CARGO_MANIFEST_DIR=/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep CARGO_PKG_AUTHORS='' CAR
    GO_PKG_DESCRIPTION='' CARGO_PKG_HOMEPAGE='' CARGO_PKG_LICENSE='' CARGO_PKG_LICENSE_FILE='' CARGO_PKG_NAME=parent_dep C
    ARGO_PKG_README='' CARGO_PKG_REPOSITORY='' CARGO_PKG_RUST_VERSION='' CARGO_PKG_VERSION=0.1.0 CARGO_PKG_VERSION_MAJOR=0
     CARGO_PKG_VERSION_MINOR=1 CARGO_PKG_VERSION_PATCH=0 CARGO_PKG_VERSION_PRE='' CARGO_RUSTC_CURRENT_DIR=/home/epage/src/
    personal/dump/recursive_pub_reexport LD_LIBRARY_PATH='/home/epage/src/personal/dump/recursive_pub_reexport/target/debu
    g/deps:/home/epage/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib:/home/epage/.rustup/toolchains/nightly-x86_
    64-unknown-linux-gnu/lib' /home/epage/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustc --crate-name paren
    t_dep --edition=2021 crates/parent_dep/src/lib.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future
    -incompat --diagnostic-width=118 --crate-type lib --emit=dep-info,metadata -C embed-bitcode=no -C debuginfo=2 -C metad
    ata=b0e93a3b309a3856 -C extra-filename=-b0e93a3b309a3856 --out-dir /home/epage/src/personal/dump/recursive_pub_reexpor
    t/target/debug/deps -C incremental=/home/epage/src/personal/dump/recursive_pub_reexport/target/debug/incremental -L de
    pendency=/home/epage/src/personal/dump/recursive_pub_reexport/target/debug/deps --extern grandparent_dep=/home/epage/s
    rc/personal/dump/recursive_pub_reexport/target/debug/deps/libgrandparent_dep-ec231377d3a2b1c4.rmeta`
        Checking pub_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/pub_dep)
         Running `CARGO=/home/epage/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo CARGO_CRATE_NAME=pub_dep
     CARGO_MANIFEST_DIR=/home/epage/src/personal/dump/recursive_pub_reexport/crates/pub_dep CARGO_PKG_AUTHORS='' CARGO_PKG
    _DESCRIPTION='' CARGO_PKG_HOMEPAGE='' CARGO_PKG_LICENSE='' CARGO_PKG_LICENSE_FILE='' CARGO_PKG_NAME=pub_dep CARGO_PKG_
    README='' CARGO_PKG_REPOSITORY='' CARGO_PKG_RUST_VERSION='' CARGO_PKG_VERSION=0.1.0 CARGO_PKG_VERSION_MAJOR=0 CARGO_PK
    G_VERSION_MINOR=1 CARGO_PKG_VERSION_PATCH=0 CARGO_PKG_VERSION_PRE='' CARGO_RUSTC_CURRENT_DIR=/home/epage/src/personal/
    dump/recursive_pub_reexport LD_LIBRARY_PATH='/home/epage/src/personal/dump/recursive_pub_reexport/target/debug/deps:/h
    ome/epage/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib:/home/epage/.rustup/toolchains/nightly-x86_64-unknow
    n-linux-gnu/lib' /home/epage/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustc --crate-name pub_dep --edit
    ion=2021 crates/pub_dep/src/lib.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future-incompat --dia
    gnostic-width=118 --crate-type lib --emit=dep-info,metadata -C embed-bitcode=no -C debuginfo=2 -C metadata=523c770971b
    0c118 -C extra-filename=-523c770971b0c118 --out-dir /home/epage/src/personal/dump/recursive_pub_reexport/target/debug/
    deps -C incremental=/home/epage/src/personal/dump/recursive_pub_reexport/target/debug/incremental -L dependency=/home/
    epage/src/personal/dump/recursive_pub_reexport/target/debug/deps --extern parent_dep=/home/epage/src/personal/dump/rec
    ursive_pub_reexport/target/debug/deps/libparent_dep-b0e93a3b309a3856.rmeta`
        Checking simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
         Running `CARGO=/home/epage/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/cargo CARGO_CRATE_NAME=simple
    CARGO_MANIFEST_DIR=/home/epage/src/personal/dump/recursive_pub_reexport CARGO_PKG_AUTHORS='' CARGO_PKG_DESCRIPTION=''
    CARGO_PKG_HOMEPAGE='' CARGO_PKG_LICENSE='' CARGO_PKG_LICENSE_FILE='' CARGO_PKG_NAME=simple CARGO_PKG_README='' CARGO_P
    KG_REPOSITORY='' CARGO_PKG_RUST_VERSION='' CARGO_PKG_VERSION=0.1.0 CARGO_PKG_VERSION_MAJOR=0 CARGO_PKG_VERSION_MINOR=1
     CARGO_PKG_VERSION_PATCH=0 CARGO_PKG_VERSION_PRE='' CARGO_PRIMARY_PACKAGE=1 CARGO_RUSTC_CURRENT_DIR=/home/epage/src/pe
    rsonal/dump/recursive_pub_reexport LD_LIBRARY_PATH='/home/epage/src/personal/dump/recursive_pub_reexport/target/debug/
    deps:/home/epage/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib:/home/epage/.rustup/toolchains/nightly-x86_64
    -unknown-linux-gnu/lib' /home/epage/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/bin/rustc --crate-name simple
    --edition=2021 src/lib.rs --error-format=json --json=diagnostic-rendered-ansi,artifacts,future-incompat --diagnostic-w
    idth=118 --crate-type lib --emit=dep-info,metadata -C embed-bitcode=no -C debuginfo=2 -C metadata=2b1b07927f979b6c -C
    extra-filename=-2b1b07927f979b6c --out-dir /home/epage/src/personal/dump/recursive_pub_reexport/target/debug/deps -C i
    ncremental=/home/epage/src/personal/dump/recursive_pub_reexport/target/debug/incremental -L dependency=/home/epage/src
    /personal/dump/recursive_pub_reexport/target/debug/deps --extern 'priv:pub_dep=/home/epage/src/personal/dump/recursive
    _pub_reexport/target/debug/deps/libpub_dep-523c770971b0c118.rmeta' -Z unstable-options`
        Finished dev [unoptimized + debuginfo] target(s) in 0.08s

    With line wrapping
    image

  4. epage commented on Dec 27, 2023

    @epage
    Contributor

    We only pass --extern for direct dependencies which means rustc is only told about priv for direct dependencies and rustc then collect that information when recursing through dependencies. I think this should work if everything builds cleanly with --forbid exported_private_dependencies (or --deny but they respect the rules perfectly).

    In the transitional case where foundational packages have not been updated, users will have to use #[allow(exported_private_dependencies)] when putting transitive types in their API.

    In the transitional case where foundation packages have been updated but intermediate packages have not been, no warning will be given even when it should.

    So the next questions are

    • Are there cases I'm missing?
    • What are our options for fixing this?
    • How much do we care?
  5. linyihai commented on Dec 28, 2023

    @linyihai
    ContributorAuthor

    Thank you for dig into this issue.

  6. epage commented on Dec 28, 2023

    @epage
    Contributor

    Another case I want to test is when a package has a private dependency in its API (so should warn) but it is declared public in a transitive dependency.

  7. epage commented on Dec 28, 2023

    @epage
    Contributor

    image

    image

    Looks like once a dependency is public, it is always viewed as public.

  8. epage commented on Dec 28, 2023

    @epage
    Contributor

    Summary: only direct dependencies are marked as public/private; all transitive uses of it respect the highest visibility present in that subtree of the dependency graph.

    Example Side effects

    • If you directly depend on a transitive dependency that is marked as public, it will be considered public for you, not emitting any warnings
    • If a grandparent is re-exported by a parent (and declared it public) and you use it in your API, you won't get a warning about needing to mark the parent as public (or grandparent despite not directly depending on it) because the parent declaring the grandparent public makes the compiler think its also public for you

    This is because we only pass --extern for direct dependencies and visibility is tied to --extern.

    Possible solutions (without knowing the compiler side)

    • The compiler check the graph of externs to see what the effective visibility is for the current crate being compiled
    • Cargo pass visibility for the entire dependency graph, separate from --extern

    @ehuss I think you were involved in the conversations on the cargo/rustc handshake for this. Any thoughts on how to move this forward?

  9. linyihai commented on Dec 29, 2023

    @linyihai
    ContributorAuthor

    #119428 (comment)

    This scenario is more common and worth writing a test case for. I plan to add this in rust-lang/cargo#13183

  10. ehuss commented on Dec 29, 2023

    @ehuss
    Contributor

    I believe that is a rustc issue. It looks like it is checking the wrong thing when it is checking the public-status. It is looking at the DefId of the item, which is where the item is defined. However, I don't think that is correct in the face of re-exports. It should be checking the public status of the crate of the Use item from where it was re-exported. I don't know much about the DefIdVisitor or the visibility analysis stuff in general, so I can't really suggest a different approach. If you want to work on fixing it yourself, you'll probably want to ask the compiler team for assistance in coming up with a different implementation strategy.

    I don't think it is necessary to pass more visibility data to rustc. It should be able to compute the effective visibility from the information it has.

    It sounds like there needs to be a more principled definition of what it means to "leak" a private item, since it seems like there are some edge cases that the current approach doesn't handle correctly.

  11. added
    needs-triageThis issue may need triage. Remove when done. See docs forge.rust-lang.org/release/issue-triaging
    on Dec 30, 2023
  12. transferred this issue fromrust-lang/cargoon Dec 30, 2023
  13. epage commented on Dec 30, 2023

    @epage
    Contributor

    If checking the visibility from where it is re-expoerted from has problems, another idea (without any compiler knowledge) is to compute the effective visibility between the current crate and the DefId, by walking the tree of public dependencies to see if there is a path that can reach it.

  14. apiraino commented on Jan 17, 2024

    @apiraino
    Contributor

    Nominating for a compiler meeting discussion

    @rustbot label +I-compiler-nominated +T-compiler

  15. 11 remaining items

  16. tgross35 commented on Feb 23, 2025

    @tgross35
    Member

    I have not checked the case in the top post, but some of this has been improved with #135501.

  17. epage commented on Feb 26, 2025

    @epage
    Contributor

    Thanks @tgross35 !

    Stepping through the different cases (with an in-dev cargo tree feature)

    Original
    $ nargo tree
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    └── pub_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/pub_dep)
        └── parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
            └── grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    $ nargo tree --depth public --workspace
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
    grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    
    parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
    └── grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    
    pub_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/pub_dep)
    └── parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep) (*)
    
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    $ cargo +nightly check --all
        Checking grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
        Checking parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
        Checking pub_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/pub_dep)
        Checking simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    warning: type `FromPriv` from private dependency 'grandparent_dep' in public interface
     --> src/lib.rs:3:1
      |
    3 | pub fn use_priv(_: pub_dep::FromPriv) {}
      | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: `#[warn(exported_private_dependencies)]` on by default
    
    warning: `simple` (lib) generated 1 warning
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.10s
    • Correctly warn on private dep that re-exported an item from many layers of public deps
    Original variant 1
    $ git diff
    diff --git i/crates/parent_dep/Cargo.toml w/crates/parent_dep/Cargo.toml
    index 5f27dc7..a73d7cc 100644
    --- i/crates/parent_dep/Cargo.toml
    +++ w/crates/parent_dep/Cargo.toml
    @@ -6,4 +6,4 @@ version = "0.1.0"
     edition = "2021"
    
     [dependencies]
    -grandparent_dep = { version = "0.1.0", path = "../grandparent_dep", public = true }
    +grandparent_dep = { version = "0.1.0", path = "../grandparent_dep", public = false }
    
    $ nargo tree
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    └── pub_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/pub_dep)
        └── parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
            └── grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    
    $ nargo tree --depth public --workspace
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
    grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    
    parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
    
    pub_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/pub_dep)
    └── parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep) (*)
    
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    
    $ cargo +nightly check --all
        Checking grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
        Checking parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
    warning: type `FromPriv` from private dependency 'grandparent_dep' in public interface
     --> crates/parent_dep/src/lib.rs:4:1
      |
    4 | pub fn use_priv(_: grandparent_dep::FromPriv) {}
      | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: `#[warn(exported_private_dependencies)]` on by default
    
    warning: `parent_dep` (lib) generated 1 warning
        Checking pub_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/pub_dep)
    warning: type `FromPriv` from private dependency 'grandparent_dep' in public interface
     --> crates/pub_dep/src/lib.rs:4:1
      |
    4 | pub fn use_priv(_: parent_dep::FromPriv) {}
      | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: `#[warn(exported_private_dependencies)]` on by default
    
    warning: `pub_dep` (lib) generated 1 warning
        Checking simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    warning: type `FromPriv` from private dependency 'grandparent_dep' in public interface
     --> src/lib.rs:3:1
      |
    3 | pub fn use_priv(_: pub_dep::FromPriv) {}
      | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: `#[warn(exported_private_dependencies)]` on by default
    
    warning: `simple` (lib) generated 1 warning
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
    • Warn on all dependents when one layer of transitive deps is private
    Original variant 2
    $ git diff
    diff --git i/Cargo.toml w/Cargo.toml
    index a9012ec..8ac91a5 100644
    --- i/Cargo.toml
    +++ w/Cargo.toml
    @@ -9,4 +9,4 @@ version = "0.1.0"
     edition = "2021"
    
     [dependencies]
    -pub_dep = { version = "0.1.0", path = "./crates/pub_dep" }
    +pub_dep = { version = "0.1.0", path = "./crates/pub_dep", public = true }
    diff --git i/crates/parent_dep/Cargo.toml w/crates/parent_dep/Cargo.toml
    index 5f27dc7..a73d7cc 100644
    --- i/crates/parent_dep/Cargo.toml
    +++ w/crates/parent_dep/Cargo.toml
    @@ -6,4 +6,4 @@ version = "0.1.0"
     edition = "2021"
    
     [dependencies]
    -grandparent_dep = { version = "0.1.0", path = "../grandparent_dep", public = true }
    +grandparent_dep = { version = "0.1.0", path = "../grandparent_dep", public = false }
    
    $ nargo tree
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    └── pub_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/pub_dep)
        └── parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
            └── grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    
    $ nargo tree --depth public --workspace
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
    grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    
    parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
    
    pub_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/pub_dep)
    └── parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep) (*)
    
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    └── pub_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/pub_dep) (*)
    
    $ cargo +nightly check --all
        Checking grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
        Checking parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
    warning: type `FromPriv` from private dependency 'grandparent_dep' in public interface
     --> crates/parent_dep/src/lib.rs:4:1
      |
    4 | pub fn use_priv(_: grandparent_dep::FromPriv) {}
      | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: `#[warn(exported_private_dependencies)]` on by default
    
    warning: `parent_dep` (lib) generated 1 warning
        Checking pub_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/pub_dep)
    warning: type `FromPriv` from private dependency 'grandparent_dep' in public interface
     --> crates/pub_dep/src/lib.rs:4:1
      |
    4 | pub fn use_priv(_: parent_dep::FromPriv) {}
      | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: `#[warn(exported_private_dependencies)]` on by default
    
    warning: `pub_dep` (lib) generated 1 warning
        Checking simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    warning: type `FromPriv` from private dependency 'grandparent_dep' in public interface
     --> src/lib.rs:3:1
      |
    3 | pub fn use_priv(_: pub_dep::FromPriv) {}
      | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: `#[warn(exported_private_dependencies)]` on by default
    
    warning: `simple` (lib) generated 1 warning
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.13s
    • Warn on all dependents when one layer of transitive deps is private
    Diamond
    $ nargo tree --workspace
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    ├── left_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/left_dep)
    │   └── priv_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/priv_dep)
    └── right_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/right_dep)
        └── priv_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/priv_dep)
    
    $ nargo tree --depth public --workspace
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s
    left_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/left_dep)
    └── priv_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/priv_dep)
    
    priv_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/priv_dep)
    
    right_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/right_dep)
    └── priv_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/priv_dep)
    
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    
    $ cargo +nightly check --all
        Checking priv_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/priv_dep)
        Checking left_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/left_dep)
        Checking right_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/right_dep)
        Checking simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    warning: type `FromPriv` from private dependency 'priv_dep' in public interface
     --> src/lib.rs:2:5
      |
    2 |     pub fn use_priv(_: left_dep::FromPriv) {}
      |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: `#[warn(exported_private_dependencies)]` on by default
    
    warning: type `FromPriv` from private dependency 'priv_dep' in public interface
     --> src/lib.rs:5:5
      |
    5 |     pub fn use_priv(_: right_dep::FromPriv) {}
      |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
    warning: `simple` (lib) generated 2 warnings
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.08s
    • Correctly warn on multiple private deps that re-exported an item from a public dep
    Diamond variant 1
    $ git diff
    diff --git i/crates/left_dep/Cargo.toml w/crates/left_dep/Cargo.toml
    index 41f2079..41cd713 100644
    --- i/crates/left_dep/Cargo.toml
    +++ w/crates/left_dep/Cargo.toml
    @@ -6,4 +6,4 @@ version = "0.1.0"
     edition = "2021"
    
     [dependencies]
    -priv_dep = { version = "0.1.0", path = "../priv_dep", public = true }
    +priv_dep = { version = "0.1.0", path = "../priv_dep", public = false }
    
    $ nargo tree
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    ├── left_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/left_dep)
    │   └── priv_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/priv_dep)
    └── right_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/right_dep)
        └── priv_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/priv_dep)
    
    $ nargo tree --depth public --workspace
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.14s
    left_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/left_dep)
    
    priv_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/priv_dep)
    
    right_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/right_dep)
    └── priv_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/priv_dep)
    
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    
    $ cargo +nightly check --all
        Checking priv_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/priv_dep)
        Checking right_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/right_dep)
        Checking left_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/left_dep)
    warning: type `FromPriv` from private dependency 'priv_dep' in public interface
     --> crates/left_dep/src/lib.rs:3:1
      |
    3 | pub fn use_priv(_: priv_dep::FromPriv) {}
      | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: `#[warn(exported_private_dependencies)]` on by default
    
    warning: `left_dep` (lib) generated 1 warning
        Checking simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    warning: type `FromPriv` from private dependency 'priv_dep' in public interface
     --> src/lib.rs:2:5
      |
    2 |     pub fn use_priv(_: left_dep::FromPriv) {}
      |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: `#[warn(exported_private_dependencies)]` on by default
    
    warning: type `FromPriv` from private dependency 'priv_dep' in public interface
     --> src/lib.rs:5:5
      |
    5 |     pub fn use_priv(_: right_dep::FromPriv) {}
      |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
    warning: `simple` (lib) generated 2 warnings
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s
    • Correctly warn on private dep
    • Correctly warn on multiple private deps that re-exported an item from either a public or private path to a dep
    Diamond variant 2
    $ git diff
    diff --git i/Cargo.toml w/Cargo.toml
    index c47b24d..d9aeedb 100644
    --- i/Cargo.toml
    +++ w/Cargo.toml
    @@ -9,5 +9,5 @@ version = "0.1.0"
     edition = "2021"
    
     [dependencies]
    -left_dep = { version = "0.1.0", path = "./crates/left_dep" }
    -right_dep = { version = "0.1.0", path = "./crates/right_dep" }
    +left_dep = { version = "0.1.0", path = "./crates/left_dep", public = true }
    +right_dep = { version = "0.1.0", path = "./crates/right_dep", public = true }
    diff --git i/crates/left_dep/Cargo.toml w/crates/left_dep/Cargo.toml
    index 41f2079..41cd713 100644
    --- i/crates/left_dep/Cargo.toml
    +++ w/crates/left_dep/Cargo.toml
    @@ -6,4 +6,4 @@ version = "0.1.0"
     edition = "2021"
    
     [dependencies]
    -priv_dep = { version = "0.1.0", path = "../priv_dep", public = true }
    +priv_dep = { version = "0.1.0", path = "../priv_dep", public = false }
    
    $ nargo tree
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    ├── left_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/left_dep)
    │   └── priv_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/priv_dep)
    └── right_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/right_dep)
        └── priv_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/priv_dep)
    
    $ nargo tree --depth public --workspace
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
    left_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/left_dep)
    
    priv_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/priv_dep)
    
    right_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/right_dep)
    └── priv_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/priv_dep)
    
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    ├── left_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/left_dep) (*)
    └── right_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/right_dep) (*)
    
    $ cargo +nightly check --all
    warning: type `FromPriv` from private dependency 'priv_dep' in public interface
     --> crates/left_dep/src/lib.rs:3:1
      |
    3 | pub fn use_priv(_: priv_dep::FromPriv) {}
      | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: `#[warn(exported_private_dependencies)]` on by default
    
    warning: `left_dep` (lib) generated 1 warning
        Checking simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s
    • Correctly warn on private dep
    • No warning when using an item from a public dep that was re-export from a private dep (despite Original variant 2 doing so)
    Direct and transitive
    $ nargo tree
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    ├── grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    └── parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
        └── grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    $ nargo tree --depth public --workspace
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
    grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    
    parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
    └── grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    
    $ cargo +nightly check --all
        Checking grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
        Checking parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
        Checking simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    warning: type `FromPriv` from private dependency 'grandparent_dep' in public interface
     --> src/lib.rs:2:5
      |
    2 |     pub fn use_priv(_: parent_dep::FromPriv) {}
      |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: `#[warn(exported_private_dependencies)]` on by default
    
    warning: type `FromPriv` from private dependency 'grandparent_dep' in public interface
     --> src/lib.rs:5:5
      |
    5 |     pub fn use_priv(_: grandparent_dep::FromPriv) {}
      |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
    warning: `simple` (lib) generated 2 warnings
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.10s
    • Correctly warn on multiple private deps that directly use or use a re-export of an item from a public dep
    Direct and transitive variant 1
    $ git diff
    diff --git i/crates/parent_dep/Cargo.toml w/crates/parent_dep/Cargo.toml
    index 5f27dc7..a73d7cc 100644
    --- i/crates/parent_dep/Cargo.toml
    +++ w/crates/parent_dep/Cargo.toml
    @@ -6,4 +6,4 @@ version = "0.1.0"
     edition = "2021"
    
     [dependencies]
    -grandparent_dep = { version = "0.1.0", path = "../grandparent_dep", public = true }
    +grandparent_dep = { version = "0.1.0", path = "../grandparent_dep", public = false }
    
    $ nargo tree
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    ├── grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    └── parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
        └── grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    
    $ nargo tree --depth public --workspace
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
    grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    
    parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
    
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    
    $ cargo +nightly check --all
    warning: type `FromPriv` from private dependency 'grandparent_dep' in public interface
     --> crates/parent_dep/src/lib.rs:4:1
      |
    4 | pub fn use_priv(_: grandparent_dep::FromPriv) {}
      | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: `#[warn(exported_private_dependencies)]` on by default
    
    warning: `parent_dep` (lib) generated 1 warning
        Checking simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    warning: type `FromPriv` from private dependency 'grandparent_dep' in public interface
     --> src/lib.rs:2:5
      |
    2 |     pub fn use_priv(_: parent_dep::FromPriv) {}
      |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: `#[warn(exported_private_dependencies)]` on by default
    
    warning: type `FromPriv` from private dependency 'grandparent_dep' in public interface
     --> src/lib.rs:5:5
      |
    5 |     pub fn use_priv(_: grandparent_dep::FromPriv) {}
      |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    
    warning: `simple` (lib) generated 2 warnings
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.05s
    • Correctly warn on private dep
    • Correctly warn on multiple private deps that directly use or use a re-export of an item from a public dep
    Direct and transitive variant 2
    $ git diff
    diff --git i/Cargo.toml w/Cargo.toml
    index ed242c1..55c073c 100644
    --- i/Cargo.toml
    +++ w/Cargo.toml
    @@ -9,5 +9,5 @@ version = "0.1.0"
     edition = "2021"
    
     [dependencies]
    -grandparent_dep = { version = "0.1.0", path = "crates/grandparent_dep" }
    -parent_dep = { version = "0.1.0", path = "./crates/parent_dep" }
    +grandparent_dep = { version = "0.1.0", path = "crates/grandparent_dep", public = true }
    +parent_dep = { version = "0.1.0", path = "./crates/parent_dep", public = true }
    diff --git i/crates/parent_dep/Cargo.toml w/crates/parent_dep/Cargo.toml
    index 5f27dc7..a73d7cc 100644
    --- i/crates/parent_dep/Cargo.toml
    +++ w/crates/parent_dep/Cargo.toml
    @@ -6,4 +6,4 @@ version = "0.1.0"
     edition = "2021"
    
     [dependencies]
    -grandparent_dep = { version = "0.1.0", path = "../grandparent_dep", public = true }
    +grandparent_dep = { version = "0.1.0", path = "../grandparent_dep", public = false }
    
    $ nargo tree
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    ├── grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    └── parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
        └── grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    
    $ nargo tree --depth public --workspace
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.12s
    grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    
    parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep)
    
    simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
    ├── grandparent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/grandparent_dep)
    └── parent_dep v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport/crates/parent_dep) (*)
    
    $ cargo +nightly check --all
    warning: type `FromPriv` from private dependency 'grandparent_dep' in public interface
     --> crates/parent_dep/src/lib.rs:4:1
      |
    4 | pub fn use_priv(_: grandparent_dep::FromPriv) {}
      | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
      |
      = note: `#[warn(exported_private_dependencies)]` on by default
    
    warning: `parent_dep` (lib) generated 1 warning
        Checking simple v0.1.0 (/home/epage/src/personal/dump/recursive_pub_reexport)
        Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.04s
    • Correctly warn on private dep
    • No warning when using an item from a public dep that was re-export from a private dep (despite Original variant 2 doing so)
  18. epage commented on Feb 26, 2025

    @epage
    Contributor

    Diamond variant 2

    Direct and transitive variant 2

    • No warning when using an item from a public dep that was re-export from a private dep (despite Original variant 2 doing so)

    I'm not sure if this should be a blocker for stabilization. The type is public in your API, just the path used to access it isn't.

    Original variant 2

    • Warn on all dependents when one layer of transitive deps is private

    I worry this will make adoption of this feature more difficult.

    Take cargo -Zminimal-versions. Cargo has been hesitant to stabilize it because someone using it will need to first get their dependency into compliance before they can, rather than being something they can act on all of their own (granted, there is a workaround of no-op deps to force versions higher).

    Applying that here, adoption of private dependencies will need to happen from the bottom up unless people workaround non-compliant dependencies by adding a direct public dependency on their transitive dependency (leveraging the "Direct and transitive variant 2" case).

    If the lint could make the assumption that everything public in a dependency comes from a public transitive dependency (i.e. only warn if I reference something from a item path that is not public), this might smooth out adoption. Whether a dependency intended for a transitive dependency to be public or not won't be noticeable from the noise until it has adopted public. Once it does, in theory, things should be good. If they do publish with the lint firing, I guess it would help you from accidentally depending on something you shouldn't but that will likely only help in some cases.

  19. ShoyuVanilla commented on Feb 28, 2025

    @ShoyuVanilla
    Member

    I've been working on this for some days and managed to create a PoC that fixes the remaining part of this issue; the access path things.

    I made the following design choices:

    • Warn only when a re-exported item is accessed through a private dependency, and do not warn when accessed through a public dependency.
      • This fixes "No warning when using an item from a public dep that was re-export from a private dep (despite Original variant 2 doing so)"
    • Avoid transitive privacy warnings, i.e. warn iff the "first" layer of access path is a private dependency. As @epage mentioned in the above comment, enforcing transitive privacy would make adoption unnecessarily difficult in transitional state.
      • This fixes "Warn on all dependents when one layer of transitive deps is private"

    However, I'm still unsure about some implementation details, so I’ve asked a question on Zulip: Question about tracking crate provenance in PartialRes.

    I'd really appreciate any feedback on this

  20. mladedav commented on Jul 21, 2025

    @mladedav
    Contributor

    If I understand the discussion correctly, there isn't a consensus yet about what to do with dependencies that are transitively public but are used/reexported from a private dependency.

    There's currently a test that summarizes it:

    Test case

    // A shared dependency, where the public side reexports the same item as a
    // direct private dependency.
    //
    //          shared_direct_private
    //                  /\
    //        (public) /  | (PRIVATE)
    //                /   |
    //        reexport    |
    //                \   |
    //        (public) \  /
    //                  \/
    //                shared
    //
    
    #![crate_type = "lib"]
    #![deny(exported_private_dependencies)]
    
    extern crate shared;
    extern crate reexport;
    
    // FIXME: Should this trigger?
    //
    // One could make an argument that I said I want "reexport" to be public, and
    // since "reexport" says "shared_direct_private" is public, then it should
    // transitively be public for me. However, as written, this is explicitly
    // referring to a dependency that is marked "private", which I think is
    // confusing.
    pub fn leaks_priv() -> shared::Shared {
        shared::Shared
    }
    
    pub fn leaks_pub() -> reexport::Shared {
        reexport::Shared
    }

    I would expect it to work in a way where I can reexport anything from a public dependency but cannot reexport anything from a private dependency, regardless of what other dependencies I might depend on directly or indirectly.

    For one, if only direct dependencies matter, I have a better visibility into what is my public dependency. I might have something from a transitive dependency in my public API which was reexported through a direct dependency, but for all intents and purposes, it could have just as well be created by the direct dependency. The alternative is that I might be surprised to find out I have an unexpected package in my public API. I think something like the motivating example in the RFC could conceivably happen to some people where they accidentally put e.g. serde_json::Error into their public API but it will not be linted if it is public transitively.

    Cargo could also add a lint about declaring private dependencies that are actually public if we went with non-path privacy. But this feels like forcing people to have public dependencies they don't really want.

    That also ties into the possible issues with adoption. If the only thing a given crate must pay attention to is its direct dependencies, any crate can adopt and benefit from this feature whenever they like without having to wait for their dependencies to adopt it too.

    The only relevant thing I found in the RFC was that public transitive dependencies are also considered public dependencies and private dependencies is everything else. But I suspect that is mostly kept there from the original version where these distinctions were made for the resolver logic.


    I was originally thinking it would be path-sensitive, so that they would be different (bar::Thing would be OK, but baz::Thing would not). Then, rustc would only care about the visibility of direct dependencies. However, thinking about that more, I'm feeling like I might be wrong. Either way, I can imagine this has the potential to be confusing.

    @ehuss do you remember why you thought that doing this by paths might be wrong?

  21. ehuss commented on Oct 2, 2025

    @ehuss
    Contributor

    do you remember why you thought that doing this by paths might be wrong?

    I don't remember exactly, but I suspect I was just waffling on whether or not to allow publicly exposing bar::Thing. Thinking about it right now, I would probably lean towards allowing it (which would require being path-sensitive). Implementation-wise I don't know if that is feasible 😦 .

  22. epage commented on May 25, 2026

    @epage
    Contributor

    @Mark-Simulacrum This is the public/private dep issue I mentioned.

  23. mladedav commented on Jun 30, 2026

    @mladedav
    Contributor

    @Eh2406 pinging you here because of this comment. If we changed the lint to work based on where a name is imported from instead of where the underlying type is defined, the transitive property would not automatically hold anymore.

  24. epage commented on Jun 30, 2026

    @epage
    Contributor

    While the lint won't be directly enforcing that public = true is set through a series a reexports, the lint will fire at each layer of the reexports, helping to ensure that the flag is set correctly through the tree. So while the lint won't enforce transitive correctness, the over all workflow will.

  25. mladedav commented on Jul 1, 2026

    @mladedav
    Contributor

    Yes, but the main difference I see there is that in a chain A -(pub)-> B -(pub)-> C where A also privately depends on C, it cannot accidentally make larger part of the C API public than what B already exported.

    And in A -(pub)-> B -(pub)-> C -(pub)-> D, if B doesn't re-export anything originating in D, then for all intents and purposes, D isn't a public dependency of A. So at that point, it's not a recursive transitive closure.

  26. epage commented on Jul 2, 2026

    @epage
    Contributor

    Yes, but the main difference I see there is that in a chain A -(pub)-> B -(pub)-> C where A also privately depends on C, it cannot accidentally make larger part of the C API public than what B already exported.

    I think it is correct that A can only use parts of C in its public API that came through B; A cannot expand the public scope of Cs API.

    And in A -(pub)-> B -(pub)-> C -(pub)-> D, if B doesn't re-export anything originating in D, then for all intents and purposes, D isn't a public dependency of A. So at that point, it's not a recursive transitive closure.

    I suspect there is a communication gap. When @Eh2406 talked about it public deps being recursive, it was specifically in the context of a reply about Deref though a chain like A -(pub)-> B -(pub)-> C. @Eh2406 is also on the Cargo team and answering on a Cargo issue and likely had a Cargo viewpoint in that reply. Cargo does not have any extra visibility into the subsets viewed. cargo tree -e public and eventually cargo doc will recursively walk all public dependencies. cargo add will auto-select versions from public dependencies as part of an effort to align major versions. This is dependent on the intent of the lint but not on the exact implementation (whether it checks where you import from or traced the reexports and it was valid at each level). In my time working on this, we've never considered having A -(pub)-> B -(pub)-> C; A -> C imply A -> C is public.

    All that said, if narrowing of public APIs happens in ways that an APi is no longer public (like D in your case), then yes, Cargo will still treat D as public while rustcs lint shoudn't allow D to be used in As public API. Depending on what question you are asking, D is still relevant. For example, "if D makes a breaking change, what is the impact", and the answer is "it is a breaking change to upgrade C -> D which is a breaking change to B- > C which is a breaking change to A -> B. Though the breaking APi change isn't transitively relevant, the version number bumps are. Even for the questions where D isn't relevant (like to As rust API), the scope and impact of including D is likely small enough to not be an issue.

  27. mladedav commented on Jul 8, 2026

    @mladedav
    Contributor

    Sorry for any confusion I might have caused then. I must have misunderstood something and thought this was still undecided and the current lint implementation is still basing the decisions on where the item was defined and not where it was imported from, which does implement this as a transitive closure.

    I take it then this is just waiting for someone to redo the lint implementation.

    Thanks for the explanation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-lintsArea: Lints (warnings about flaws in source code) such as unused_mut.A-visibilityArea: Visibility / privacyC-bugCategory: This is a bug.F-public_private_dependenciesfeature: public_private_dependenciesL-exported_private_dependenciesLint: exported_private_dependenciesT-compilerRelevant to the compiler team, which will review and decide on the PR/issue.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions