Mehdi Akiki
Rust Failure Atlas / Runtime, memory, and library APIs

RFA-587 · Case file with fixtures · Case 559 of 694 · Compiler evidence

Rust Unsized Values Must Stay Behind a Pointer

Dynamically sized values need pointer metadata and storage owned elsewhere. Coerce to a slice reference or an owning pointer, not to a bare slice value.

Reviewed
Rust
Rust 1.98.1, edition 2024
Targets
all Rust targets
Profiles
dev, release, test

Direct answer

What this Rust failure means

Why it happens
The runtime-sized sequence was separated from the pointer whose metadata carries its length and connects it to owned backing storage.
First discriminating check
Use normal coercion to a slice reference or choose an owning pointer such as Box of slice when independent ownership is required.

The type [usize] describes a slice with a runtime-known number of elements. Since its size is not part of the type, it cannot be produced as an ordinary local value whose stack layout must be known. The failing fixture tries to cast an array reference to bare [usize], and rustc reports E0620.

A slice value needs data and length

An array [usize; 2] has a compile-time length and size. A slice [usize] describes a contiguous sequence whose length is supplied at runtime. Code uses it through a pointer-like type such as &[usize], whose representation carries a data address and length metadata.

The E0620 explanation calls [usize] unsized and recommends casting to a reference. The broader Reference section on dynamically sized types also includes str and trait objects.

“Unsized” does not mean the allocation has no size. A concrete slice at runtime certainly spans a particular byte length. It means the size is not a compile-time constant determined by the bare Rust type.

The repair preserves the pointer layer

The repaired fixture produces &[usize]. The array owns storage, while the slice reference borrows the whole sequence and carries its length. The assertion compares the view with the expected elements.

Usually no as cast is needed because array references coerce to slice references in an expected context: let values: &[usize] = &[1, 2];. I prefer coercion because it uses the language's safe unsizing relationship without suggesting arbitrary bit reinterpretation.

If the slice needs independent ownership, Box<[usize]>, Rc<[usize]>, or Arc<[usize]> may fit. A Vec<usize> adds capacity for growth. These containers make different allocation and sharing promises while keeping the unsized slice behind a sized pointer.

Metadata is part of pointer meaning

A reference to one sized value is commonly described as a thin pointer. A slice reference needs length metadata; a trait-object reference needs metadata that supports dynamic dispatch. This is why turning a raw data address into a slice reference requires both pointer and correct length.

I never reconstruct that metadata from guesses. At FFI boundaries I receive or compute a validated element count, check alignment and allocation bounds, and create a slice only while the storage remains valid. An incorrect length can make later safe-looking indexing access memory outside the allocation.

The compiler's safe coercions preserve known metadata. E0620 is a signal not to throw that pointer structure away.

Generic code has an implicit Sized bound

Most generic type parameters are Sized by default. Writing T: ?Sized relaxes that bound, but values of T still need to be used behind a pointer. A function can accept &T where T: ?Sized; it cannot take an arbitrary unsized T by value under ordinary stable calling conventions.

The last field of a struct may be dynamically sized, making the full struct unsized too. Constructing and owning such values requires an appropriate pointer and unsizing path. This is advanced layout work; a slice, trait object, or ordinary generic container is usually clearer.

Slices express borrowed sequence APIs well

Public functions often accept &[T] rather than &Vec<T>. The slice says the function needs contiguous elements, not capacity or vector-specific ownership. Arrays and vectors can both provide the view.

Returning a slice ties its lifetime to backing storage. If the function creates new elements, it should return an owning container. I do not force a slice return by leaking an allocation.

I test empty, one-element, and longer slices, plus boundary indexes through checked get where input is untrusted. The pointer metadata makes these operations possible, but API policy decides whether an invalid index should be optional or a programmer-error panic.

My E0620 checklist

  • Is the destination a bare slice, str, trait object, or another unsized type?
  • Which pointer or owner carries the value's runtime metadata?
  • Can normal array-to-slice or concrete-to-trait coercion replace a cast?
  • Who owns the backing storage and how long does it remain valid?
  • Does the code need borrowing, shared ownership, or growable capacity?
  • In generic code, should T: ?Sized appear only behind a pointer?
  • At unsafe boundaries, where are length, alignment, and allocation bounds proved?
  • Do tests cover empty and boundary-length values?

The core principle is that runtime-sized data still needs a compile-time-sized handle. I fix E0620 by preserving or choosing that handle, letting its metadata connect a flexible view to storage whose ownership and validity are explicit.