-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Custom DSTs #2594
RFC: Custom DSTs #2594
Conversation
It's likely that this RFC should be merged with some amount of #2580, specifically the stuff for mentioning VTables. |
Also, add more required traits
#2255 also seems like a relevant link, since I'm not entirely sure whether those issues are settled. I think the current status of that discussion is:
This seems like a crucial (albeit not obviously crucial) piece of the argument in favor of this RFC, though I've never seen it spelled out like this before, so it seems worth asking whether I got all of that right. My only actual concern with the RFC is the same one that niko articulated last time: this doesn't appear to be a hard blocker or a game changer for anything, so do we really want to prioritize work on it in the near future? But we can always accept the RFC and not implement it for a while; this is clearly the best proposal so far and the design space seems pretty thoroughly explored at this point. To double-check: The |
Instead of declaring a type which would otherwise be In the current language the only such types are The problem there of course is not knowing what the alignment of that field should be, so maybe instead of relying entirely on Just in general it feels slightly nicer and less magical if the act of implementing a trait only causes a trait to become implemented, rather than another one to become deimplemented. (There would be some precedent for the latter w.r.t. Another question: I'm guessing the language implementation would implicitly call the provided |
@glaebhoerl I would argue that an Places the language calls |
@Ixrec technically you're right, but it's such an incredible ergonomics win, and it's been suggested so many times. It also is an incredible ergonomics win for Matrix libraries, imo, since they'll suddenly be able to use the I'd also argue it should be a blocker for |
Yeah, I'm totally on board with actually doing this. Just wanted to be sure I understood it all correctly.
Interestingly, the extern types RFC states "before this is stabilized there should be some trait bound or similar on them that prevents [ |
@Ixrec ah, good, I literally started writing this because someone was suggesting we panic on |
Sorry, by "figure this out" I meant come to an explicit consensus on whether or not panicking was the least bad solution to that problem. Now that I look closer, it seems like that discussion petered out after rust-lang/rust#43467 (comment) which explicitly suggests we make |
Since we have a reasonable type system solution in this RFC, I don't think panicking is a reasonable choice. |
# Unresolved questions | ||
[unresolved-questions]: #unresolved-questions | ||
|
||
- Bikeshedding names. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DynamicallySized
-> DynSized
;
Feels distinctly more rust-y to me since we have dyn
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And DynSized
has been used in previous (non-RFC?) proposals and discussion, so more people are already familiar with it. Not the best argument, but I was surprised to see DynamicallySized
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like longer names, probably the C++ programmer in me. This name's not all that important tho :P
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am very mildly in favor of the longer name if this is expected to be a trait bound that relatively few users will ever need to write directly (it is, right?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah - it gets implied by ?Sized
. The only time you'd need to write it is for T: ?DynamicallySized
, in case you want to be generic wrt extern types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer longer names, but DynSized
feels more consistent with the rest of Rust to me and that trumps my "longer name" preference.
text/0000-custom-dst.md
Outdated
- `extern type`s do not implement `DynamicallySized`, although in theory one | ||
could choose to implement the trait for them | ||
(that usecase is not supported by this RFC). | ||
- `T: DynamicallySized` bounds imply a `T: ?Sized` bound. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
N.B. Type: Trait
is not a bound (because Trait + 'static
is a bound...), it is a constraint.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the distinction?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So a trait Foo { ... }
has an implicit first parameter we call Self
which is not present in Haskell; This means that when you say something like MyTrait
, it has the kind bound = k1 -> constraint
where k1 : type | lifetime ;
. The operator :
in this context can then be understood as something of kind type -> bound -> constraint
. Notice that when you say Show Int =>
in Haskell, Show :: * -> Constraint
(a "bound") but Show Int :: Constraint
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah, I see!
Something like this RFC would be really nice for the However, |
@jturner314 That's unworkable because it would break existing code generic over references and pointers. I think what you'd want to be able to use this feature is const generics and actually parametrize those types by the number of dimensions, e.g. |
*/ | ||
type Metadata: 'static + Copy + Send + Sync + Eq + Ord + Unpin; | ||
|
||
fn size_of_val(&self) -> usize; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if these methods are implemented to return invalid values (UB?)
struct CWStr([u16; 0]);
unsafe impl DynamicallySized {
type Metadata = ();
fn size_of_val(&self) -> usize { 3 }
fn align_of_val(&self) -> usize { 3 }
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UB, that's why it's an unsafe impl.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The text should spell out exactly why DynamicallySized
is unsafe
to implement and which guarantees implementations have to uphold.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ubsan Can you add these additional docs? I'd find it really helpful.
1We actually use a small-vector optimization for four axes or less, but that's irrelevant for this discussion.
Yeah, that's what I was afraid of. Another possible approach that would enable this use-case is providing an owned equivalent of Out of curiosity, what's a case where the "use |
I guess the simplest case would be any code relying on the fact that copying a |
Good point. We could treats panics as aborts in this case, though. (We already do this kind of thing for I thought of another potentially breaking case – code could rely on the original and copy from Another potentially breaking case is dealing with uninitialized memory. If the type of the uninitialized memory is Yeah, I'm coming to the conclusion that a |
@jturner314 Rust is designed with "moves and copies are literally bitwise copies always" as a core promise, as contrasted with C++. Unsafe code is free to rely on this as much as it wants to. If it's opt-in, maybe it could happen. But I also feel like the dynamic number of dimensions is less common (or will become less common with const generics). |
I understand, and I agree that it's probably a fairly uncommon use-case. It's just disappointing that this RFC is so close and yet not quite sufficient for all views in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a few questions as comments.
Additionally, I'm having trouble understanding the "2D Views of Planes" example. From what I can figure out, it seems to be trying to represent a 2D, column-major, contiguous, owned array type (PlaneBuf
) and a 2D (non-contiguous?) view type (Plane
). However, it seems to treat the "width" as the number of rows (i.e. column length) and the "height" as the number of columns (i.e. row length); this is the reverse of how they're usually defined. Also, it's unclear if Plane
is supposed to be contiguous in memory or not.
I'd be happy to fix/improve the example, but I have a couple of questions:
- Is the data of a
Plane
assumed to be contiguous in memory? I guess 'no' since there's astride
field separate from thewidth
andheight
. - How should
size_of_val
be defined for non-contiguousPlane
s?
} | ||
``` | ||
|
||
These functions return the size and alignment of the header of a type; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the "header of a type"? Is it the Metadata
for that type? The Metadata
plus any padding between the Metadata
and the pointer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, the header is the bit of the type that is unsized - it's what sizeof(FAM_type)
returns in C; i.e.
struct fam {
int x;
char buf[];
};
_Static_assert(sizeof(fam) == sizeof(int), "");
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, that makes sense. Will you please define "header" in the RFC itself?
and for existing DSTs, | ||
|
||
```rust | ||
assert_eq!(size_of_header::<[T]>(), 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't this be the size of one usize
(representing the length of the slice), not 0?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, because size_of_header
returns the minimum size of [T]
, not the size of the metadata.
|
||
```rust | ||
assert_eq!(size_of_header::<[T]>(), 0); | ||
assert_eq!(align_of_header::<[T]>(), align_of::<T>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is the alignment equal to the alignment of T
? Wouldn't it be the alignment of usize
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because this is the alignment of [T]
, not the alignment of &[T]
|
||
```rust | ||
#[repr(C)] | ||
struct CStr([c_char; 0]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could this be struct CStr([c_char])
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No - you specifically unsize sized types. The definition here is just the "header"; similar to a C definition
struct CStr {
char buffer[];
};
I have an API that semantically accepts a I wish I had a |
What's the latest on this RFC in any case? |
Those two new language traits are at the intersection of rust-lang#1861, rust-lang#2580, rust-lang#2594, and this RFC's raison d'être is solely to try to get some progress for those 3 RFCs all at once by specifying their common core part.
Some updates from the @rust-lang/lang team:
|
// - Unpin + Copy - all pointer types | ||
// - Send + Sync - &T, &mut T | ||
// - Eq + Ord - *const T, *mut T | ||
type Metadata: 'static + Copy + Send + Sync + Eq + Ord + Unpin; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When written this way, these bounds can never be relaxed later as doing so would be a breaking change. An alternative formulation would be to turn them into constraints.
It's probably not wise most beneficial to move all bounds. But can we be sure all future use cases for extern types guarantee 'static
and Send
, Sync
, Unpin
in particular?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An alternative formulation would be to turn them into constraints.
Both are handled almost identically in the compiler. In either case a user of Pointee
is allowed to depend on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hm, I didn't realize. Then what would be a proper way of introducing those constraints?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it is possible to add constraints to the implementer without allowing the user to depend on the constraints.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it realistic that these bounds can be relaxed?
'static
- even something as simple as creatingBox<TypeWithNonStaticMetadata>
and callingBox::leak
would invalidate itCopy
- creating shared reference would become impossible (yeah, you didn't mention it, but for completeness...)T: Sync
implies&T: Send
and the users can rely on it today.print_in_other_thread
would stop compiling in this example if the bounds were relaxed- Relaxing
Unpin
would mean that it'd be unsound to move&mut T
. Even if conceivable, it'd be a massive footgun.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'static - even something as simple as creating Box and calling Box::leak would invalidate it
Who's to say that all custom DSTs can be boxed, or allocated?
T: Sync implies &T: Send and the users can rely on it today. print_in_other_thread would stop compiling in this example if the bounds were relaxed
As noted T: ?Sized
will imply T: Contiguous
and similarly could imply <T as Pointee>::Metadata: Sync
etc. such that all soundness requirements of the existing impl<T: ?Sized + Sync> Send for &T
are fulfilled. Then that print_in_other_thread
would need to be relaxed further when it wants to support arbitrary metdata.
Relaxing Unpin would mean that it'd be unsound to move &mut T. Even if conceivable, it'd be a massive footgun.
Same as above, if Unpin
is implied by T: ?Sized
(or stronger) then the footgun is hardly massive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that could work, I think.
Then that
print_in_other_thread
would need to be relaxed further when it wants to support arbitrary metdata.
It's a bit sad that many generics will have to be modified to become more flexible but there's probably no better solution. At least I don't know of such. Perhaps an automated analysis tool would help here.
The rendered link in the OP is broken. Here's a current one. |
The lang team discussed this as part of following up on "Backlog Bonanza" on 2021-03-17. Custom DSTs are not part of the roadmap for 2021. We have already accepted #2580 (tracking issue rust-lang/rust#81513). We want to see that work completed and gather experience with what it offers; after that has happened, we would consider Custom DSTs as a roadmap item. @rfcbot postpone |
Team member @pnkfelix has proposed to postpone this. The next step is review by the rest of the tagged team members:
No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
🔔 This is now entering its final comment period, as per the review above. 🔔 |
The final comment period, with a disposition to postpone, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. The RFC is now postponed. |
This has been a long time coming - similar to #2580, but more general.
On the unresolved question aboutI've chosen to make this change.?Sized -> DynamicallySized
, and the need for?DynamicallySized
. I think this is actually a reasonable change which makesextern type
s act much nicer around generic functions that already exist, and fixes all the Rust code that has been written already. No longer is, for example,Box<extern-type>
allowed.Note that
DynamicallySized
is a new language trait, on the level ofSized
andCopy
.Rendered