Understanding Rust Error E0746: Unboxed Trait Object as Return Value
Rust Error E0746: Overview
Rust Error E0746 occurs when an unboxed trait object is used as a return value. Two key points about Rust trait objects are: 1) They do not have a statically known size, and 2) they must implement the Sized trait. This error happens when there is an attempt to return a trait object that is not sized.
Illustrating Rust Error E0746
Here's an erroneous code example that demonstrates this error:
```rust
#![allow(unused)]
fn main() {
trait T {
fn bar(&self);
}
struct S(usize);
impl T for S {
fn bar(&self) {}
}
// Having the trait `T` as return type is invalid because
// unboxed trait objects do not have a statically known size:
fn foo() -> dyn T { // error!
S(42)
}
}
```
Solutions for Rust Error E0746
There are a few ways to resolve the error. We'll discuss three common solutions: using impl Trait, trait objects with Box/Rc/Arc, and implementing the trait on an enum.
1) Using impl Trait
One solution is to use the impl keyword followed by the trait name as the return type. This approach will allow the compiler to select the materialized type for the function, while callers will only see that the return type implements the required trait. Take a look at the following example:
```rust
#![allow(unused)]
fn main() {
trait T {
fn bar(&self);
}
struct S(usize);
impl T for S {
fn bar(&self) {}
}
// The compiler will select `S(usize)` as the materialized return type of this
// function, but callers will only know that the return type implements `T`.
fn foo() -> impl T { // ok!
S(42)
}
}
```
2) Using Trait Objects with Box/Rc/Arc
If multiple types are involved and it's acceptable to use dynamic dispatch, you can use trait objects with Box, Rc, or Arc container types. This allows callers to access only the associated items from the trait. See the following example:
```rust
#![allow(unused)]
fn main() {
trait T {
fn bar(&self);
}
struct S(usize);
impl T for S {
fn bar(&self) {}
}
struct O(&'static str);
impl T for O {
fn bar(&self) {}
}
// This now returns a "trait object" and callers are only be able to access
// associated items from `T`.
fn foo(x: bool) -> Box<dyn T> { // ok!
if x {
Box::new(S(42))
} else {
Box::new(O("val"))
}
}
}
```
3) Implementing the Trait on an Enum
If you still want to access the original types, you can create a new enum with a variant for each type and implement the trait on the returned enum. This way, callers can access the original types directly by matching on the returned enum. The following example demonstrates this approach:
```rust
#![allow(unused)]
fn main() {
trait T {
fn bar(&self);
}
struct S(usize);
impl T for S {
fn bar(&self) {}
}
struct O(&'static str);
impl T for O {
fn bar(&self) {}
}
enum E {
S(S),
O(O),
}
// The caller can access the original types directly, but it needs to match on
// the returned `enum E`.
fn foo(x: bool) -> E {
if x {
E::S(S(42))
} else {
E::O(O("val"))
}
}
}
```
Conclusion
In this article, we explained Rust Error E0746, which occurs when an unboxed trait object is used as a return value. We provided three solutions to resolve the error: using impl Trait, trait objects with container types (e.g., Box/Rc/Arc), and implementing the trait on an enum.