Why can't default implementations assume `Sized` while being object-safe?

Let's say you have a trait that has a method that lets you get it as a different trait:

trait Bar { }

trait Foo: Bar {
    fn as_bar(&self) -> &dyn Bar;
}

All implementations of this method will be the same; i.e.

fn as_bar(&self) -> &dyn Bar { self }

Therefore, it'd be nice to offload this needless repetition to the trait itself by offering a default implementation, i.e. the exact same as the above:

fn as_bar(&self) -> &dyn Bar { self }

If you try this, however, the compiler will yell at you:

error[E0277]: the size for values of type `Self` cannot be known at compilation time
 --> src/lib.rs:4:36
  |
4 |     fn as_bar(&self) -> &dyn Bar { self }
  |                                    ^^^^ doesn't have a size known at compile-time
  |
  = note: required for the cast to the object type `dyn Bar`
help: consider further restricting `Self`
  |
4 |     fn as_bar(&self) -> &dyn Bar where Self: Sized { self }
  |                                  ^^^^^^^^^^^^^^^^^

If you try to follow the compiler's advice by putting where Self: Sized on it, however, you can no longer use as_bar() on dyn Foos, missing the point entirely. I understand why it can't assume Self: Sized because you can implement traits for types that are ?Sized, but it's really frustrating to have to strew

fn as_bar(&self) -> &dyn Bar { self }

on every type you impl the trait for, just because default impls can't make certain assumptions, and "fixing" those assumpetions with Self: Sized breaks something else about the impl instead.

Am I missing some crucial detail, or is this just an unsolvable problem?

AFAIK, this is currently unsolvable. Default impls with extra restrictions are part of the unstable specialization feature (which is – by the way – in my opinion quite unfortunate because this way the feature is needlessly blocked on all the problems of specialization like its soundness issues):

#![feature(specialization)]

trait Bar {}

trait Foo: Bar {
    fn as_bar(&self) -> &dyn Bar;
    fn more_methods(&self);
}

default impl<T: Foo> Foo for T { // <- T has an implicit T: Sized bound
    fn as_bar(&self) -> &dyn Bar {
        self
    }
}

struct SomeStruct {}
impl Bar for SomeStruct {}
impl Foo for SomeStruct {
    fn more_methods(&self) {
        // …
    }
}
1 Like

Okay, maybe it’s not entirely unsolvable, yet IMO it would be way nicer if Rust just got a feature that allowed proper default implementations with additional bounds. An additional trait will do the job, I guess. It could even become a supertrait of Bar itself, so that other traits like Foo that extend Bar will all get the as_bar method for free.

trait Bar: AsBar {}

trait AsBar {
    fn as_bar(&self) -> &dyn Bar;
}

impl<T: Bar> AsBar for T { // <- T has an implicit T: Sized bound
    fn as_bar(&self) -> &dyn Bar {
        self
    }
}

trait Foo: Bar {
    fn more_methods(&self);
}

struct SomeStruct {}
impl Bar for SomeStruct {}
impl Foo for SomeStruct {
    fn more_methods(&self) {
        // …
    }
}

fn foo(x: &dyn Foo) {
    // it works!
    let y: &dyn Bar = x.as_bar();
}

Fortunately, min_specialization can (verbosely) handle similar cases, so there’s a chance this will be possible on stable before the heat death of the universe.

As long as there’s a need to split the trait, I don’t consider it proper “default method implementations with additional trait bounds”. :wink:

2 Likes

If we adapted the approach that (GHC) Haskell takes, we’d be writing something like

trait Bar { }

trait Foo: Bar {
    fn as_bar(&self) -> &dyn Bar;

    default fn as_bar(&self) -> &dyn Bar
    where
        Self: Sized
    {
        self
    }
}

No need for specialization, a limit of a single default implementation per trait methods, and it’s still super useful.

2 Likes

Note that's more-or-less what I originally proposed in the linked IRLO thread, but the only response I got was "specialization can already do that." It's an unsatisfying answer because the specialization feature seems doomed to nightly purgatory for the forseeable future; nobody seems interested in pursuing this separately.

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.