Request for a practical explanation of the Sized trait

Greetings everyone, I'm struggling to grasp the concept here, what is the difference in practice between a generic ?Sized and generic Sized.

Also, suppose I have the two following structs:

struct Qux<T: MyTrait> {
    field: Rc<T>
}

and

struct Baz<T: MyTrait> {
    field: T
}

Since I can implement MyTrait both for T and Rc<T> why should these definition be so different? The former requires T to be ?Sized, what are the practical implications?

In most situations where you have a generic type parameter, the parameter is implicitly bound by Sized. The reason is that it would be too onerous to have to specify it yourself every place it is (currently) needed -- such as function inputs and outputs.

// So onerous
fn foo<F: Sized, R: Sized, E: Sized>(f: F) where F: Fn() -> Result<R, E>

The ?Sized bound means "remove the implicit Sized bound". It's not a negative bound -- types that are Sized are still permitted -- it's a loosening of the implicit bounds.

The main place where Sized is not an implicit bound is trait declarations.

trait MyTrait /* `where Self: Sized` is NOT an implicit bound */ {}

So you can implement MyTrait for dynamically sized types... and so the compiler to implement MyTrait for dyn MyTrait.


The Rc example doesn't require the T: ?Sized bound on its own. Probably you're trying to put a dyn MyTrait in it? Any dyn Trait does not implement Sized -- they have dynamic sizes, not statically known sizes. (Same with [T] and str and a few others.) So this is when you need : ?Sized -- when your type parameter might represent a type that does not implement Sized. If you want T to be dyn Trait or str or [T], etc, that's when you need T: ?Sized.

2 Likes

?Sized is a special bound that relaxes the implicit Sized bound. Here’s the definition given in the Rust Reference.

The Book has more info about ?Sized and dynamically sized types.

1 Like

Oh, and you asked why they are different. If you can't pass non-Sized types directly, how can you pass them? You put them behind some sort of indirection -- like a &dyn MyTrait or a Box<dyn MyTrait>. Or... an Rc<dyn MyTrait>!

Rc<SomethingThatImplementsMyTrait> can also be coerced to a Rc<dyn MyTrait>, because Rc<T> implements CoerceUnsized. You can see other types which have this ability under the implementors section of that page.

Baz could hold non-Sized types in field too, but that would make Baz itself a DST (dynamically sized type; not Sized). Which is almost never what you want. Qux is still Sized due to the indirection of Rc.

1 Like

Yes, correct, I'm trying to sneak in a dyn MyTrait. Should I expect performance differences between Qux<dyn MyTrait> and Baz<Rc<dyn MyTrait>>? Is there a best among these two?

I would expect no performance difference. Which is best depends on what you're doing. If the Rc makes sense for the struct, I'd make it part of the field. If the functionality of the struct doesn't have anything to do with Rc, I'd leave it out.

1 Like

Thank you all very much for all the precious knowledge.

1 Like

It doesn't require T to be ?Sized, because that's not what the syntax T: ?Sized means.

Unlike every other trait, the Sized trait is actually required by default. This specifically means that there is an invisible requirement for Sized, always, unless you say otherwise.

The first two structs means exactly the same thing as the second two.

To get rid of that implicit bound, you use ?Sized. This is a special syntax, designed back in 2014, though a lot of the discussion was done before the RFC process was formalized, in a series of blog posts (warning: this is for pre-1.0 Rust, but it spells out the rationale pretty well):

So Ben Blum has doing some investigation into the full implications of the Sized bound that I proposed as part of the dynamically sized types post. It’s clear that, if we change nothing else, the impact of Sized will be somewhat greater than I thought. He estimates somewhere around 40% of the files in libstd need at least one Sized bound; the actual number may wind up being somewhat higher.

2 Likes

Can someone remind me what these @ and ~ pointer types are? I think ~ is like Box, right? What is @ or @mut? (And maybe, if I don't misremember what I read about ~, there was something about ~[T] being actually rather like Vec<T> and not Box<[T]>?)

What is @ or @mut?

Manish talks about them in passing from a blog post from a while ago: Designing a GC in Rust - In Pursuit of Laziness

Specifically:

Rust itself had a garbage collector until a bit more than a year ago. These “managed pointers” ( @T ) were part of the language. They were removed later with a plan to make GC a library feature.

I think ~ was just for regular owned pointers (aka Box), but this is very before my time with rust. I don't know if that meant ~[T] was like Vec or what.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.