1. Symptoms
The Rust compiler emits error E0661 when you attempt to invoke a method on a struct or enum field that has an unsized type. This error manifests during compilation and prevents the binary from being built, regardless of whether the method being called is defined on the field type or inherited through a trait implementation.
The compiler produces output similar to the following when this error is encountered:
error[E0661]: cannot call method on field of unsized type
--> src/main.rs:5:5
|
5 | my_struct.my_field.some_method();
| ^^^^^^^^^^^^^^^^
|
= note: the method's receiver is unsized, the method cannot be called on this type
error: aborting due to 1 previous error
In this example, the compiler identifies that my_field has an unsized type, making it impossible to resolve the method call at compile time. The error is detected at the exact line and column where the method invocation occurs, which helps pinpoint the problematic code location.
When using generic types with trait bounds, you may see additional context about why the type is considered unsized:
error[E0661]: cannot call method on field of unsized type
--> src/lib.rs:12:8
|
12 | self.data.process()
| ^^^^^^^^^ unsized receiver prevents method call
|
= help: consider storing the data in a Box<dyn Trait> or using a reference
The error message specifically mentions “unsized receiver” because method dispatch in Rust requires knowledge of the receiver’s size to properly construct the vtable pointer and determine the method’s entry address at runtime.
2. Root Cause
Error E0661 arises from a fundamental constraint in Rust’s type system: all values passed by value must have a known size at compile time. This requirement exists because the Rust compiler needs to determine how much stack space to allocate when a function is called and how to properly copy or move values between locations in memory.
When you store a value with an unsized type directly in a struct field, that struct itself becomes unsized. Method resolution for these unsized types requires the compiler to know the exact memory layout of the receiver, but unsized types by definition have a size that can only be determined at runtime. This creates an unresolvable conflict that the compiler signals as error E0661.
Unsized types in Rust include trait objects created with dyn Trait syntax, slices represented as [T] for some type T, and raw str types. When a struct contains a field of one of these types without wrapping it in a pointer type like Box<T>, Rc<T>, or &T, the entire struct becomes unsized. Rust does not allow storing unsized types directly as struct fields except behind some form of indirection.
The method resolution process in Rust involves the compiler consulting the method’s receiver type to determine which implementation to use. For unsized receivers, the compiler cannot construct the necessary machinery for method dispatch because the vtable construction and virtual method calls depend on knowing the concrete size of the type. Even if the method being called does not require self to be moved, the mere presence of an unsized field complicates the memory layout analysis that the compiler performs during code generation.
Trait objects are inherently unsized because they represent a value that implements a particular trait, but the underlying concrete type can vary. A dyn Display field could refer to a String, a u32, or any type implementing Display, and each of these has a different size. The compiler cannot predict which type will be stored in the field at runtime, so it cannot determine the compile-time size requirement.
3. Step-by-Step Fix
The primary solution to error E0661 involves wrapping unsized values in pointer types that provide a sized reference to the underlying data. The most common approaches are outlined below, progressing from the simplest to more complex scenarios.
Fix 1: Wrap in Box
When you need to store a trait object directly in a struct, use Box<dyn Trait> instead of dyn Trait alone. This approach is particularly useful when you need ownership of the underlying value.
Before:
trait Processable {
fn process(&mut self);
}
struct Worker {
data: dyn Processable, // E0661: unsized field
}
impl Worker {
fn do_work(&mut self) {
self.data.process(); // Error occurs here
}
}
After:
trait Processable {
fn process(&mut self);
}
struct Worker {
data: Box<dyn Processable>, // Sized: Box<dyn Trait> has known size
}
impl Worker {
fn do_work(&mut self) {
self.data.process(); // Works correctly
}
}
The Box<dyn Trait> type has a known size (the size of a pointer on the target architecture) while still allowing dynamic dispatch to the underlying type. This enables the compiler to properly construct method calls and manage memory layout.
Fix 2: Use a Reference or Mutable Reference
If ownership is not required, wrapping the unsized field in a reference provides the simplest fix. The reference itself has a known size and can point to data of varying sizes.
Before:
struct Container {
items: [i32], // Unsized slice type
}
impl Container {
fn length(&self) -> usize {
self.items.len() // E0661: cannot call method on unsized field
}
}
After:
struct Container<'a> {
items: &'a [i32], // Sized: reference has known size
}
impl Container<'_> {
fn length(&self) -> usize {
self.items.len() // Works correctly
}
}
This approach requires managing the lifetime of the referenced data but provides a clean solution that maintains the ability to work with slice-like data.
Fix 3: Use Rc or Arc for Shared Ownership
When multiple owners need to share unsized data, Rc<dyn Trait> or Arc<dyn Trait> provide interior mutability and shared ownership semantics.
Before:
use std::fmt::Display;
struct Logger {
message: dyn Display, // Unsized: cannot store directly
}
impl Logger {
fn log(&self) {
println!("{}", self.message);
}
}
After:
use std::fmt::Display;
use std::rc::Rc;
struct Logger {
message: Rc<dyn Display>, // Sized smart pointer
}
impl Logger {
fn log(&self) {
println!("{}", self.message);
}
}
The choice between Box, Rc, and Arc depends on your concurrency requirements. Use Box for single ownership, Rc for shared ownership in single-threaded contexts, and Arc for thread-safe shared ownership.
Fix 4: Redesign to Avoid Unsized Fields
In some cases, the cleanest solution is to redesign the data structure to avoid storing unsized data as a field. Consider using generics or type erasure at a higher level of abstraction.
Before:
struct Document {
content: dyn AsRef<str>, // Unsized
}
impl Document {
fn word_count(&self) -> usize {
self.content.as_ref().split_whitespace().count()
}
}
After:
struct Document {
content: String, // Always sized, implements AsRef<str>
}
impl Document {
fn word_count(&self) -> usize {
self.content.split_whitespace().count()
}
}
By storing a concrete sized type like String and relying on the AsRef<str> trait bound for methods, you achieve the desired behavior without encountering the unsized field limitation.
4. Verification
After applying one of the fixes described above, verify that the error has been resolved by recompiling your project. Run the following command to check for any remaining compilation errors:
cargo build 2>&1
A successful build produces output indicating that the compilation succeeded:
Compiling your_project v0.1.0
Finished dev [optimized] target(s) in 0.65s
Execute your test suite to ensure that the behavioral correctness of your code remains intact after the structural changes:
cargo test
For more complex changes involving trait objects, write additional unit tests that exercise the polymorphic behavior through the pointer wrapper. This confirms that dynamic dispatch continues to function as expected:
#[cfg(test)]
mod tests {
use super::*;
struct ConcreteProcessor;
impl Processable for ConcreteProcessor {
fn process(&mut self) {
// concrete implementation
}
}
#[test]
fn test_worker_calls_process() {
let processor = ConcreteProcessor;
let mut worker = Worker {
data: Box::new(processor)
};
worker.do_work();
// assertions about side effects
}
}
Review the generated documentation to ensure that the structural changes are properly reflected in the public API:
cargo doc --no-deps
Finally, if you use static analysis tools, run them to catch any subtle issues introduced by the changes:
cargo clippy -- -W clippy::all
5. Common Pitfalls
Several recurring mistakes lead to error E0661 or cause confusion when attempting to resolve it. Understanding these pitfalls helps avoid wasted debugging time and prevents introducing new errors while fixing the original issue.
The first pitfall involves forgetting that Option< dyn Trait > is also unsized. Developers sometimes attempt to use Option<Box<dyn Trait>> but mistakenly create Option<dyn Trait> when using pattern matching. Ensure that any type containing dyn Trait is wrapped in a sized pointer type at every level of nesting.
The second pitfall occurs when working with generic types and accidentally relaxing trait bounds. If you have a generic struct that should store a sized type but accidentally remove the Sized bound, you may encounter E0661 in unexpected locations. Always ensure your generic parameters maintain the Sized bound unless you specifically need unsized coercion.
The third pitfall involves using dyn Trait in function parameters. When a function accepts dyn Trait by value, the caller must supply a sized wrapper. Forgetting this leads to confusing error messages that point to call sites rather than the function definition:
// This function requires callers to provide Box<dyn Trait>
fn process(item: Box<dyn Display>) {
println!("{}", item);
}
// Calling without Box causes an error at the call site
fn caller() {
let text = String::from("hello");
process(text); // Error: text is not Box<dyn Display>
}
The fourth pitfall concerns mutable references to unsized types. When you store &mut dyn Trait in a struct, the type is technically sized because references have a known size, but you lose the ability to modify the underlying data through the trait object unless you use interior mutability patterns. Be explicit about your mutation requirements when designing structs with trait object fields.
The fifth pitfall involves mixing compile-time and runtime polymorphism incorrectly. Some developers attempt to use generics for runtime polymorphism, which does not work because generics are resolved at compile time. If you need runtime polymorphism, you must use trait objects; if you need compile-time polymorphism, you must use generics. Using the wrong approach leads to type errors that may superficially resemble E0661.
6. Related Errors
Error E0509 occurs when attempting to move a value out of a struct that contains an unsized field. This error is closely related to E0661 because both stem from the same underlying limitation: unsized types cannot be moved by value. E0509 specifically addresses the case where you try to move a field out of a struct containing unsized data, which would require copying an amount of memory that cannot be determined at compile time.
Error E0606 addresses casting between types of different sizes, which is relevant because unsized types have no concrete size available for type coercion or casting analysis. When you attempt to cast an unsized type to another type, the compiler cannot determine the target size requirements. This error often appears alongside E0661 when working with complex type hierarchies involving trait objects and unsized types.
Error E0277 is a general-purpose error that appears when trait bounds are not satisfied or when type relationships cannot be established. In the context of unsized types, E0277 frequently accompanies E0661 because the method resolution process relies on establishing that the receiver type satisfies all necessary trait bounds. The error message from E0277 often provides additional context about which specific trait bounds are missing or cannot be satisfied.
Error E0661 serves as a reminder that Rust’s ownership model and memory layout analysis require all by-value types to have statically determinable sizes. By wrapping unsized types in appropriately sized pointer types, you maintain the dynamic polymorphism semantics while satisfying the compiler’s requirements for memory layout analysis during code generation.