Search code examples
genericsreferencerustdynamic-sizing

Why is ?Sized required to pass a reference to a generic type parameter?


When I run the following code on Rust playground...

fn take_part_1<'a>(s: &'a str) -> &'a str {
    s.split(':').next().unwrap()
}

fn take_part_2<'a, T: 'a + AsRef<str>>(s: &'a T) -> &'a str {
    let b = s.as_ref().split(':').next().unwrap();
    b
}

fn main() {
    println!("{}", take_part_1("a:b"));
    println!("{}", take_part_2("a:b"));
}

... the compiler returns an error that doesn't make sense:

12 |     println!("{}", take_part_2("a:b"));
   |                    ^^^^^^^^^^^ doesn't have a size known at compile-time
   |
   = help: the trait `std::marker::Sized` is not implemented for `str`

I can fix it by adding ?Sized like so:

fn take_part_2<'a, T: 'a + AsRef<str> + ?Sized>(s: &'a T) -> &'a str {

Why is this ?Sized required (and why does the compiler point the error at the function call)? What does it do? Shouldn't I be able to pass the reference to an unsized object and have it just work?

What confuses me is that the non-generic implementation works as you'd expect, with no requirements for ?Sized (even though the compiler has pointed out that str isn't Sized!)


Solution

  • Most places where you use a type parameter, Rust will implicitly insert an additional Sized bound. That's because it is the most common case - if it didn't behave that way then you'd have to write the bound yourself everywhere and it would get repetitive and noisy.

    For example, your take_part function is exactly equivalent to this:

    fn take_part_2<'a, T: 'a + AsRef<str> + Sized>(s: &'a T) -> &'a str {
        let b = s.as_ref().split(':').next().unwrap();
        b
    }
    

    However, your function's implementation does not require T to be Sized because it only ever uses it by reference. Adding : ?Sized effectively removes that implicit bound, communicating to the type checker that the size of T doesn't need to be known, so your function is as general as possible. In this case, making it more general allows it to be called with T as str, a dynamically sized type.