rust-lang/rust · error

unsized locals must not be `extern` types

Error message

unsized locals must not be `extern` types

What it means

Thrown by `panic!("unsized locals must not be `extern` types")` in `load_operand` (builder.rs:726) when loading a place whose layout is unsized and whose struct tail is a `ty::Foreign` (an `extern type` per RFC 1861). rustc can copy unsized locals/args only if it can compute their dynamic size; extern types have no statically derivable size, so loading one as a local is explicitly forbidden. The comment notes this check is intentional and t-opsem should be consulted before removing it.

Source

Thrown at compiler/rustc_codegen_llvm/src/builder.rs:726

        unsafe {
            let load = llvm::LLVMBuildLoad2(self.llbuilder, ty, ptr, UNNAMED);
            // Set atomic ordering
            llvm::LLVMSetOrdering(load, AtomicOrdering::from_generic(order));
            // LLVM requires the alignment of atomic loads to be at least the size of the type.
            llvm::LLVMSetAlignment(load, size.bytes() as c_uint);
            load
        }
    }

    #[instrument(level = "trace", skip(self))]
    fn load_operand(&mut self, place: PlaceRef<'tcx, &'ll Value>) -> OperandRef<'tcx, &'ll Value> {
        if place.layout.is_unsized() {
            let tail = self.tcx.struct_tail_for_codegen(place.layout.ty, self.typing_env());
            if matches!(tail.kind(), ty::Foreign(..)) {
                // Unsized locals and, at least conceptually, even unsized arguments must be copied
                // around, which requires dynamically determining their size. Therefore, we cannot
                // allow `extern` types here. Consult t-opsem before removing this check.
                panic!("unsized locals must not be `extern` types");
            }
        }
        assert_eq!(place.val.llextra.is_some(), place.layout.is_unsized());

        if place.layout.is_zst() {
            return OperandRef::zero_sized(place.layout);
        }

        #[instrument(level = "trace", skip(bx))]
        fn scalar_load_metadata<'a, 'll, 'tcx>(
            bx: &mut Builder<'a, 'll, 'tcx>,
            load: &'ll Value,
            scalar: abi::Scalar,
            layout: TyAndLayout<'tcx>,
            offset: Size,
        ) {
            if bx.cx.sess().opts.optimize == OptLevel::No {
                // Don't emit metadata we're not going to use

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Do not bind extern types as unsized locals/by-value args — keep them behind a pointer (`&`, `*const`, `Box`) instead.
  2. Disable `unsized_locals` if the code doesn't strictly require it, since extern types + unsized locals is unsupported.
  3. Refactor the extern type usage so the value is never loaded by value (operate through references).
  4. Update to a newer nightly in case the interaction gains defined behavior, but expect this to remain disallowed by design.
  5. If you believe the load is legitimate, consult the t-opsem working group / file an issue, since the check is deliberately conservative.

Example fix

// before: binding an extern type as an unsized local panics on load
#![feature(extern_types, unsized_locals)]
extern "C" { type Opaque; }
fn f(x: Box<Opaque>) {
    let local: Opaque = *x;   // panic: unsized locals must not be `extern` types
}

// after: keep the extern type behind a pointer, never load by value
fn f(x: Box<Opaque>) {
    let _ref: &Opaque = &*x;  // OK: no by-value load of the extern-tailed unsized value
}
Defensive patterns

Strategy: type-guard

Type guard

fn assert_not_extern_local<T: ?Sized>() {
    // reject `extern` types used as values/locals; only allow them behind a pointer.
    const _: () = { /* compile-time lint hook: ensure no extern type reaches load_operand by value */ };
}
// At the source level, never write:   let x: MyExternType = ...;
// instead always use:                 let x: &MyExternType = ...;

Prevention

When it happens

Trigger: Triggered when code uses the `unsized_locals` and/or `extern_types` nightly features such that an unsized local (or by-value unsized argument) is bound to a value whose tail type is an `extern "C"`-style foreign type, and the codegen builder tries to load it. The `load_operand` path then hits the panic because no dynamic size can be determined for the foreign-typed tail.

Common situations: Experimenting with `#![feature(extern_types)]` together with `#![feature(unsized_locals)]`; binding an FFI extern type by value into a local; passing an extern-typed value as an unsized argument. Both features are unstable and their interaction is unsupported, so this panic guards an unimplemented corner.

Related errors


AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03). Data as JSON: /data/errors/ffade53ce7d97035.json. Report an issue: GitHub.