Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiler forces me to implement trait method but the `Self` trait bound on method is never satisfied for my type

Tags:

rust

traits

I have a trait Foo. I want to force implementors to define a method, if those implementors implement another trait (Clone in this example). My idea (Playground):

trait Foo {
    // Note: in my real application, the trait has other methods as well,
    // so I can't simply add `Clone` as super trait
    fn foo(&self) 
    where 
        Self: Clone;
}

struct NoClone;
impl Foo for NoClone {}

Sadly, this leads to:

error[E0046]: not all trait items implemented, missing: `foo`
 --> src/lib.rs:8:1
  |
2 | /     fn foo(&self) 
3 | |     where 
4 | |         Self: Clone;
  | |____________________- `foo` from trait
...
8 |   impl Foo for NoClone {}
  |   ^^^^^^^^^^^^^^^^^^^^ missing `foo` in implementation

I don't understand this error: the compiler clearly knows that NoClone does not implement Clone, so why am I required to provide a definitoin for foo? In particular, if I attempt to provide a definition (Playground):

impl Foo for NoClone {
    fn foo(&self) 
    where 
        Self: Clone
    {
        unreachable!()
    }
}

I get the error:

error[E0277]: the trait bound `NoClone: std::clone::Clone` is not satisfied
  --> src/lib.rs:9:5
   |
9  | /     fn foo(&self) 
10 | |     where 
11 | |         Self: Clone
12 | |     {
13 | |         unreachable!()
14 | |     }
   | |_____^ the trait `std::clone::Clone` is not implemented for `NoClone`
   |
   = help: see issue #48214
   = help: add #![feature(trivial_bounds)] to the crate attributes to enable

So the compiler knows for sure. (FYI: with #![feature(trivial_bounds)] it compiles, but I don't want to define a bunch of methods with unreachable!() as body.)

Why does the compiler force me to provide the method definition? Can I work around this problem somehow?

like image 507
Lukas Kalbertodt Avatar asked Apr 04 '19 17:04

Lukas Kalbertodt


1 Answers

All implementors of a trait need to implement all methods that don't have a default implementation. It's the point of a trait that it has a defined interface. Adding trait bounds to a method does not change anything about this rule.

This is what the language reference says on the topic:

A trait implementation must define all non-default associated items declared by the implemented trait, may redefine default associated items defined by the implemented trait, and cannot define any other items.

This also means that a trait bound on Self in a method declaration on a trait is functionally equivalent to declaring a supertrait, except that the trait can only be used in the method that declares the bound.

The obvious work-around is to define a separate trait for the methods that have additional requirements on Self:

trait FooExt: Foo + Clone {
    fn foo(&self);
}

You can now implement Foo for all types, and FooExt in addition for the types that are Clone.

Updated as requested in the comments: There is a GitHub issue discussing whether it should be allowed to implement methods with unsatisfiable trait bounds without the method body, so at least the { unimplemted()! } part could be dropped. As of April 2019, this discussion has not come to any conclusion yet, and not even the exact syntax for implementing the uncallable methods has been settled.

like image 146
Sven Marnach Avatar answered Oct 15 '22 11:10

Sven Marnach