Methods taking self by value v.s. dynamically-sized types


#1

This fails, unsurprisingly:

struct DynamicallySized([u8]);

trait ByValue {
    fn take(self);
}

/// a.rs:10:13: 10:17 error: the trait `core::marker::Sized` is not implemented for the type `[u8]` [E0277]
/// a.rs:10     fn take(self) {}
///                     ^~~~
impl ByValue for DynamicallySized {
    fn take(self) {}
}

This as well:

/// a.rs:19:13: 19:17 error: the trait `core::marker::Sized` is not implemented for the type `Self` [E0277]
/// a.rs:19     fn take(self) {}
///                     ^~~~
trait ByValueDefault {
    fn take(self) {}
}

Now this is getting interesting: with a default method and a Self: Sized bound, we can implement a trait with a method that takes self by value for an dynamically-sized type. (std::iter::Iterator does this.) Though of course the type doesn’t have that particular method. (It still has other methods in the trait.) Still, in this case the default method is kinda pointless.

trait ByValueDefaultWhereSized {
    fn take(self) where Self: Sized {}
}

impl ByValueDefaultWhereSized for DynamicallySized {
}

Can we get rid of it? Doing so understandably errors, but maybe it shouldn’t.

trait ByValueWhereSized {
    fn take(self) where Self: Sized;
}

/// a.rs:30:1: 31:2 error: not all trait items implemented, missing: `take` [E0046]
impl ByValueWhereSized for DynamicallySized {
}

Language change proposal: when a non-default trait method has bounds which are not satisfied for a particular impl, defining that method in that impl should not be required (since it would not be available anyway).


#2

CC https://github.com/rust-lang/rust/issues/20021.


#3

So, we are currently threading a kind of subtle line here. The idea is that the idea of a fn signature whose argument (or return) type is unsized is not in error. However, calling such a fn is not possible, because the current compiler (and Rust ABI) has no way to pass (nor receive) ownership of a DST value. Similarly, implementing such a fn (that is, defining a fn body) is not possible. So you only get errors when you try to do one of those two things.

There are a variety of reasons we adopted these semantics (which are discussed in, among other places, RFC 1214):

  • Forwards compatibility with passing DST by-value.
  • Allowing the closure hierarchy to exist – in particular, we want Fn: FnMut: FnOnce, but FnOnce has a self method. We also want Fn and FnMut to be object-safe today. So if merely having a self method were enough to require that Self: Sized, then FnOnce: Sized would be necessary, and in turn Fn would not be object safe.
  • Similarly, for return values, many more traits would require a Sized bound than currently do.

Perhaps not the best compromise, but it’s the one we struck.