19

This code give me a compilation error:

trait IBoo {
    fn new() -> Box<IBoo>;
}

while this code compiles without any error:

trait IBoo {
    //fn new() -> Box<IBoo>;
}

trait IFoo {
    fn new() -> Box<IBoo>;
}
  1. Why does the first one not compile? rustc --explain E0038 does not give me a direct hint why it is not possible.
  2. Is it possible to combine construction and methods in one interface (trait)?
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
user1244932
  • 7,352
  • 5
  • 46
  • 103

2 Answers2

22

This is from the description of E0038:

Method has no receiver

Methods that do not take a self parameter can't be called since there won't be a way to get a pointer to the method table for them.

trait Foo {
    fn foo() -> u8;
}

This could be called as <Foo as Foo>::foo(), which would not be able to pick an implementation.

Adding a Self: Sized bound to these methods will generally make this compile.

trait Foo {
    fn foo() -> u8 where Self: Sized;
}

You can do this:

trait IBoo {
    fn new() -> Box<IBoo>
    where
        Self: Sized;
}

In other cases, you can place the restriction on the entire impl:

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
aSpex
  • 4,790
  • 14
  • 25
  • 1
    Thanks for this! I didn't realise it was possible to add `Self` constraints to individual trait methods. I had thought constraints had to be on the trait itself, and that this sort of problem could only be approached by breaking the trait up. – Peter Hall Jul 02 '16 at 13:13
  • Thanks for writing this up; I *completely* forgot about that. – DK. Jul 02 '16 at 15:14
  • More [detail](https://stackoverflow.com/questions/30938499/why-is-the-sized-bound-necessary-in-this-trait) about `Self: Sized`, go! – AurevoirXavier Dec 15 '18 at 18:34
21

The compiler tells you the exact reason this doesn't work:

error[E0038]: the trait `IBoo` cannot be made into an object
 --> src/main.rs:2:5
  |
2 |     fn new() -> Box<IBoo>;
  |     ^^^^^^^^^^^^^^^^^^^^^^ the trait `IBoo` cannot be made into an object
  |
  = note: method `new` has no receiver

Note the last line. It's telling you that the reason for the error is that new() doesn't depend on having an instance of a value implementing the IBoo trait.

By not taking a pointer of some kind to self, the method cannot be invoked by dynamic dispatch. If it can't be invoked by dynamic dispatch, this means it cannot go into the trait's associated vtable. There has to be an associated vtable, because that's how something like Box<IBoo> works. Some time ago, the core Rust developers decided that including even a single "non-object safe" method in a trait disqualified the whole trait from being used as an object.

To put it in other words: because you've defined a method that cannot be dynamically dispatched, the IBoo trait as a whole is disqualified from being used with dynamic dispatch.

If you want some kind of constructor function, you need to have some other way of writing that. This could be using plain function pointers, or an IBooFactory trait, as you might with Java.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
DK.
  • 55,277
  • 5
  • 189
  • 162
  • Thanks for answer, but why compiler's error emitted only for usage of `IFoo` or `IBoo`? Why I can define `trait` with functions without `self`, and errors emitted only when I made attempt to use such trait? – user1244932 Jul 02 '16 at 12:10
  • 3
    @user1244932 Because traits aren't *just* used for dynamic dispatch. There's no issue with static dispatch. The compiler doesn't have to construct a vtable *unless* you ask it to, and that's the point at which it complains. – DK. Jul 02 '16 at 12:13
  • Thanks again for answer – user1244932 Jul 02 '16 at 12:14
  • 1
    Does aSpex's answer invalidate this one? It seems that `trait IBoo { fn new() -> Box where Self: Sized; }` compiles just fine. (Haven't tried actually *calling* `new`, though.) – user4815162342 Jan 16 '17 at 10:05
  • @user4815162342 It doesn't invalidate it, but it's definitely the better answer. Sometimes, I wish the author of an accepted answer could "redirect" to another, better answer. – DK. Jan 16 '17 at 13:07
  • 1
    OK, I think I get it now. The sentence from your answer, "because you've defined a method that cannot be dynamically dispatched, the IBoo trait *as a whole* is disqualified from being used with dynamic dispatch" still stands. What aSpex's solution does is explicitly make `new()` unavailable to trait objects, thus making it (so to say) optional. With non-trait-object-safe `new` out of the way, the trait again becomes eligible for use with trait-object types such as `Box`. – user4815162342 Jan 16 '17 at 13:15
  • I'm surprised this wasn't implemented to only put trait-object safe methods in the vtable and complain only when a non-trait-object safe method call is attempted using dynamic dispatch. It seems rather short sighted to disqualify traits already being used with static dispatch from using dynamic dispatch likely halfway through an implementation because the compiler can't overlook the obvious intentions of a programmer. – Lindenk Jul 15 '19 at 19:52