How to fix Rust E0277 the size for values cannot be known at compilation time

Fix Rust E0277 by wrapping unsized types like str in a pointer such as Box or using a reference to provide a known size.

When the compiler can't measure your type

You're writing a struct to hold user data. You add a field for the username. You type name: str. You hit save. The compiler rejects you with E0277: "the size for values of type str cannot be known at compilation time." You stare at the screen. str is just a string. How can Rust not know the size of a string?

The compiler isn't being difficult. It's protecting you from a memory layout that doesn't exist.

The blueprint problem

Rust variables need a fixed size at compile time. The compiler lays out memory like a blueprint. Every variable gets a specific number of bytes reserved. i32 takes 4 bytes. bool takes 1 byte. The compiler knows this instantly. It calculates the offset of every field in a struct. It determines how much stack space a function call needs. If the size isn't known, the blueprint falls apart.

Some types refuse to give a size. str is one. A string slice can be one character or a million. [i32] is an array of unknown length. dyn Trait is a trait object that could be any type implementing the trait. These are Dynamically Sized Types, or DSTs.

Think of a variable as a parking spot. The lot manager needs to paint lines of a specific width. A compact car fits in a compact spot. An i32 is a compact car. A str is a caravan. The caravan might be 10 feet long or 50 feet long. You can't paint a parking spot for "a caravan" because you don't know how long the caravan will be. You need a different approach. You hand the driver a key card. The key card has a fixed size. The key card points to where the caravan is parked elsewhere. In Rust, that key card is a pointer.

You can't park a caravan in a compact spot. Wrap it in a pointer.

The Sized trait

The compiler enforces this rule through a hidden trait called Sized. Almost every type you write implements Sized. When you declare let x: T, the compiler assumes T: Sized. It checks the size, reserves the bytes, and moves on.

str explicitly does not implement Sized. It's a slice. A slice is a view into data, not the data itself. The size depends on the underlying buffer, which varies at runtime.

When you write name: str, the compiler looks for str: Sized. It finds nothing. It emits E0277.

Wrapping in String works because String is a struct containing a pointer, a length, and a capacity. That's three usize values. The size is 3 * usize. Fixed. Known. String implements Sized.

Wrapping in &str works because a reference is a pointer plus a length. Two usize values. Fixed size. &str implements Sized.

The pointer carries the size information. The pointer itself has a known size. The compiler is happy.

/// User struct fails because str has no fixed size.
struct User {
    // str is a slice. It could be any length.
    // The compiler cannot reserve memory for this field.
    name: str,
}

/// User struct works with a pointer wrapper.
struct UserFixed {
    // String owns the data and has a fixed size.
    // It stores a pointer, length, and capacity.
    name: String,
}

Convention aside: The community prefers String for owned text and &str for borrowed text. Box<str> exists but is less common. Use Box<str> when you have a &str and need to transfer ownership without the extra capacity overhead of a String, or when you are interning strings. For most code, String is the right choice.

The pointer is the key card. It has a fixed size. The compiler accepts it.

Trait objects and dynamic dispatch

Trait objects are the other big trigger for E0277. A trait like Animal can be implemented by Dog, Cat, Elephant. Each has a different size. dyn Animal represents "some type that implements Animal". The compiler doesn't know which one. It doesn't know the size.

You can't return dyn Animal by value. You can't store dyn Animal in a struct field. You must wrap it. &dyn Animal, Box<dyn Animal>, Rc<dyn Animal>.

trait Animal {
    fn speak(&self);
}

struct Dog;
impl Animal for Dog {
    fn speak(&self) { println!("Woof"); }
}

struct Cat;
impl Animal for Cat {
    fn speak(&self) { println!("Meow"); }
}

/// This fails. dyn Animal is a trait object.
/// It doesn't have a fixed size.
/// The compiler cannot determine the memory layout.
fn make_sound(animal: dyn Animal) {
    animal.speak();
}

/// This works. The reference has a fixed size.
/// It points to the animal wherever it lives.
fn make_sound_fixed(animal: &dyn Animal) {
    animal.speak();
}

Convention aside: Always write dyn Trait. Rust 2021 edition requires the dyn keyword for trait objects. Writing Box<Animal> triggers a warning telling you to add dyn. The community follows this strictly. It makes trait objects visually distinct from concrete types. You never write Box<Animal>. You write Box<dyn Animal>.

Trait objects demand a pointer. The size varies, so the address must be fixed.

Realistic scenario: A list of shapes

You're building a rendering engine. You have a Shape trait. You want a list of shapes to draw. You try Vec<dyn Shape>. The compiler rejects you with E0277.

Vec<T> requires T: Sized. A vector stores elements inline in a contiguous buffer. It needs to know the stride between elements. dyn Shape has no fixed stride. The vector can't calculate where the next element starts.

The fix is Vec<Box<dyn Shape>>. The vector stores boxes. A box has a fixed size. The vector stores pointers to the shapes. The shapes live on the heap, each with their own size. The vector doesn't care. It just stores the pointers.

trait Shape {
    fn area(&self) -> f64;
}

struct Circle {
    radius: f64,
}

impl Shape for Circle {
    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius * self.radius
    }
}

struct Square {
    side: f64,
}

impl Shape for Square {
    fn area(&self) -> f64 {
        self.side * self.side
    }
}

/// This fails. Vec requires sized elements.
/// dyn Shape has no fixed size.
// let shapes: Vec<dyn Shape> = vec![
//     Circle { radius: 1.0 },
//     Square { side: 2.0 },
// ];

/// This works. Vec stores boxes.
/// Each box has a fixed size.
/// The shapes are allocated on the heap.
let shapes: Vec<Box<dyn Shape>> = vec![
    Box::new(Circle { radius: 1.0 }),
    Box::new(Square { side: 2.0 }),
];

for shape in &shapes {
    println!("Area: {}", shape.area());
}

Convention aside: When you see Vec<Box<dyn Trait>>, you're looking at dynamic dispatch. The compiler generates a vtable for the trait. Each call goes through the vtable. This has a small runtime cost compared to static dispatch. If performance matters and the types are known at compile time, consider generics with impl Trait or a concrete enum instead.

Pick the pointer wrapper that matches your ownership needs. The size rule never changes.

Pitfalls and edge cases

You'll see E0277 in a few shapes.

impl Trait vs dyn Trait: You write fn foo(x: impl Animal). This works. impl Trait in argument position is monomorphized. The caller provides a concrete type. The size is known at the call site. This is different from dyn Animal. impl Animal is static dispatch. dyn Animal is dynamic dispatch.

?Sized bound: Sometimes you want to allow DSTs in a generic function. You can write fn foo<T: Animal + ?Sized>(x: &T). The ?Sized relaxes the bound. It tells the compiler "T might not be Sized". You must still use a pointer like &T or Box<T> to access the value. You can't take T by value if it might be unsized.

E0277 on generics: If you see E0277 on a type parameter, look for a missing Sized bound or a stray DST. Generics assume T: Sized by default. If you pass a DST, the compiler complains. Add ?Sized if you intend to support DSTs, or wrap the DST in a pointer before passing it.

/// This function accepts any type, sized or not.
/// The ?Sized bound relaxes the default requirement.
/// You must still use a pointer to access T.
fn print_any<T: ?Sized>(value: &T) {
    // We can't print T directly because it might not implement Display.
    // This is just a demonstration of the bound.
    let _ = value;
}

/// This works with a reference to a str.
/// str is unsized, but &str is sized.
print_any(&"hello");

Relax the bound only when you have a pointer. ?Sized without a pointer is a dead end.

Decision matrix

Use String when you need to own the text and might modify it. It allocates on the heap and tracks capacity for efficient growth.

Use &str when you are borrowing text from somewhere else. It has zero cost and points to existing data.

Use Box<str> when you need to own a string slice without the extra capacity of a String. This happens when you intern strings or leak a String to get a static reference.

Use &dyn Trait when you want to accept any type implementing a trait without taking ownership. The caller keeps the data.

Use Box<dyn Trait> when you need to own a trait object and store it in a collection or return it from a function. The box allocates the concrete type on the heap.

Use Vec<Box<dyn Trait>> when you need a list of owned trait objects. The vector stores boxes, which have a fixed size, allowing contiguous storage of pointers.

Use impl Trait when you want static dispatch and the concrete type is known at compile time. This avoids the overhead of dynamic dispatch and vtables.

Check your generics. If you see E0277 on a type parameter, look for a missing Sized bound or a stray DST.

Where to go next