-
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
Recursive and unsized enums #1151
Comments
I think this is a really cool idea, if it can work (I don't momentarily see any reason why it couldn't, but I haven't thought very hard). Reminds me a bit of serialization formats where you have a flat stream of bytes with tags to indicate what comes next. |
It could be a little tricky to define how to construct one of these. A similar instance of the same problem exists today: it’s valid to to define a struct whose last field is a non-type-parameter unsized type… struct Dst {
x: [u8],
} …but it’s basically impossible to construct one of these, because there’s no |
@P1start: Afaik @eddyb has a few ideas how to make that work. |
@P1start you would only allow it in an in-place context ( |
I think this is really a cool idea, and is needed for the sake of consistency. |
A useful extension of this is that an unsized enum which is non recursive could also be useful for taking up less space (consider |
I'll just weigh in in favor of this given my internals question where I unexpectedly ran into this same limitation. |
There are discussions elsewhere about doing
so that if Assuming the trait fields RFC, there is a version with nicer borrowing semantics
where We can maybe do the recursive unwrapping these describe using this approach like
except with lifetimes and All this looks less ergonomic of course, but you could maybe fix that with macros somehow. And it's definitely something lower-level developers run into frequently. I donno if this approach might place less nicely with future schemes for type-level numerics though. |
To bump this thread: now that we have enums with well-defined memory layout such as Then it would be possible at least for an unsafe code to construct such enums using same techniques as for any other structs (which such |
@RReverser It helps with the optimization problem, but you still have the issue of the metadata not always being valid, and you also need to read from behind the data pointer to determine the active variant to get e.g. the alignment of the I forget what we did for unions, did we just ban unsized unions? (cc @RalfJung) |
@eddyb Unsized unions are in fact banned, playground. |
Hmm. Isn't that the case for any unsized data structures anyway? E.g. you need to read from behind the data pointer to get alignment of a struct ending with a |
No, the reference/pointer to the struct contains the data pointer and the vtable pointer, there's nothing behind the data pointer that's not in a version of the struct where the |
Ah, got it, makes sense... And, I suppose, same applies to actual variant vtables. On a second thought, given that only one variant of the enum can be active and hold unsized data, couldn't Then other parts of the type system could continue reading layout and vtable information as they normally do. The downside is that now you have to read from behind data pointer when taking a reference... but that's probably still fine, because creating a reference to invalid data is a declared UB anyway, and still better than the alternative. |
No, counterexample enum Foo {
N,
C(Foo)
} Some examples of this type Just to make sure we understand why this isn't possible in general, because the enum above is solvable by just encoding the "length" of the enum, but it only get's more complex from here: Consider this, (a simple length won't suffice, we also need to know what the last element is, but that's just a bit more metadata that we could store) enum Foo {
A,
B,
C(Foo)
} Or even this, (where a length is insufficient) enum Foo {
A,
B(Foo),
C(Foo),
} Here is a case that would truly require reading from the data pointer, no other way around it.
I think this is fine, we should be able to do that. |
Sorry, before reading in depth, I'm confused:
So which one is it? 😀 (as the comment you referenced had only a single suggestion) |
Reading through the counter-examples.
I don't see why not. Such enum enum Foo {
N,
C(Foo)
} would be equivalent to already existing and working struct definition (not exactly equivalent code, just an example for demonstration purposes) pub struct Foo_N;
pub struct Foo_C<V: ?Sized>(Foo<V>);
pub struct Foo<V: ?Sized> {
tag: u8,
variant_data: V,
}
pub fn dyn_foo() -> Box<Foo<dyn std::any::Any>> {
let foo = Foo {
tag: 1,
variant_data: Foo_C(Foo {
tag: 1,
variant_data: Foo_C(Foo {
tag: 0,
variant_data: Foo_N
})
})
};
Box::new(foo)
} Its construction is already well-defined and works on stable Rust. Godbolt link All I'm suggesting is for unsized enums to be encoded in the same way. |
Sorry, I may have misunderstood what you were proposing, but I thought you meant that
After that realization I may have misinterpreted the second part of your comment to mean
They sure do look the same, but they are very different in a single pivotal way. In the enum example you have just 1 type, but in the struct example you have 3 types |
Now that we're getting there... why not? Note that we wouldn't need to generate it for every possible combination, just for the ones actually constructed in code (and only for unsized enums), which are limited, just like in case with structs. I believe there was even another long-term RFC going around about allowing to use separate enum variants as individual types, which seems like would intersect quite nicely with this proposal, as this one also suggests to treat enum variants as separate types. |
So you are trying to blur the distinction between enums and traits? That sounds interesting. I could see this as a way of implementing a sort of sealed traits. One small problem, how do you construct the vtable (if we are going down that route)?. With traits we can never affect the bare trait object's implementation of the trait, so it is able to just call out using the v-table. But with enums, we are effectively writing that very implementation. What I'm trying to get at is this: We don't have |
It might be possible to create a |
It is currently forbidden to have this enum, because it is recursive:
However in theory we could accept it by making
Sequence
unsized.The text was updated successfully, but these errors were encountered: