rust-lang / rust-lang/rust

TAIT + next-solver will allow constructing a unit struct without mentioning its type, causing `pinned-init` to be unsound.

Open
#153,535 8 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-impl-trait A-inference A-macros A-rust-for-linux C-bug F-type_alias_impl_trait I-unsound T-lang T-types WG-trait-system-refactor
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

I have also described this issue at https://github.com/rust-lang/rfcs/pull/3444#issuecomment-4016145373

The pinned-init crate has a macro whose soundness relies on having a type that cannot be constructed outside the macro, due to the type's name being impossible to mention.

The TAIT feature, along with the next-solver, allows writing a struct literal without mentioning the type name.

Therefore, TAIT with the next-solver will cause the pinned-init crate to become unsound.

The below code creates an uninitialized Box<i32> with the pinned-init crate version 0.0.10, and with the -Znext-solver flag enabled. It causes a segmentation fault in my testing.

#![feature(type_alias_impl_trait)]

use std::pin::Pin;

use pinned_init::{init, stack_pin_init};

struct Thing<T> {
    field: T,
}

fn conjure<T>() -> T {
    panic!()
}

#[expect(unreachable_code, unused_variables)]
fn make_uninit<T: Unpin>(dummy: T) -> T {
    let initializer = init!(Thing {
        field <- {
            return Ok('block: {
                // This type will end up being inferred to be the unnamable `__InitOk` type defined in the `init!` macro
                type InferredType = impl Sized;
                if false {
                    let fake_value: InferredType = loop {};
                    break 'block fake_value;
                }
                InferredType {}
            });
            conjure::<T>()
        }
    });
    stack_pin_init!(let pinned_value = initializer);
    let mut pinned_value: Pin<&mut Thing<T>> = pinned_value;
    let mut_value: &mut T = &mut pinned_value.field;
    std::mem::replace(mut_value, dummy)
}

fn main() {
    println!("{}", make_uninit::<Box<i32>>(Box::new(1)));
}

cc @rust-lang/rust-for-linux (owner of the pinned-init crate)

cc @lcnr

Meta

rustc --version --verbose:

rustc 1.96.0-nightly (80282b130 2026-03-06)
binary: rustc
commit-hash: 80282b130679a654eaa22f028a908c51be53d436     
commit-date: 2026-03-06
host: x86_64-pc-windows-msvc
release: 1.96.0-nightly
LLVM version: 22.1.0

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the provided pinned-init example with -Znext-solver, then inspect the init! macro in pinned_init/macros.rs alongside TAIT handling and the next-solver behavior described in issues #63063 and RFC PR #3444. Done means the interaction no longer permits the unsound construction, with coverage for the demonstrated case.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.