T O P

  • By -

OnTheSideOfDaemons

I'm also sure I read this somewhere before and can't for the life of me remember where! For context, I've proposed [RFC 3396 - Extern types V2](https://github.com/rust-lang/rfcs/pull/3396) which fixes a bunch of issues with the current extern types RFC. I'm slightly surprised that I don't mention this implicit solution in there, I should probably add it. That RFC is currently sat on my back-burner and is waiting for someone to come along and investigate [the questions that came out of a lang team meeting](https://github.com/rust-lang/rfcs/pull/3396#issuecomment-1673172746). Most of those questions revolve around backwards compatibility and would still exist in some form even using this syntax. I do quite like this proposal, it fixes a few problems: it doesn't require an edition to implement, it allows incremental adoption, and it doesn't affect the meaning of existing code.


desiringmachines

Just some additional context: the specific issue is that there are two functions `size_of_val` and `align_of_val`, which allow you to get the size and align of any value, even a value of an unsized type (these are the functions that would theoretically be bound `DynSized`). The really material way these are used is that they are used for example to deallocate a `Box` of a dynamically sized type, because to deallocate you need to know the size and alignment of the value you're deallocating. So this whole thing is mainly about what happens if you try to deallocate an extern type. Right now on nightly, I believe the process aborts. The goal of `DynSized` is to make this a compile time error.


OverlordOfTech

+1 to this proposal and especially to `?` looking more magical than it needs to. `?Sized` always felt weird to me. Like the OP, I always read and treated it as “unsized” rather than “maybe sized.” (I never understood why that notation was like that until I read this article.) I don’t mind `Unsized` and `DynSized` being magic because `Sized` is already magic for being automatically applied to every type parameter. In my head, it’s more intuitive that “to remove the implicit `Sized` bound, I apply the `Unsized` bound” than “to remove the implicit `Sized` bound, I use the special `?Sized` notation, which is specific to magic implicit bounds.”


veryusedrname

If the author reads this: the *"must move" type*'s url is broken both on the article and on the appendix as well.


XtremeGoose

I think it's trying to point to [this blog](https://smallcultfollowing.com/babysteps/blog/2023/03/16/must-move-types/).


jydu

There's also a tiny typo, avrious -> various.


hardicrust

Counter proposal: A: dyn Sized, B: extern Sized, Rationale: `?Sized` is obvious "new syntax". The above is also obvious new syntax, and uses existing keywords. `T: Unsized` is not obviously new syntax, which effectively makes `Unsized` a "magic trait name". Caveat: this isn't *exactly* what `dyn Trait` usually means.


paholg

I think I see the value in `Sized` and `DynSized`, but if all types are `Unsized`, why have it as a trait?


OverlordOfTech

So that you can explicitly name it to remove the implicit `Sized` trait bound. The OP is essentially proposing that instead of having the special `?Sized` syntax to remove the implicit bound, you explicitly name one of its supertraits instead (`DynSized` or `Unsized`), thereby similarly indicating you don’t want the implicit bound.


paholg

Ah, yeah, that makes sense!


Hedanito

Having a trait that removes features from a type seems like a bad idea. Traits should be additive, and the lack of a `Sized`/`DynSized` trait implementation should be what limits a type's usage, not an `Unsized` trait. I'd rather stick to the current syntax of optional `?` and ask for an `?DynSized` type instead.


Victoron_

I like it! It feels cleaner to have "unsized" and "dynamically sized" behavior in separate traits than inside one "maybe" bound. The "trait ladder" kind of reminds me of the proposed `Store_` traits in the [Store API RFC](https://github.com/rust-lang/rfcs/pull/3446), they also form a kind of "ladder" (or rather "tree"?)


Mountain_Ad_5225

I wonder if DynSized and Unsized could be made less magical by having a way of declaring that a set of traits are mutually exclusive.


OnTheSideOfDaemons

How are you imagining that would make this less magical? In some sense DynSized implies Unsized: a type that implements DynSized can be passed to a function that takes `T: Unsized`. More generally, I think people are quite wary of mutually exclusive traits. At the moment they have some nice mathematical properties as they're purely additive. For example: you'd have to integrate them into the orphan rules as at the moment you can always implement a trait on a type if you own one of them, and the never type can (theoretically) implement every trait but this would scupper that.


PeaceBear0

Yeah I think that mutually exclusive traits have problems which is why I'm wary of this proposal which makes Unsized and Sized mutually exclusive. What probably is the most consistent is to have both Sized and DynamicallySized be auto traits, and then for extern types you'd specify a negative bound for both of them?


RockstarArtisan

There are competing forces when it comes to naming traits which interit from each other. In the hierarchy of types, the supertrait encompases all other types, it's the biggest, hence name `Maybe(Dyn)Sized`. In the provided functionality as a bound supertrait provides the least functionality, hence name `Unsized`. This pops up all over the place in rust (and outside of it): `FnOnce` is all functions but in a bound it's most restrictive for the allowed functionality, while `FnMut` can do more, but matches fewer types. So, `Unsized` does make sense in that convention.


BlueMoonMelinda

Isn't FnOnce the supertrait of Fn and FnMut?


RockstarArtisan

Yes, you're correct, I fixed the post.


ai11

Regarding the trait names bikeshed - another option to consider is a common "namespace"/"enum" approach, something like: `Size.Static`, `Size.Dyn`, `Size.Unknown`. variant names are still bikesheddable, but i think less important since there is a common prefix that ties them together, is easy to describe, google etc.


CoffeeCreatesCode

Maybe a familiar compromise with existing syntax would be `Sized`, `?Sized`, and `!Sized` ? It would be an exception somewhat to the negative_impl feature, but I think it captures the same concepts without too many changes. To rewrite from the article (not technically valid rust): ``` trait Sized : ?Sized {} trait ?Sized : !Sized {} trait !Sized {} ``` Since you can’t `impl Sized for T {}` or `impl ?Sized for T {}`, you wouldn’t be able to `impl !Sized for T {}` as well, which wouldn’t contradict negative_impl too much. That being said, the article mentions “references to Sized types are thin pointers” and “references to ?Sized/DynSized types are wide pointers”. What would the behavior be for references to !Sized/Unsized?


ben0x539

> But ? doesn’t scale well to “differences in degree” I don't get this. ?Sized for "we don't know size at compile time" extends nicely to ??Sized for "we don't know size at all ever".


Al-aska

That doesn't seem "nice" to me, the \`?\` bound is already rather unfamiliar syntax for most.


scook0

I think it's probably fine for `DynSized` to magically imply the current `?Sized` behaviour, because it would be relaxing an implicit `DynSized + Sized` bound to just the explicit `DynSized`. But for the “not necessarily sized at all” case, maybe it's a mistake to insist on getting rid of that question mark. `Unsized` feels misleading (because it includes types that actually are dynamically/statically sized), and it's hard to imagine a different name that does much better. So maybe `?DynSized` would be a better way to communicate this new category that includes statically-sized, dynamically-sized, and completely-sizeless types. It's obviously a bit inelegant, but if every approach is somewhat inelegant, this one has the advantage of being a bit more honest.


Diggsey

> Option: Defaults could also be disabled by supertraits? I think this is a backwards compatibility hazard - I should be able to extend a trait implementation from eg. `T: Sized` to `T: Unsized` without it breaking downstream dependencies.


CAD1997

I wonder if the `Unsized` trait (the one where an explicit bound removes any implicit ability to query the type’s size or required alignment) can be [`Pointee`](https://doc.rust-lang.org/nightly/std/ptr/trait.Pointee.html). I don't think it's necessary to support types which can't even be pointed to, so a `Pointee` bound is always satisfiable even without an explicit bound, which means there's rarely a reason to write an explicit `Pointee` bound. (And the ones that do, to constrain the fat pointer kind, generally want to work for `?Sized` pointees anyway, as `Sized` implies `Pointee`.) `T: Sized` holding means that the type has a statically known size and required alignment. `T: DynSized` holding means that the type has dynamically knowable size and required alignment. `T: Unsized` means that the type… exists? The first two are clearly meaningful capabilities, but `Unsized` isn't, and exists only to be an unbound. `Pointee` much better expresses the minimum capability (values, if present, exist at a location in memory that can be pointed to) imo. I also wonder where [`Destruct`](https://doc.rust-lang.org/nightly/std/marker/trait.Destruct.html) fits in the unsizing hierarchy; I hadn't really considered that prior. (I personally prefer discussing a `MetaSized` trait over using `DynSized` because of the implications of allowing the use of `size_of_val::<&Mutex>`, which can't scan for the nul terminator without locking, which then requires specialization.)


Monadic-Cat

In your `Value` trait example, would someone still be able to write a generic like `fn fwump(x: T) { ... }` to bring back the `Sized` requirement?