Join GitHub today
GitHub is home to over 31 million developers working together to host and review code, manage projects, and build software together.
Sign upRFC: Anonymous enum types called joins, as `A | B` #402
Conversation
This comment has been minimized.
This comment has been minimized.
|
Note that this plays very well with #243 by allowing |
reem
referenced this pull request
Oct 16, 2014
Merged
First-class error handling with `?` and `catch` #243
This comment has been minimized.
This comment has been minimized.
netvl
commented
Oct 16, 2014
|
I would very much like to see union types (why do you call them join types BTW?) in Rust! Ceylon language is probably the nicest example in existence of their implementation. |
This comment has been minimized.
This comment has been minimized.
|
While I totally agree this is a useful feature for errors, it's a major change to the type system, and one that introduces a bunch of machine representation issues, especially if Seperate to that worry, I'd recommend taking a look at OCaml's "polymorphic variants" for inspiration, which is a neat idea similar to this. |
This comment has been minimized.
This comment has been minimized.
|
@Ericson2314 By "coerces to" I meant that the compiler will implicitly insert the translation, not that they have the same representation. Notably, it is not safe to transmute from @netvl the above is related to why I did not call this |
This comment has been minimized.
This comment has been minimized.
|
@netvl Wow, Ceylon's union type is pretty cool! It is used in ways I wouldn't anticipate this being used in Rust though, i.e. I wouldn't necessarily expect |
This comment has been minimized.
This comment has been minimized.
|
"By "coerces to" I meant that the compiler will implicitly insert the translation". Ok, that's a step in the right direction IMO. If Edit: Making |
This comment has been minimized.
This comment has been minimized.
netvl
commented
Oct 16, 2014
|
@reem, well, "union types" is an established term for such kind of types, AFAIK. It is a bikeshed, however, I agree, I just wanted to clarify it :) Union types in Ceylon (well, in just about any language on JVM, and not only union types - all types) rely much on subtyping. In Rust subtyping is almost nonexistent, and introducing it not only makes the type system much more complicated, there would also be no natural way to create subtypes in absence of inheritance or something like it. So it makes sense to treat union types just as anonymous enums, just as like you suggest. After all, closures already are generating anonymous structures. I don't really know how the compiler operates, but I think this can be made in a similar way. |
huonw
reviewed
Oct 16, 2014
| In the same vein, multiple occurrences of `A | B`, even in different crates, | ||
| are the same type. | ||
|
|
||
| As a result of this, no trait impls are allowed for join types. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
reem
Oct 16, 2014
Author
Actually I had not thought this through. My main motivation in saying this was that you can't write an impl for a type or trait you didn't define, but with the new rules only one of the types in the union has to be yours. We could use exactly the same rules as with tuples.
huonw
reviewed
Oct 16, 2014
| impl Show for A | B { | ||
| fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||
| match *self { | ||
| ref a @ A => a.fmt(f), |
This comment has been minimized.
This comment has been minimized.
huonw
Oct 16, 2014
Member
What about matching on the substructure of a type? e.g.
enum Foo { X(u8), Y(i8) }
match x {
X(1) => { ... }
Y(2) => { ... }
_ => { ... }
}How would this be encoded for u8 | i8?
This comment has been minimized.
This comment has been minimized.
reem
Oct 16, 2014
Author
You would specify the type with a suffix:
match x {
1u => { ... },
2i => { ... },
_ => { ... }
}
huonw
reviewed
Oct 16, 2014
| `as` where it would be inconvenient to otherwise give a type hint. | ||
|
|
||
| ```rust | ||
| let x = A as A | B; |
This comment has been minimized.
This comment has been minimized.
huonw
Oct 16, 2014
Member
This is ambigous with bit-or, e.g. x as u8 | y could be (x as u8) | y or x as (u8 | y).
This comment has been minimized.
This comment has been minimized.
reem
Oct 16, 2014
Author
How is this solved for things like x as A + B being parseable as (x as A) + B or x as (A + B)?
This comment has been minimized.
This comment has been minimized.
huonw
Oct 16, 2014
Member
It's parsed as (x as A) + B. (To be clear, my comments were things that should at least be mentioned in the RFC, i.e. just missing details, not necessarily problems.)
This comment has been minimized.
This comment has been minimized.
|
Is |
This comment has been minimized.
This comment has been minimized.
flying-sheep
commented
Oct 16, 2014
I'd prefer if not. That would make no sense and be just sad |
This comment has been minimized.
This comment has been minimized.
|
The motivation mentions error handling but this problem can also be resolved in a different way through #201. |
This comment has been minimized.
This comment has been minimized.
|
@flying-sheep why does it make no sense? |
This comment has been minimized.
This comment has been minimized.
|
If a flat union |
This comment has been minimized.
This comment has been minimized.
|
@kennytm In the same vein, My reasoning for this is that let t = T as T | T; // ambiguous as to which anonymous variant is instantiated
match t {
T => {} // ambiguous as to how this is matched
} |
reem
added some commits
Oct 16, 2014
This comment has been minimized.
This comment has been minimized.
|
@reem You cannot disallow that because:
Also disallowing fn perform_either<T, A, B>(a: || -> Result<T, A>, b: || -> Result<T, B>) -> Result<T, A | B> { ... }
perform_either(
|| rename_file(),
|| copy_and_delete_original_file(),
);
// Will get `IoError | IoError` here. |
This comment has been minimized.
This comment has been minimized.
|
A previous proposal for this supported |
This comment has been minimized.
This comment has been minimized.
|
I think it's possible to resolve unambiguously without positioning if we just make |
This comment has been minimized.
This comment has been minimized.
|
It's not shorthand for We could also have "do what I mean": (This is true even with generics that can be instantiated to be the same type, since the point-of-coercion just sees the two distinct generic types, it does not care that they could end up both being |
This comment has been minimized.
This comment has been minimized.
|
I can't really think of a situation where I'd care about which variant was chosen out of |
This comment has been minimized.
This comment has been minimized.
arthurprs
commented
Oct 16, 2014
|
I kinda prefer this over #201 |
This comment has been minimized.
This comment has been minimized.
Tobba
commented
Oct 16, 2014
|
There are a lot of uses of union types outside of error handling
should be equal to
You could possibly allow coercing a union type to another union type during match. |
This comment has been minimized.
This comment has been minimized.
|
As before I think we should clearly distinguish between anonymous sum types and union types, at least to avoid confusing ourselves. Both are potentially useful, they are vaguely similar but actually quite different, and the main thing they have in common is that both want the same syntax. Anonymous sum types would be nothing more than special built-in syntax for Union types would be a kind of type-based union which essentially has one variant per type, rather than "positional" variants. This may also allow automatic conversions from (Maybe more later, only had time for this right now.) |
This comment has been minimized.
This comment has been minimized.
|
@reem I'd like to see more implementation details spelled out. For example, what is the in-memory representation and so forth? |
This comment has been minimized.
This comment has been minimized.
|
@nikomatsakis In terms of machine representation, a join I'm not really sure what else there is to work out, but I'm certainly missing some things so any hints or questions would be really appreciated. |
This comment has been minimized.
This comment has been minimized.
|
This is an interesting idea but there are a number of subtleties involved that I'm not sure are fully addressed by the current proposal. Some care should be taken as there are non-obvious issues with an ad-hoc approach that can lead to unsoundness and other problems. It would be useful to understand this proposal in relation to similar ideas elsewhere, like extensible/polymorphic variants in OCaml, extensible exceptions ( I'm also concerned that interaction with generics will lead to serious usability issues without something like row-polymorphism and/or subtyping (like OCaml). I don't know enough about Rust's internals to have a good idea whether either of those would be feasible here but neither are trivial additions in most cases. |
This comment has been minimized.
This comment has been minimized.
Tobba
commented
Oct 17, 2014
|
Let me leave in an argument for T | T becoming T:
|
This comment has been minimized.
This comment has been minimized.
|
I've thought a bit about the duplicate type problem, and I want to articulate my new thoughts: We must support duplicate types in joins, at minimum when instantiated through generics. Let's take an example: fn try_both<A, B>(a: || -> Option<A>, b: || -> Option<B>) -> Option<A | B> {
a().unwrap_or_else(|| b());
}This function must work when instantiated with If we assert that in the case where EDIT: Effectively, if you instantiate the above method with the same type parameter, the type of the above function would be |
This comment has been minimized.
This comment has been minimized.
|
I do not like this idea. I am seeing comments like "I don't care about this case" in response to issues with generics, and that makes me think this isn't well-thought out. What does this actually do that the existing type system cannot already do? Is this going to be a weird wart on Rust's type system? I think any solution like this must be motivated by existing problems with more than syntax. For example, if there were a way that this could lead to collapsed enum representations for repeatedly nested types, that would be a win in my view. But it is not clear to me that this proposal attempts this, which again leads me to feel that it isn't that well thought-out. |
This comment has been minimized.
This comment has been minimized.
|
If you think of Also, if you want |
This comment has been minimized.
This comment has been minimized.
|
@zwarich You've pointed out an important issue. I've given this some more thought and I'm now actually unsure if @pythonesque This is a large and important feature, so I certainly think further consideration is warranted, but I also think that this, in some form, is a feature that we will likely want as it vastly simplifies many common cases. |
This comment has been minimized.
This comment has been minimized.
|
In the triage meeting today we decided to close this RFC and not merge it, as it is lacking some critical details with no single obvious solution. We did agree that this space of ideas has some compelling use cases, so I opened an issue in the RFCs repo to track looking into it further: #409. |
zwarich
closed this
Oct 24, 2014
This comment has been minimized.
This comment has been minimized.
|
As others have noted this seems like a small and obvious feature to improve convenience and ergonomics at first, but it's actually a pretty big one with potentially profound implications for the type system. At least in its most general form. With respect to representation, my feeling is that the correct one would likely be Here are the tricky questions I can think of. Most of them have already been posed (and often answered in a way) in the RFC and comments, but just to gather them in one place. I'm going to use a variadic
In the short term, a pragmatic approach might be to just forbid most of the things, e.g. to disallow type variables (and possibly trait objects as well) from appearing in unions, which would avoid many, but not all, of the difficulties (but also be less useful). In the longer term it's probably best to study the literature, such as linked by @darinmorrison. But all in all I'm not sure that all this machinery would be worthwhile for what is in the end a "constant-factor" improvement in convenience, rather than an increase in expressiveness or abstraction power.
This comment appears to be at odds with my previous sentence. My impression was that the main benefit of this feature would be being able to avoid a bunch of |
This comment has been minimized.
This comment has been minimized.
scialex
commented
Dec 9, 2014
|
I have tried my hand at making a new version of this RFC that tries to address some of the concerns with it. Do you think it's worth submitting a pull request? |
This comment has been minimized.
This comment has been minimized.
|
@scialex I haven't read the entire thing yet, but does it propose a compilation strategy? |
This comment has been minimized.
This comment has been minimized.
scialex
commented
Dec 10, 2014
|
@zwarich not explicitly. I do not think it really needs a huge one. The compiler figures out all the overlaps that could occur given the type information it has, creates an enum with one varient for each containing a type of the given combinations. matches and all impls are just desuggared to use this. We have type mono-morph so it should be guaranteed to work (I think, its been a while since compilers and I haven't taken a very deep look at rustc internals). This does rely on being willing to have the in memory layout of ie Also this could cause perf problems with large numbers of very generic types but that doesn't seem to be the main usecase. IE this would take a lot of time/memory fn stuff<A, B, C, D, E>(a: &A, b: &B, c: &C, d: &D, e: &E) -> &(A|B|C|D|E) { ... }since it would be really returning a 120-variant enum. |
This comment has been minimized.
This comment has been minimized.
flying-sheep
commented
Dec 10, 2014
|
your new RFC is great! i’m desperately missing something like join types in my attempt to create a typesafe document tree (the children of one type can be of one of several other types, e.g. |
This comment has been minimized.
This comment has been minimized.
|
@scialex I like some of the changes in the RFC, but I think the treatment of duplicate types could use a lot of work, since it makes using variants of more than 2 extremely unergonomic - As a result, I don't think that RFC should be used as-is. This has sort of slipped my mind in the past weeks, but I think I may take another stab at significantly clarifying many of the edge cases and important ideas brought up here. |
This comment has been minimized.
This comment has been minimized.
|
Now that we have the // implement FromError for LibError.
enum LibError { ... }
impl FromError<ErrorX> for LibError { ... }
impl FromError<ErrorY> for LibError { ... }
// then just use `try!` as is from now on.
pub fn some_other_operation() -> Result<(), LibError> {
let x = try!(produce_error_x());
let y = try!(produce_error_y());
Ok(())
} |
This comment has been minimized.
This comment has been minimized.
scialex
commented
Dec 10, 2014
|
@flying-sheep: Cool that's actually almost the exact same reason why I wanted to revive it. I am making a file-system. stuff can return @kennytm: Added note about FromError. @reem: Updated the RFC to address that concern, making it easier to match on large unions. I have improved it some and am going to submit it. Submitted pull request #514 |
reem commentedOct 16, 2014
Add support for defining anonymous, enum-like types using
A | B.Rendered