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
!)
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.