Pierre-Antoine
Pierre-Antoine

Reputation: 2094

Can I declare that implementations of a given trait should not be expected to be Sized?

I defined a trait Foo, implemented this trait for [u32], and wrote a function bar taking this trait as argument (playground):

trait Foo {
    fn foo(&self) -> u32;
}

impl Foo for [u32] {
    fn foo(&self) -> u32 {
        self[0]
    }
}

fn bar<T>(f: &T) -> u32
where
    T: Foo,
{
    f.foo() + 1
}

fn main() {
    let f: &[u32] = &[42];
    bar(f);
}

This does not compile because bar implicitly expects its arguments to be Sized:

error[E0277]: the trait bound `[u32]: std::marker::Sized` is not satisfied
  --> src/main.rs:20:5
   |
20 |     bar(f);
   |     ^^^ `[u32]` does not have a constant size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `[u32]`
note: required by `bar`
  --> src/main.rs:11:1
   |
11 | / fn bar<T>(f: &T) -> u32
12 | | where
13 | |     T: Foo,
14 | | {
15 | |     f.foo() + 1
16 | | }
   | |_^

I can fix it with T: Foo + ?Sized, but then I would have to do this for every function expecting a Foo, which is a pain...

Can I declare once and for all that implementations of Foo should not be expected to be Sized? I tried trait Foo: ?Sized in line 1, but the compiler complains about it.

This question is not the same as Trait implementing Sized. In that question, the Foo parameter is moved, so it is normal that the compiler wants to know its size at compile time. In my case, the parameter is a reference, so it does not need to be sized -- but still the compiler implicitly assumes it is, unless explicitly told (using + ?Sized). What I would like to change is this implicit assumption, for this particular trait.

Upvotes: 2

Views: 142

Answers (1)

Tim Diekmann
Tim Diekmann

Reputation: 8486

Why does the compiler complain about trait Foo: ?Sized?

When you say that every Foo is Sized, you're kind of hiding the truth to yourself. Yes, every Foo is Sized but actually every type has a given size at some point. The real important information is that you're not saying how much this size is.

In this case you asking for a sized [u32]:

error[E0277]: the trait bound `[u32]: std::marker::Sized` is not satisfied
  |
  | impl Foo for [u32] {
  |      ^^^ `[u32]` does not have a constant size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `[u32]`

For further information consult this answer of Trait implementing Sized.

Why can't T be unsized, since we only have a reference?

Let me quote again from this answer from Why does a reference to a trait in a generic function have to implement Sized?

By default, all generic types on functions implicitly have the Sized bound, regardless of how they are used. You need to explicitly opt-out of that requirement using ?Sized

This will solve your problem:

fn bar<T>(f: &T) -> u32
where
    T: Foo + ?Sized,
{
    f.foo() + 1
}

Playground

It would also be possible to implement Foo for &[u32]:

impl<'a> Foo for &'a [u32] {
    fn foo(&self) -> u32 {
        self[0]
    }
}

fn bar<T>(f: T) -> u32
where
    T: Foo,
{
    f.foo() + 1
}

Playground

In this particular case, you could even generalize your implementation for Foo and it would work on Vecs and arrays as well as references to those types:

impl<T: AsRef<[u32]>> Foo for T {
    fn foo(&self) -> u32 {
        self.as_ref()[0]
    }
}

With the new impl Trait syntax, bar can be shortened in the last two cases to

fn bar(f: impl Foo) -> u32 {
    f.foo() + 1
}

Playground

Upvotes: 2

Related Questions