Skip to content

Add a new lint UNCONSTRUCTABLE_PUB_STRUCT to detect unconstructable public structs#146440

Draft
mu001999 wants to merge 4 commits into
rust-lang:mainfrom
mu001999-contrib:lint/unconstructible_pub_struct
Draft

Add a new lint UNCONSTRUCTABLE_PUB_STRUCT to detect unconstructable public structs#146440
mu001999 wants to merge 4 commits into
rust-lang:mainfrom
mu001999-contrib:lint/unconstructible_pub_struct

Conversation

@mu001999
Copy link
Copy Markdown
Member

@mu001999 mu001999 commented Sep 11, 2025

View all comments

Add a new lint UNCONSTRUCTABLE_PUB_STRUCT to detect unconstructable public structs, based on the following observations:

  1. A public struct with private field(s) cannot be directly constructed from external crates.
  2. Associated functions with a receiver require an already constructed value of type Self.
  3. Therefore, public structs with private fields and their associated functions that take a receiver can be included in the local crate's dead code analysis.
  4. If a public struct with private fields cannot be constructed in any reachable code path, it could be considered dead.

And, the lint UNCONSTRUCTABLE_PUB_STRUCT won't affect the dead-code lint's result, but allow or deny dead-code lint may affect the result of UNCONSTRUCTABLE_PUB_STRUCT

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Sep 11, 2025
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Sep 11, 2025

r? @davidtwco

rustbot has assigned @davidtwco.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@mu001999 mu001999 changed the title Implement lint unconstructible_pub_struct Add a new lint UNCONSTRUCTIBLE_PUB_STRUCT to detect unconstructible public structs Sep 11, 2025
@juntyr
Copy link
Copy Markdown
Contributor

juntyr commented Sep 11, 2025

Would the lint fire on token structs that are public, have private fields, have no public constructor method, but expose a limited number of pre-constructed objects, e.g. through a static that contains an optional token?

@mu001999
Copy link
Copy Markdown
Member Author

a static that contains an optional token

won't fire like private types used in such places

@mu001999 mu001999 force-pushed the lint/unconstructible_pub_struct branch from 480b1d7 to 021712b Compare September 15, 2025 07:39
@davidtwco
Copy link
Copy Markdown
Member

Nominating for t-lang to decide whether we want this lint, then I'll review the implementation.

Also, s/unconstructible/unconstructable.

@davidtwco davidtwco added the I-lang-nominated Nominated for discussion during a lang team meeting. label Sep 17, 2025
@mu001999 mu001999 changed the title Add a new lint UNCONSTRUCTIBLE_PUB_STRUCT to detect unconstructible public structs Add a new lint UNCONSTRUCTABLE_PUB_STRUCT to detect unconstructable public structs Sep 17, 2025
@traviscross traviscross added the P-lang-drag-2 Lang team prioritization drag level 2.https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang. label Sep 17, 2025
@bors

This comment was marked as resolved.

@mu001999 mu001999 force-pushed the lint/unconstructible_pub_struct branch from 6075d81 to 497ad71 Compare October 18, 2025 09:57
@rustbot

This comment has been minimized.

@bors

This comment was marked as resolved.

@mu001999 mu001999 force-pushed the lint/unconstructible_pub_struct branch from 497ad71 to b3b3a05 Compare October 19, 2025 05:22
@rustbot
Copy link
Copy Markdown
Collaborator

rustbot commented Oct 19, 2025

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@Darksonn
Copy link
Copy Markdown
Member

Does this trigger on structs that are intended only as marker types in generic parameters?

@mu001999
Copy link
Copy Markdown
Member Author

mu001999 commented Oct 24, 2025

Does this trigger on structs that are intended only as marker types in generic parameters?

Such types usually have intended private unit fields and this won't trigger on them. This will only trigger types with trivial fields but not be used or cannot be constructed.

@nikomatsakis
Copy link
Copy Markdown
Contributor

Are there examples of code in the wild that is affected by this lint?

@scottmcm
Copy link
Copy Markdown
Member

scottmcm commented Nov 5, 2025

I worry about completeness here. If I have something like pub struct Foo(pub [u8]); it's not "constructible" in a sense, but it might be entirely expected anyway. What if there's something only created via slice_from_raw_parts and pointer casts to get &MyType?


Musing: what if this was signature-based, say? Could it be phrased as "why is this pub when it's not in a signature; maybe it should be pub(crate)?" or something?

@joshtriplett
Copy link
Copy Markdown
Member

We discussed this in today's @rust-lang/lang meeting.

Was there any particular motivating use case that led to proposing this? Can you point to some code that motivated this?

We wondered about potential corner cases, notably structs that are only constructed in unsafe code. For instance, something using repr(C) or repr(transparent) that's constructed via transmute, or a dynamically sized type that requires unsafe code to construct. We're hoping those can be handled and won't produce false positives.

Once those are addressed, we'd like to see the (triaged) results of a crater run with this lint marked as deny-by-default, so we can get an idea of 1) how widespread this is and 2) whether this catches any issues.

@traviscross traviscross added I-lang-radar Items that are on lang's radar and will need eventual work or consideration. and removed I-lang-nominated Nominated for discussion during a lang team meeting. labels Nov 5, 2025
@traviscross
Copy link
Copy Markdown
Contributor

Please renominate for lang when these answers are available.

@mu001999 mu001999 marked this pull request as draft November 6, 2025 03:18
@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 6, 2025
@mu001999 mu001999 force-pushed the lint/unconstructible_pub_struct branch from b3b3a05 to a1aaeb6 Compare November 12, 2025 01:42
@bors
Copy link
Copy Markdown
Collaborator

bors commented Jan 7, 2026

☔ The latest upstream changes (presumably #150645) made this pull request unmergeable. Please resolve the merge conflicts.

1 similar comment
@rust-bors
Copy link
Copy Markdown
Contributor

rust-bors Bot commented Jan 7, 2026

☔ The latest upstream changes (presumably #150645) made this pull request unmergeable. Please resolve the merge conflicts.

@mu001999
Copy link
Copy Markdown
Member Author

mu001999 commented Feb 28, 2026

I think this PR needs further design regarding its interaction with the lint dead_code.

@rustbot blocked

@mu001999 mu001999 closed this Feb 28, 2026
@rustbot rustbot removed the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Feb 28, 2026
@mu001999 mu001999 reopened this Feb 28, 2026
@rustbot rustbot added the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Feb 28, 2026
@rustbot rustbot added S-blocked Status: Blocked on something else such as an RFC or other implementation work. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Mar 21, 2026
@Dylan-DPC Dylan-DPC added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-blocked Status: Blocked on something else such as an RFC or other implementation work. labels May 1, 2026
@mu001999 mu001999 force-pushed the lint/unconstructible_pub_struct branch from 4dc0f12 to 5ec9bbc Compare May 14, 2026 16:28
@mu001999
Copy link
Copy Markdown
Member Author

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label May 14, 2026
rust-bors Bot pushed a commit that referenced this pull request May 14, 2026
…uct, r=<try>

Add a new lint `UNCONSTRUCTABLE_PUB_STRUCT` to detect unconstructable public structs
@rust-bors

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@mu001999 mu001999 force-pushed the lint/unconstructible_pub_struct branch from 5ec9bbc to e5cd4a2 Compare May 14, 2026 16:38
@rust-log-analyzer
Copy link
Copy Markdown
Collaborator

The job aarch64-gnu-llvm-21-2 failed! Check out the build log: (web) (plain enhanced) (plain)

Click to see the possible cause of the failure (guessed by this bot)
Executing "/scripts/stage_2_test_set2.sh"
+ /scripts/stage_2_test_set2.sh
PR_CI_JOB set; skipping tidy
+ '[' 1 == 1 ']'
+ echo 'PR_CI_JOB set; skipping tidy'
+ SKIP_TIDY='--skip tidy'
+ ../x.py --stage 2 test --skip tidy --skip tests --skip coverage-map --skip coverage-run --skip library --skip tidyselftest
##[group]Building bootstrap
    Finished `dev` profile [unoptimized] target(s) in 0.04s
##[endgroup]
downloading https://static.rust-lang.org/dist/2026-04-14/rustfmt-nightly-aarch64-unknown-linux-gnu.tar.xz
---
[BUILD] mini_core
error: struct `DynMetadata` is never constructed
##[error]  --> example/mini_core.rs:30:12
   |
30 | pub struct DynMetadata<Dyn: PointeeSized> {
   |            ^^^^^^^^^^^
   |
   = note: this `pub` struct is unconstructable externally and never constructed locally, so consider providing a pub constructor or removing this
   = note: `#[deny(unconstructable_pub_struct)]` (part of `#[deny(unused)]`) on by default

error: aborting due to 1 previous error

"/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-codegen/cg_clif/dist/rustc-clif" "-Zannotate-moves" "-Zrandomize-layout" "-Zunstable-options" "--check-cfg=cfg(bootstrap)" "-Zmacro-backtrace" "-Csplit-debuginfo=off" "-Clink-arg=-L/usr/lib/llvm-21/lib" "-Cllvm-args=-import-instr-limit=10" "-Clink-args=-Wl,-z,origin" "-Clink-args=-Wl,-rpath,$ORIGIN/../lib" "-Alinker-messages" "-L" "crate=/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-codegen/cg_clif/build/example" "--out-dir" "/checkout/obj/build/aarch64-unknown-linux-gnu/stage2-codegen/cg_clif/build/example" "-Cdebuginfo=2" "--target" "aarch64-unknown-linux-gnu" "-Cpanic=abort" "--check-cfg=cfg(jit)" "--edition=2024" "example/mini_core.rs" "--crate-type" "lib,dylib" exited with status ExitStatus(unix_wait_status(256))
Command `/checkout/obj/build/aarch64-unknown-linux-gnu/stage0/bin/cargo run -Zwarnings --target aarch64-unknown-linux-gnu -Zbinary-dep-depinfo -j 4 -Zroot-dir=/checkout --locked --color=always --profile=release --manifest-path /checkout/compiler/rustc_codegen_cranelift/build_system/Cargo.toml -- test --download-dir /checkout/obj/build/cg_clif_download --out-dir /checkout/obj/build/aarch64-unknown-linux-gnu/stage2-codegen/cg_clif --no-unstable-features --use-backend cranelift --sysroot llvm --skip-test testsuite.extended_sysroot [workdir=/checkout/compiler/rustc_codegen_cranelift]` failed with exit code 1
Created at: src/bootstrap/src/core/build_steps/test.rs:3955:25
Executed at: src/bootstrap/src/core/build_steps/test.rs:4000:26

--- BACKTRACE vvv
   0: std::backtrace_rs::backtrace::libunwind::trace
             at /rustc/ef0fb8a2563200e322fa4419f09f65a63742038c/library/std/src/../../backtrace/src/backtrace/libunwind.rs:117:9
   1: std::backtrace_rs::backtrace::trace_unsynchronized::<<std::backtrace::Backtrace>::create::{closure#0}>
             at /rustc/ef0fb8a2563200e322fa4419f09f65a63742038c/library/std/src/../../backtrace/src/backtrace/mod.rs:66:14
   2: <std::backtrace::Backtrace>::create
             at /rustc/ef0fb8a2563200e322fa4419f09f65a63742038c/library/std/src/backtrace.rs:331:13
   3: <bootstrap::utils::exec::DeferredCommand>::finish_process
             at /checkout/src/bootstrap/src/utils/exec.rs:939:17
   4: <bootstrap::utils::exec::DeferredCommand>::wait_for_output::<&bootstrap::utils::exec::ExecutionContext>
             at /checkout/src/bootstrap/src/utils/exec.rs:831:21
   5: <bootstrap::utils::exec::ExecutionContext>::run
             at /checkout/src/bootstrap/src/utils/exec.rs:741:45
   6: <bootstrap::utils::exec::BootstrapCommand>::run::<&bootstrap::core::builder::Builder>
             at /checkout/src/bootstrap/src/utils/exec.rs:339:27
   7: <bootstrap::core::build_steps::test::CodegenCranelift as bootstrap::core::builder::Step>::run
             at /checkout/src/bootstrap/src/core/build_steps/test.rs:4000:26
   8: <bootstrap::core::builder::Builder>::ensure::<bootstrap::core::build_steps::test::CodegenCranelift>
             at /checkout/src/bootstrap/src/core/builder/mod.rs:1595:36
   9: <bootstrap::core::build_steps::test::CodegenCranelift as bootstrap::core::builder::Step>::make_run
             at /checkout/src/bootstrap/src/core/build_steps/test.rs:3941:17
  10: <bootstrap::core::builder::StepDescription>::maybe_run
             at /checkout/src/bootstrap/src/core/builder/mod.rs:476:13
  11: bootstrap::core::builder::cli_paths::match_paths_to_steps_and_run
             at /checkout/src/bootstrap/src/core/builder/cli_paths.rs:141:22
  12: <bootstrap::core::builder::Builder>::run_step_descriptions
             at /checkout/src/bootstrap/src/core/builder/mod.rs:1138:9
  13: <bootstrap::core::builder::Builder>::execute_cli
             at /checkout/src/bootstrap/src/core/builder/mod.rs:1117:14
  14: <bootstrap::Build>::build
             at /checkout/src/bootstrap/src/lib.rs:803:25
  15: bootstrap::main
             at /checkout/src/bootstrap/src/bin/main.rs:130:11
  16: <fn() as core::ops::function::FnOnce<()>>::call_once
             at /rustc/ef0fb8a2563200e322fa4419f09f65a63742038c/library/core/src/ops/function.rs:250:5
  17: std::sys::backtrace::__rust_begin_short_backtrace::<fn(), ()>
             at /rustc/ef0fb8a2563200e322fa4419f09f65a63742038c/library/std/src/sys/backtrace.rs:166:18
  18: std::rt::lang_start::<()>::{closure#0}
             at /rustc/ef0fb8a2563200e322fa4419f09f65a63742038c/library/std/src/rt.rs:206:18
  19: <&dyn core::ops::function::Fn<(), Output = i32> + core::panic::unwind_safe::RefUnwindSafe + core::marker::Sync as core::ops::function::FnOnce<()>>::call_once
             at /rustc/ef0fb8a2563200e322fa4419f09f65a63742038c/library/core/src/ops/function.rs:287:21
  20: std::panicking::catch_unwind::do_call::<&dyn core::ops::function::Fn<(), Output = i32> + core::panic::unwind_safe::RefUnwindSafe + core::marker::Sync, i32>
             at /rustc/ef0fb8a2563200e322fa4419f09f65a63742038c/library/std/src/panicking.rs:581:40
  21: std::panicking::catch_unwind::<i32, &dyn core::ops::function::Fn<(), Output = i32> + core::panic::unwind_safe::RefUnwindSafe + core::marker::Sync>
             at /rustc/ef0fb8a2563200e322fa4419f09f65a63742038c/library/std/src/panicking.rs:544:19
  22: std::panic::catch_unwind::<&dyn core::ops::function::Fn<(), Output = i32> + core::panic::unwind_safe::RefUnwindSafe + core::marker::Sync, i32>
             at /rustc/ef0fb8a2563200e322fa4419f09f65a63742038c/library/std/src/panic.rs:359:14
  23: std::rt::lang_start_internal::{closure#0}
             at /rustc/ef0fb8a2563200e322fa4419f09f65a63742038c/library/std/src/rt.rs:175:24
  24: std::panicking::catch_unwind::do_call::<std::rt::lang_start_internal::{closure#0}, isize>
             at /rustc/ef0fb8a2563200e322fa4419f09f65a63742038c/library/std/src/panicking.rs:581:40
---
  31: __libc_start_main
  32: _start


Command has failed. Rerun with -v to see more details.
Bootstrap failed while executing `--stage 2 test --skip tidy --skip tests --skip coverage-map --skip coverage-run --skip library --skip tidyselftest`
Build completed unsuccessfully in 0:26:00
  local time: Thu May 14 17:09:10 UTC 2026
  network time: Thu, 14 May 2026 17:09:10 GMT
##[error]Process completed with exit code 1.

@rust-bors
Copy link
Copy Markdown
Contributor

rust-bors Bot commented May 14, 2026

☀️ Try build successful (CI)
Build commit: 51f5dfb (51f5dfbf846f8119ef576ad8c6b09b854ad81b99, parent: 480d8520dfc8f8d77869d7d3b2011026272c9528)

@rust-timer

This comment has been minimized.

@rust-timer
Copy link
Copy Markdown
Collaborator

Finished benchmarking commit (51f5dfb): comparison URL.

Overall result: ❌ regressions - BENCHMARK(S) FAILED

Benchmarking means the PR may be perf-sensitive. It's automatically marked not fit for rolling up. Overriding is possible but disadvised: it risks changing compiler perf.

Next, please: If you can, justify the regressions found in this try perf run in writing along with @rustbot label: +perf-regression-triaged. If not, fix the regressions and do another perf run. Neutral or positive results will clear the label automatically.

@bors rollup=never
@rustbot label: -S-waiting-on-perf +perf-regression

❗ ❗ ❗ ❗ ❗
Warning ⚠️: The following benchmark(s) failed to build:

  • Job failure
  • issue-58319
  • image-0.25.6
  • cranelift-codegen-0.119.0

❗ ❗ ❗ ❗ ❗

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
0.3% [0.2%, 0.4%] 18
Regressions ❌
(secondary)
0.4% [0.2%, 0.4%] 7
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
- - 0
All ❌✅ (primary) 0.3% [0.2%, 0.4%] 18

Max RSS (memory usage)

Results (primary 2.4%, secondary -1.7%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
2.4% [2.4%, 2.4%] 1
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-1.7% [-1.9%, -1.5%] 2
All ❌✅ (primary) 2.4% [2.4%, 2.4%] 1

Cycles

Results (secondary -7.3%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-7.3% [-7.3%, -7.3%] 1
All ❌✅ (primary) - - 0

Binary size

This perf run didn't have relevant results for this metric.

Bootstrap: 510.687s -> 510.851s (0.03%)
Artifact size: 398.06 MiB -> 398.11 MiB (0.01%)

@rustbot rustbot added perf-regression Performance regression. and removed S-waiting-on-perf Status: Waiting on a perf run to be completed. labels May 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

I-lang-radar Items that are on lang's radar and will need eventual work or consideration. P-lang-drag-2 Lang team prioritization drag level 2.https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang. perf-regression Performance regression. S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-rust-analyzer Relevant to the rust-analyzer team, which will review and decide on the PR/issue. T-rustdoc-frontend Relevant to the rustdoc-frontend team, which will review and decide on the web UI/UX output.

Projects

None yet

Development

Successfully merging this pull request may close these issues.