Returning Option<Self> in a trait

I’ve got a series of structs that can be parsed from a file, and I’d like to have them use a generic trait.

I’ve tried writing the following:

pub trait Object {
    fn parse(reader: &mut Read) -> Option<Self>;
}

However, that doesn’t compile:

error[E0277]: the size for values of type `Self` cannot be known at compilation time
 --> src/database/object.rs:7:5
  |
7 |     fn parse(reader: &mut Read) -> Option<Self>;
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ doesn't have a size known at compile-time
  |

This works fine if I just return Self rather than Option<Self>, which I don’t fully understand (if the size of Self is known at compile time, then why isn’t the side of Option<Self>?).

Can anyone help me to understand this? What should I do here? Just returning Self won’t be enough as the function can fail.

It seems to work if you specifically restrict the function to cases where Self is Sized:

pub trait Object {
    fn parse(reader: &mut Read) -> Option<Self> where Self: Sized;
}

Thanks, that looks to fix it!

The reason is Option<T> (in particular in Some(T)) moves the value and since by default Self is ?Sized (i.e. it’s size may not be known at compile time), then it’s not clear how many bytes should be moved!

There are three main ways to specify sizedness (playground)

pub trait Object0 {
    fn parse(reader: &mut Read) -> Option<&Self>; // <-- any ptr is sized, &, &mut, Box, etc.
}

pub trait Object1: Sized {  // <-- trait itself is sized, hence Self
    fn parse(reader: &mut Read) -> Option<Self>;
}

pub trait Object2 {
    fn parse(reader: &mut Read) -> Option<Self> where Self: Sized; // <-- specifies the method can be called if Self is sized
}
2 Likes

I’m surprised that it allows -> Self too, but this seems to behave as if Sized is implicit. You still get an error if you try to implement this for an unsized type:

use std::io::Read;

pub trait Object {
    fn parse(reader: &mut Read) -> Self;
}

impl Object for str {
    fn parse(reader: &mut Read) -> Self {
        unimplemented!()
    }
}
error[E0277]: the size for values of type `str` cannot be known at compilation time
 --> src/lib.rs:8:36
  |
8 |     fn parse(reader: &mut Read) -> Self {
  |                                    ^^^^ doesn't have a size known at compile-time
  |
  = help: the trait `std::marker::Sized` is not implemented for `str`
  = note: to learn more, visit <https://doc.rust-lang.org/book/ch19-04-advanced-types.html#dynamically-sized-types-and-the-sized-trait>
  = note: the return type of a function must have a statically known size
1 Like

I think it has to do with something something well-formedness something something. Basically, my thinking comes from this (accepted, but not implented) RFC:

Which contains such language as this: (emphasis mine)

    fn panic_if_not_borrowed<'a, B>(cow: Cow<'a, B>) -> &'a B
//      where B: ToOwned
    {
        match cow {
            Cow::Borrowed(b) => b,
            Cow::Owned(_) => panic!(),
        }
    }
//  ^ the trait `std::borrow::ToOwned` is not implemented for `B`

However what we know is that if Cow<'a, B> is well-formed, then B has to implement ToOwned. We would say that such a bound is implied by the well-formedness of Cow<'a, B>.

Currently, impls and functions have to prove that their arguments are well-formed. Under this proposal, they would assume that their arguments are well-formed, leaving the responsibility for proving well-formedness to the caller. Hence we would be able to drop the B: ToOwned bounds in the previous examples

In other words, the issue with returning Option<Self> is simply that, as soon as this type appears anywhere in your signature, the bounds in the definition of struct Option<T> (which are T: Sized) must be proven for the type to be well-formed. Returning Self is not an issue because Self is well-formed.

…whatever that means.

1 Like

Since it can be related, let’s just mention object safety.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.