Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.
Sign upRFC: Pointer metadata & VTable #2580
Conversation
SimonSapin
added some commits
Oct 25, 2018
This comment has been minimized.
This comment has been minimized.
|
A previous iteration of this RFC is also visible at #2579. It was based on @Gankro’s proposal https://gist.github.com/Gankro/b053cb4d1cb3bcaec070de89734720f7 |
kennytm
reviewed
Oct 27, 2018
text/0000-ptr-meta.md Outdated
This comment has been minimized.
This comment has been minimized.
mikeyhew
commented
Oct 27, 2018
•
|
First of all, thanks for opening this rfc. It's the right way to fix the raw::TraitObject API, and is a big step toward custom DST. My only criticism is I think the Vtable type should be generic over the trait object type, as mentioned in the alternatives section. Having different I like the name |
This comment has been minimized.
This comment has been minimized.
|
cc @ubsan |
oli-obk
reviewed
Oct 27, 2018
text/0000-ptr-meta.md Outdated
text/0000-ptr-meta.md Outdated
text/0000-ptr-meta.md Outdated
text/0000-ptr-meta.md Outdated
text/0000-ptr-meta.md Outdated
text/0000-ptr-meta.md Outdated
| /// | ||
| /// [dst]: https://doc.rust-lang.org/nomicon/exotic-sizes.html#dynamically-sized-types-dsts | ||
| #[lang = "pointee"] | ||
| pub trait Pointee { |
This comment has been minimized.
This comment has been minimized.
oli-obk
Oct 27, 2018
Contributor
so... I'm assuming the compiler implements
default impl<T: ?Sized> Pointee for T {
type Metadata = &'static Vtable;
}
impl<T: Sized> Pointee for T {
type Metadata = ();
}
impl Pointee for str {
type Metadata = usize;
}
impl<T: Sized> Pointee for [T] {
type Metadata = usize;
}Which means theoretically we could make Vtable generic over T allowing the drop_in_place method to take a raw pointer with the correct pointee type?
This comment has been minimized.
This comment has been minimized.
SimonSapin
Oct 28, 2018
Contributor
These impls would be accurate in current Rust, but what I had in mind instead was that the compiler would automatically generate impls, similar to what it does for the std::marker::Unsize trait. As far as the standard library is concerned these impls would be "magic", not based on specialization.
Regardless, yes, making VTable generic with a type parameter for the trait object type is possible.
text/0000-ptr-meta.md Outdated
| (Answer: they can use a different metadata type like `[&'static VTable; N]`.) | ||
|
|
||
| `VTable` could be made generic with a type parameter for the trait object type that it describes. | ||
| This would avoid forcing that the size, alignment, and destruction pointers |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
SimonSapin
Oct 28, 2018
Contributor
Without a type parameter, x.size() with x: &'static VTable necessarily executes the same code for any vtable. With a type parameter, x: &'static VTable<dyn Foo> and x: &'static VTable<dyn Bar> are different types and could execute different code. (For example, do table lookup with different offsets.) However, keeping the offset of size the same within all vtables might be desirable regardless of this API.
scottmcm
reviewed
Oct 28, 2018
| `VTable` could be made generic with a type parameter for the trait object type that it describes. | ||
| This would avoid forcing that the size, alignment, and destruction pointers | ||
| be in the same location (offset) for every vtables. | ||
| But keeping them in the same location is probaly desirable anyway to keep code size |
This comment has been minimized.
This comment has been minimized.
| type Metadata; | ||
| } | ||
| /// Pointers to types implementing this trait alias are |
This comment has been minimized.
This comment has been minimized.
scottmcm
added
T-lang
T-libs
labels
Oct 28, 2018
This comment has been minimized.
This comment has been minimized.
|
@mikeyhew I’ve very open to adding a type parameter to As to supporting super-fat pointers with multiple vtable pointers, as mentioned in the alternatives section I believe this design doesn’t prevent it. Types that don’t exist yet and are added to the language in the future (possibly custom DSTs) can have a different metadata type. For |
Centril
reviewed
Oct 28, 2018
| pub unsafe fn drop_in_place(&self, data: *mut ()) { ... } | ||
| } | ||
| ``` | ||
|
|
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
SimonSapin
Oct 28, 2018
Contributor
I came up short trying to think of a reason not to do this at all (as opposed to doing it differently). Suggestions welcome.
| and (hopefully) more compatible with future custom DSTs proposals, | ||
| this RFC resolves the question of what happens | ||
| if trait objects with super-fat pointers with multiple vtable pointers are ever added. | ||
| (Answer: they can use a different metadata type like `[&'static VTable; N]`.) |
This comment has been minimized.
This comment has been minimized.
Centril
Oct 28, 2018
Contributor
Should then [&'static VTable; 1] for dyn SomeTrait be used to make that transition smoother and to fit better with const generics?
This comment has been minimized.
This comment has been minimized.
SimonSapin
Oct 28, 2018
Contributor
This would make some sense if we were definitely gonna have super-fat pointers with multiple separate vtable pointers as fat pointer metadata. But if we don’t and end up with a different solution to upcasting, we’ll end up with a always-single-item arrays for no reason. This isn’t really the thread to get into that discussion, but my opinion is that super-fat pointer have a significant enough size cost that I’d much prefer a different solution.
Perhaps an alternative for this RFC, more neutral with respect super-fat pointers v.s. not, would be to have type Metadata = VTable<Self>; for trait objects. (See other comments about VTable’s possible type paramater.) With the pointer/reference indirection hidden away in private fields of the VTable type, this design would be compatible with having VTable<dyn A + B> contain two pointers in the future.
This comment has been minimized.
This comment has been minimized.
SimonSapin
Oct 28, 2018
Contributor
Also, is there a use case for generic code that accepts any trait object with any number of vtable pointer but not other kinds of DSTs?
| and (hopefully) more compatible with future custom DSTs proposals, | ||
| this RFC resolves the question of what happens | ||
| if trait objects with super-fat pointers with multiple vtable pointers are ever added. | ||
| (Answer: they can use a different metadata type like `[&'static VTable; N]`.) |
This comment has been minimized.
This comment has been minimized.
Centril
Oct 28, 2018
Contributor
Are we doing the proposals in the right order? Shouldn't we focus on dealing with dyn A + B + C, upcasting, and such things first? Also, cc #2035.
This comment has been minimized.
This comment has been minimized.
SimonSapin
Oct 28, 2018
Contributor
I believe the design proposed here is compatible enough with various options for multi-traits trait objects and upcasting such that there isn’t a strong dependency, and we don’t need to block this RFC on everything else being settled.
|
|
||
| * The name of `Pointee`. [Internals thread #6663][6663] used `Referent`. | ||
|
|
||
| * The location of `VTable`. Is another module more appropriate than `std::ptr`? |
This comment has been minimized.
This comment has been minimized.
Centril
Oct 28, 2018
Contributor
and should it be called Dictionary instead? ("type class dictionary")
This comment has been minimized.
This comment has been minimized.
kennytm
Oct 28, 2018
Member
Big -1 to calling it Dictionary since this typically means a key-value map (example: C#, Swift, Python).
Furthermore, here in Rust the VTable is implemented as an array of function pointers, not a HashMap (unlike e.g. Python where it is really implemented as a dict), so calling it Dictionary obscures the alleged complexity.
This comment has been minimized.
This comment has been minimized.
SimonSapin
Oct 28, 2018
Contributor
Even if the implementation happened to use HashMap, I’d prefer VTable since it’s more descriptive of the role of this type. (As opposed to: dictionary of what?) I believe that vtable is a well-enough established term of art.
kennytm
and others
added some commits
Oct 28, 2018
This comment has been minimized.
This comment has been minimized.
|
Regarding making struct VTable<Dyn> { … }… then it could be used with any type as a parameter. What does struct VTable<Dyn> where Dyn: ?Sized + std::marker::DynTrait { … }Do we want such a trait? |
This comment has been minimized.
This comment has been minimized.
|
Hmm, maybe we could get away with this? struct VTable<Dyn> where Dyn: ?Sized + Pointee<Metadata=Self> { … } |
This comment has been minimized.
This comment has been minimized.
|
Is there any way to ensure minimal compiler time is wasted performing monomorphization on useless vtable type params? If not, I would rather the API just be less safe. |
This comment has been minimized.
This comment has been minimized.
mikeyhew
commented
Oct 28, 2018
|
@Gankro the word "useless" sounds a little strong there... the purpose it to make sure you can't use a vtable from a |
This comment has been minimized.
This comment has been minimized.
|
You're supposing a situation where I somehow am writing code with two vtable types floating around, in which case I have two data pointers, and nothing can stop me from swapping the data pointers, producing the exact same effect. |
SimonSapin
referenced this pull request
Oct 28, 2018
Open
Tracking issue for `raw` stabilization (`raw::TraitObject`) #27751
SimonSapin
added some commits
Nov 14, 2018
This comment has been minimized.
This comment has been minimized.
|
A few questions:
|
This comment has been minimized.
This comment has been minimized.
|
The point of the bounds is to provide forward compatibility with Custom DSTs. For sure you can't |
This comment has been minimized.
This comment has been minimized.
|
@kennytm why is the list needed? what relies on those guarantees? |
This comment has been minimized.
This comment has been minimized.
|
@withoutboats This is needed for Custom DSTs because For example, if // all safe!
let ptr = <*const CustomDst>::from_raw_parts(&(), vec![1,2,3]);
let ptr2 = ptr;
let ptr3 = ptr;
let vec2 = metadata(ptr);
let vec3 = metadata(ptr);The whole list We could restrict the Again, the list is irrelevant if no one can (A copy of the relevant section from kennytm#2)When manipulating generic DSTs, we often need to use its metadata type. This can be exposed via associated type if the DST implements some trait. type Pointee {
type Metadata: Sized;
}That is, every type We need to ensure all existing traits implemented for pointers and references (
Thus, the final constraint would be: trait Pointee {
type Metadata: Sized + Copy + Send + Sync + Ord + Hash + Unpin + 'static;
}In principle impl<T: ?Sized> !RefUnwindSafe for T {}
impl<T: ?Sized> RefUnwindSafe for T where T::Metadata: UnwindSafe {}Fortunately, The |
This comment has been minimized.
This comment has been minimized.
It’s rather niche, and likely we’ll end up with a small number of library that do that, but for example |
This comment has been minimized.
This comment has been minimized.
|
@kennytm your comments make sense, thanks! and it is concerning that this represents a forward compat hazard with adding new autotraits like However, I don't think the metadata influences the implementations of EDIT: And I'm not so convinced that Biascally, I think |
This comment has been minimized.
This comment has been minimized.
|
@withoutboats For now the both Since we need to keep these impls without adding new impl<T: ?Sized> Hash for *const T { ... }
impl<T: ?Sized> Ord for *const T { ... }and we need to keep comparing the metadata to maintain the runtime behavior, we'll need to add the For Pin<&mut &CustomDst> // this is a _thin_ pointer to a fat pointerThe inner reference is always Fortunately there's no safe way convert an arbitrary |
This comment has been minimized.
This comment has been minimized.
|
@kennytm I was thinking of reference types, not pointer types. The behavior of pointer types is interesting, maybe even unfortunate.. |
rkruppe
referenced this pull request
Nov 15, 2018
Closed
Representation of Rust references (`&T`, `&mut T`) and raw pointers (`*const T, `*mut T`) #16
This comment has been minimized.
This comment has been minimized.
bjorn3
commented
Nov 16, 2018
|
Because of |
This comment has been minimized.
This comment has been minimized.
|
|
Centril
added
A-traits
A-traits-libstd
A-types-libstd
A-repr
A-trait-object
labels
Nov 22, 2018
This comment has been minimized.
This comment has been minimized.
mikeyhew
commented
Nov 25, 2018
|
Would it be OK if we changed the struct DynTraitMeta<T: ?Sized> {
vtable: &'static Vtable,
// not exactly sure what to put here, but this works for now. It must contain `T`.
_marker: PhantomData<*const T>,
}This type could be kept unstable, so that it could be changed in the future (potentially to I'm uneasy about using |
This comment has been minimized.
This comment has been minimized.
mikeyhew
commented
Nov 25, 2018
|
Here is one more argument for having different metadata types for different
If Now someone might say that "from_raw_parts" is an unsafe-sounding API that you would never expect to be safe. But I think that's just because of the name. It comes from We have an opportunity to make something that sounds unsafe — messing around with pointer metadata and vtables — actually be a completely safe thing to do. I think that's pretty cool, and it's a great example of the kind of programming Rust lets you do. And there are no downsides here. The only one that was suggested — potentially extra monomorphization because of an extra type parameter — doesn't hold any water. And even if it did, it seems like something that would come up so rarely that it wouldn't matter, or that could be fixed by future optimizations. |
This comment has been minimized.
This comment has been minimized.
Extending data invariants like this has repercussions beyond just this RFC. For example, it can make unsafe code invalid even if it doesn't interact with the abstractions introduced here, and if we take this step we should do due digilence to check that doesn't affect any unsafe code we want to allow. Not necessarily a problem, but it's extra work that needs to be motivated.
I don't understand how anything substantial can be made safe this way. The data pointer part of a raw pointer can be invalid, so even if one defines a method with raw pointer receiver type and can call it that way, that method couldn't really do anything with the trait object (if it's safe). While there are some bits and pieces of information one might want to put into a trait that are solely about the type it's implemented on rather than a specific object,
This function is trivial and all the uses I know of are embedded into a context where its use is crucial to other, far more subtle, unsafe code. For example, making this function safe does not help at all when converting the If not, I do not see any reason why this would make anything safer. As a general principle, usage of nominally-safe functions deeply embedded in unsafe code is often critical for the soundness of the whole code, so the advantage of making more functionality safe lies in enabling entirely safe code, not in being able to move one important line out of the
Sorry, just asserting that is not convincing. That type parameters lead to monomorphization today is a fact. That monomorphization generally causes binary size issues is also well established. That only a neglegible amount of code would be redundantly monomorphized is not obvious to me -- there are entire families of data structures that could be built on this abstraction, not to mention all the generic utilities that may be used while working on those vtable pointers. Appeals to future optimizations / "sufficiently smart compilers" also aren't great, as the extent and efficancy of these optimizations is still somewhat unknown (as they aren't implemented and we also don't yet know all the details of the code we'd want to optimize). Not to mention that even if the overhead would eventually become zero, it's still not great to have the overhead for the medium-term future. |
This comment has been minimized.
This comment has been minimized.
|
The question of whether metadata value in a raw pointer should always be valid is completely independent of whether the metadata type for
|
This comment has been minimized.
This comment has been minimized.
mikeyhew
commented
Nov 26, 2018
Actually, it is. (Sort of.) If a |
This comment has been minimized.
This comment has been minimized.
mikeyhew
commented
Nov 26, 2018
Fair point. I'm with you on that.
OK. Well I'm not sure what it will take to convince you, but I'll try. Like I said earlier in this thread, all those data structures and generic utilities will already be generic on the pointee type. Or they might be specific to one trait object type. This one extra struct having the same type parameter that everything else does won't result in any extra functions being monomorphized in either of those cases, because either the code would be monomorphized anyway (in the case of code that is generic over the pointee type), or it would not be monomorphized even with the type parameter (in the case of code that is specific to one If you're still not convinced, give me an example of code that you would want to write that you think would not be monomorphized if the metadata type didn't have the type parameter, and would be monomorphized if it did. |
This comment has been minimized.
This comment has been minimized.
Even the exampled sketched in the RFC, Specifically, let's look at If the type of the vtable is parametrized by the trait object, one can still extract the arithmetic into monomorphic code, but it's less natural (need to extract size+align and pass them separately) and the polymorphic code contains some more instructions which are then replicated for all trait object types. |
This comment has been minimized.
This comment has been minimized.
mikeyhew
commented
Nov 30, 2018
|
@rkruppe thanks for replying with an example. I'm super busy right now but I'll try to read through it and get back to you next week |
This was referenced Dec 14, 2018
todo
bot
referenced this pull request
Dec 22, 2018
Open
Replace FatPtr with a libcore type when one lands. #103
This comment has been minimized.
This comment has been minimized.
|
Great RFC @SimonSapin. I really like the idea of starting with top most super trait ( |
SimonSapin commentedOct 27, 2018
Add generic APIs that allow manipulating the metadata of fat pointers:
This RFC does not propose a mechanism for defining custom dynamically-sized types, but tries to stay compatible with future proposals that do.
HTML view