Default implementation of a trait function requires Sized

Code:

trait A
{
    fn f() -> Self;
    fn g() -> Self
    {
        Self::f()
    }
}
fn main() {}

error message:

error[E0277]: the size for values of type `Self` cannot be known at compilation time
   --> src/zips.rs:151:15
    |
151 |     fn g() -> Self
    |               ^^^^ doesn't have a size known at compile-time
    |
    = note: the return type of a function must have a statically known size
help: consider further restricting `Self`
    |
151 |     fn g() -> Self where Self: Sized
    |                    +++++++++++++++++

error[E0277]: the size for values of type `Self` cannot be known at compilation time
   --> src/zips.rs:153:9
    |
153 |         Self::f()
    |         ^^^^^^^ doesn't have a size known at compile-time
    |
    = note: the return type of a function must have a statically known size
help: consider further restricting `Self`
    |
151 |     fn g() -> Self where Self: Sized
    |                    +++++++++++++++++

The indicated fix from the compiler does work. My question is two-fold:

  1. Why do we need Sized at the first place?
  2. How can I solve this not by restricting my trait function only to Sized type?

EDIT

Now, the solution suggested by the compiler

trait A
{
    fn f() -> Self;
    fn g() -> Self where Self:Sized
    {
        Self::f()
    }
}
fn main() {}

does compile.

But how is this even possible, if we see that g is just calling f, basically g is defined as f. But these two functions have different signature (one ()->Self, another ()->Self where Self:Sized). What am I missing?

In the error Self refers to the type implementing the trait, and it is unknown what that could be at this point. It could be as trivial usize, but it could in principle also be str, which is a Dynamically Sized Type (DST), or any other type.

The thing is that DSTs can only be stored behind some kind of pointer (e.g. Box<T>), forcing it to have known size at compile time. Furthermore, having a method return an unsized value is not allowed in stable Rust at this time, i.e. no fn foo() -> str {...}

So to statically guarantee that that value being returned has a known size at compile type, Self: Sized is required.

I think the better question at this point is, why does that bound bother you? It doesn't prevent anything that wouldn't result in issues further down the road, at least judging by the toy example you gave.

5 Likes

You can't; it is not possible to return dynamically-sized types by value.

1 Like

If you did want to have these functions usable for !Sized types, here is a possible solution:

trait A: ToOwned {
    fn f() -> <Self as ToOwned>::Owned;
    fn g() -> <Self as ToOwned>::Owned {
        Self::f()
    }
}

The ToOwned trait provides a recommended “owned version” of the self type that is always Sized — for example, <str as ToOwned>::Owned is String. And it's implemented for everything that implements Clone, so it will work for most Sized types too.

2 Likes

OK, but something feels off. Why is f OK then? Plus, if f was OK, what my g does is just returning what f returns. g basically is a just a thin wrapper of f. It seems more reasonable to err on the f signature, no?

If I recall correctly, the signature itself isn't an error because unsized returns is a desired supported feature "some day". (Sorry, not in a position to track down the citation right now.)

1 Like

Do you mean signature-only functions in traits are a special case? My mental model accepts better if the compiler requires my BOTH functions (f and g) to have Sized bound to Self output.

What breaks me even more is that, even if unsized returns is a desired supported feature for another time, the compiler will allow g to directly return the output of f at all, given that it only requires Sized on g not f. This would result in g claiming to return Sized but effectively not, no?

No, because the expected behavior would then be that the trait would be still only implementable for sized types, and for those there is no issue, just like right now.

If it were then desirable to allow Self: ?Sized, then the bound could be removed. That would likely be a breaking API change though, because even though it only relaxes the bound, implementors likely have verbatim the same method signatures, bounds and all, and thus those would need to be removed as well.

Thank you. But my question would remain, in either case (whether DST returns are supported): why is f allowed to have no Sized bound, but g not. My question is more on the consistency on type-checking on the signature but not whether DST is actually supported

The real answer is quite unhelpful: because the core language developers pulled a Picard and Made It So™️.

It's likely to be somewhat of an oversight, made possible by the fact that that particular part of the grammar is quite unlikely to be exercised, at least at the moment.

It also doesn't help or hurt you, in that it doesn't make anything possible that shouldn't be.
It's just a weird little corner case.

2 Likes

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.