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 upEnum variant types #2593
Conversation
varkor
added some commits
Nov 3, 2018
Centril
added
the
T-lang
label
Nov 10, 2018
This comment has been minimized.
This comment has been minimized.
alexreg
commented
Nov 10, 2018
|
Great work, @varkor. I've been looking forward to this for a long time. Just as a side-point, I'd love to follow this up with an RFC for the ideas in https://internals.rust-lang.org/t/pre-rfc-using-existing-structs-and-tuple-structs-as-enum-variants/7529 once this gets implemented in nightly (or perhaps even before). Since you've worked on this, would appreciate your thoughts at some point. |
This comment has been minimized.
This comment has been minimized.
bchallenor
commented
Nov 10, 2018
This is possible in Scala - as in your example, Left and Right are subtypes of Either, and can be referred to independently. Coming from Scala, I miss this feature in Rust, and I am fully in favour of this RFC. |
This comment has been minimized.
This comment has been minimized.
Ah, great, I'll add that in, thanks! |
This comment has been minimized.
This comment has been minimized.
|
I think I'm in favor of the proposed functionality and semantics here. Where I'm stumbling is the nomenclature/terminology/teachability(?); it's not clear to me that "introducing a new kind of type: variant types" is the best description of this. In particular, precisely because this proposal feels so lightweight compared to previous ones, it doesn't really "feel" like what we're doing is adding a whole new type kind the way structural records or anonymous enums would be doing. It sounds like it could be equally well described as doing the "duplicating a variant as a standalone struct" workaround automagically, so those extra structs are just always there (except they get a specific layout guarantee and different conversion syntax that regular structs wouldn't get). Is there some detail I overlooked that makes this clearly not a sugar? I'm guessing this is at least partially ignorance on my part because
makes it sound like "variant types" are an actual thing with their own special properties that no other kinds of types have, and I just have no idea what that would be (since being autogenerated, having a certain layout guarantee and different conversion syntax seem like "surface level" properties that aren't really part of the type system per se). Maybe I just need to see some more examples of how these types behave? |
This comment has been minimized.
This comment has been minimized.
leonardo-m
commented
Nov 10, 2018
•
|
Nice RFC.
Is code like this still allowed, or is the compiler going to tell me that the Sum::B(b) branch of the match is impossible and needs to be removed?
Both options have advantages and disadvantages. |
This comment has been minimized.
This comment has been minimized.
This is a good question — I'll make note of it in the RFC. Although matching on variant types permits irrefutable matches, it must also accept the any other variants with the same type — otherwise it's not backwards compatible with existing code.
It's quite possible there's a better way to explain this. They are essentially as you say, though they act slightly differently from structs (on top of the points you made) in the way they are pattern-matched (as above in this comment) and their discriminant value. I thought it would be clearer to describe them as an entirely new kind of type, but perhaps calling them special kinds of structs would be more intuitive as you say. I'll think about how to reword the relevant sections. |
varkor
added some commits
Nov 10, 2018
varkor
force-pushed the
varkor:enum-variant-types
branch
from
14a6e83
to
2b00420
Nov 10, 2018
This comment has been minimized.
This comment has been minimized.
|
This was previously proposed in #1450. That was postponed because we were unsure about the general story around type fallback (e.g, integer types, default generic types, etc). Enum variants would add another case of this and so we wanted to be certain that the current approach is good and there are no weird interactions. IIRC, there was also some very minor backwards incompatibility. This RFC should address those and issues, and summarise how this RFC is different to #1450. For the sake of completeness, an alternative might be some kind of general refinement type, though I don't think that is a good fit with Rust. I'm still personally very strongly in favour of this feature! The general mood on #1405 was also positive. |
This comment has been minimized.
This comment has been minimized.
I'm not sure that would need to be at odds with variant types, if Rust ends up with refinement types I expect variant types to be refinements of their enum. |
This comment has been minimized.
This comment has been minimized.
|
First, irrespective of what happens with the RFC; I am of two minds and a bit torn about the proposal here.
(Feel free to integrate any points that you found relevant into the text of the RFC)
(Aside, but let's not go too deeply into this: I personally think that refinement / dependent typing is both a good idea, a good fit for Rust's general aim for correctness and type system power for library authors -- and RFC 2000 is sort of dependent types anyways so it's sort of sunk cost wrt. complexity -- the use cases for dependent/refinement types are sort of different than the goal here; With dependent types we wish to express things like
I agree; I think you can think of variant types in the general framework of refinement / dependent types; type FooVar = { x: Foo | x is Foo::Variant(...) }; |
Centril
added
the
A-data-types
label
Nov 10, 2018
This comment has been minimized.
This comment has been minimized.
burdges
commented
Nov 10, 2018
|
We do want formal verification of rust code eventually, and afaik doing that well requires refinement types. I'm not saying rust itself needs refinement types per se, but rust should eventually have a type system plugin/fork/preprocessor for formal verification features, like refinement types. I do like this feature of course, but ideally the syntax here should avoid conflicts with refinement types. |
This comment has been minimized.
This comment has been minimized.
Are there any such conflicts in your view? |
This comment has been minimized.
This comment has been minimized.
|
(Or to elaborate; if there are any conflicts with the RFC as proposed with refinement typing, then stable Rust as is has that conflict since the RFC does not introduce any new syntax...) |
This comment has been minimized.
This comment has been minimized.
ExpHP
commented
Nov 10, 2018
•
So, if I understand correctly, all existing code that uses enums now have coercions all over the place in order to ensure they continue functioning? I'm really not sure this works... let mut x:
x = None;
// At this point the compiler knows the type
// of x is Option<?0>::None.
// But Option<_>::Some cannot be coerced to None
x = Some(1); // type error? |
This comment has been minimized.
This comment has been minimized.
leonardo-m
commented
Nov 10, 2018
•
I think in theory the type system should infer x to be of type But I think we need a formalization of the involved type system rules, to assure soundness, before implementing this proposal... |
This comment has been minimized.
This comment has been minimized.
|
I would rather frame this as follows:
|
This comment has been minimized.
This comment has been minimized.
leonardo-m
commented
Nov 10, 2018
•
While I don't dislike LiquidHaskell-like refinement typing, lately for the future of Rust I prefer a style of verification as in the Why3 language ( http://why3.lri.fr/ , that is also related to the Ada-SPARK verification style). We'll need a pre-RFC for this. |
This comment has been minimized.
This comment has been minimized.
leonardo-m
commented
Nov 11, 2018
|
I hope this syntax is also supported (I suggest to add it to the RFC):
A question regarding the ABI: is the print_a1() function receiving the Sum discriminant too as argument? And in future it could also be supported the more DRY syntax (I think suggested by Centril):
You could also add a new (silly) example to this RFC that shows the purposes of this type system improvement:
With this improvement you can write instead:
Then you can define a list_head_succ() function that returns the head of the result of prepend() without a unwraps or Option result:
For the common case of integer intervals for Rust I sometimes prefer a shorter and simpler syntax like:
|
shepmaster
reviewed
Nov 11, 2018
"Overhead"I'm mostly interested in this RFC from the point-of-view of "enums of lots of standalone other types". The biggest example I have is the AST expressed in fuzzy-pickles, a Rust parser which uses this pattern extensively: pub enum Item {
AttributeContaining(AttributeContaining),
Const(Const),
Enum(Enum),
// ...Unfortunately, I don't see this as being a large win for such a case due to the "forced overhead" of each enum variant still being the same size as all the other variants. It's an understandable decision, just not one that I see as helping as much as it could. This is mentioned in the alternatives section, but I want to make sure the point is reiterated. Multiple variantsI didn't see any mention of if multiple variants would be supported: #[derive(Debug)]
enum Count {
Zero,
One,
Many(usize),
}
fn example(c: Count) {
use Count::*;
match c {
x @ Zero | x @ One => println!("{:?}", x), // what is the type of `x` here?
x => println!("{:?}", x),
}
} It may also be worth explicitly calling out what the type is for those catch-all patterns as well as in cases of match guards.
|
| and `impl Trait for Enum::Variant` are forbidden. This dissuades inclinations to implement | ||
| abstraction using behaviour-switching on enums (for example, by simulating inheritance-based | ||
| subtyping, with the enum type as the parent and each variant as children), rather than using traits | ||
| as is natural in Rust. |
This comment has been minimized.
This comment has been minimized.
shepmaster
Nov 11, 2018
Member
I'm a fan of the proposed style, but it might be worth stating why Rust the language wants to dissuade this pattern.
| - Passing a known variant to a function, matching on it, and use `unreachable!()` arms for the other | ||
| variants. | ||
| - Passing individual fields from the variant to a function. | ||
| - Duplicating a variant as a standalone `struct`. |
This comment has been minimized.
This comment has been minimized.
shepmaster
Nov 11, 2018
•
Member
I disagree that this goal is going to be as widely achieved by this RFC as I would like due to the following point:
the variant types proposed here have identical representations to their enums
That means that if I have an enum with large variants:
enum Thing {
One([u8; 128]),
Two(u8),
}Even the "small" variants (e.g. Thing::Two) are still going to take "a lot" of space.
This comment has been minimized.
This comment has been minimized.
eddyb
Nov 11, 2018
Member
If space is a concern then we could have it so variant types only convert to their enum by-value, so e.g. a &Thing::Two wouldn't be a valid &Thing.
That's weaker than something more akin to refinement typing, but maybe it's enough?
This comment has been minimized.
This comment has been minimized.
Centril
Nov 11, 2018
Contributor
@eddyb I think that's already the case; the RFC doesn't state anywhere, as far as I can tell, that &Thing::Two is a valid &Thing. Also note that the RFC explicitly states that Thing::Two and Thing having the same layout is not a guarantee so we could change the layout to be more space efficient.
This comment has been minimized.
This comment has been minimized.
H2CO3
commented
Jan 2, 2019
This can already be achieved by just matching on the enum once and using the contents of its variants directly. By the way, traits as bounds to type parameters result in static dispatch. Unless you specifically ask for a trait object, there's no dynamic dispatch going on by default.
That is not true anymore because there is Of course traits are different from concrete types – that's kind of their raison d'être. Generic programming is a quite high level of abstraction, indeed – so people not familiar with it might need some time until they get used to programming against abstract interfaces rather than concrete types. I wouldn't say that this means that "traits are harder to deal with" in general, nor that this would warrant conflating certain, very different aspects of the language. |
This comment has been minimized.
This comment has been minimized.
thibaultdelor
commented
Jan 3, 2019
Well then you are not using traits at all, that's a completely different story. You were making a point with abstraction and traits.
Traits have additional restriction, can be monomorphised, require knowledge about trait objects, require to be boxed in some scenario... |
This comment has been minimized.
This comment has been minimized.
I mean that
Yes, it does. But here I meant that you're doing this repeatedly because it's done on every single method, rather than doing it only once: if you pattern match on an enum to destructure it and then access the fields of the variant, you don't need to do the dispatch every time you access a field. Trait objects basically do have to do dynamic dispatch each time, but it's nicely hidden by the compiler.
On the variant types you mean? The RFC proposes to ban that.
Yes, you will. But sometimes you'll have a function which takes a
I dislike downcasting too, but destructuring an enum into its variant is not really different from downcasting, especially over a sealed trait (where you know every possible concrete type). I think the moral argument here is about when you expect consumers of a trait to be (morally) parametric. For enums you generally don't, but for unsealed traits you generally do. For a sealed trait, I think it starts to blur the line---and I think that taking advantage of that blurring in language design is valuable. |
This comment has been minimized.
This comment has been minimized.
H2CO3
commented
Jan 3, 2019
Can you please provide a more specific example? The quote above seems to go against your former argument. I suspect we might be talking about different things… What I meant is, given an enum:
you can implement a method on it, which calls into methods on the variants' associated data:
This isn't any worse, in terms of number of runtime checks, than:
No, I meant on the types of the associated values wrapped in each newtype variant.
Well, then we just disagree here. I think if consumer code wants to behave differently based on concrete types, an enum with public variants is a perfectly fine solution, and such an enum around the different types should be provided. There is no point in consuming a trait if its abstraction capabilities are thrown away because one ends up inspecting the concrete type anyway. That only results in unnecessary complication and an unclear mixture of concepts, which are hard to understand because they belong to neither approach (concrete types or abstract interfaces). To clarify, I'm not saying that switching on an enum is in itself bad; rather, switching on an enum and taking advantage of the heterogeneous types, while pretending to program against a homogeneous interface is bad.
Indeed, it isn't from a technical point of view. However,
…I disagree with this statement too. This is exactly what I'm trying to avoid, as mentioned in the previous point. |
This comment has been minimized.
This comment has been minimized.
H2CO3
commented
Jan 3, 2019
I am well aware that
That is really vague; encapsulation is a form of/tool for abstraction. I also fail to see why hiding a concrete type under an interface doesn't qualify as "abstraction". But I'm not particularly interested in arguing over definitions; I was merely falsifying your (incorrect) claim that a type implementing a non-object-safe trait can't be returned.
Again, these claims are very general and hard to concretize. Yes, generic functions and types with trait bounds can be monomorphized. Is that bad? Yes, traits can and sometimes need to be boxed, just like other types. Is that bad? When you make a trait object, it becomes a concrete type and can for all purposes be treated as one. Also based on this, I'm not sure about the "additional restrictions". You're really making an apples to oranges comparison here. Traits, generics, and trait objects all have their specific purpose, and they are used differently from (concrete, unrelated) types. I don't really see that as a problem. They are distinct language features solving different problems, which is why they all exist and are all useful in different ways. |
This comment has been minimized.
This comment has been minimized.
thibaultdelor
commented
Jan 3, 2019
•
Are you actually suggesting that it rarely occurs?!? Doing something like the following is pretty common IMO : if something {
return this;
} else {
return that;
}(where
Which problem?!? Which proposal?!?
You are arguing with a straw-man... A non object-safe trait can't be return as such. If you know the concrete type then you can but it's not much different conceptually than returning the concrete type and it's not my point. When talking about abstraction, the case where you abstract over a single type is not really the most relevant one.
Who said it was? I said harder to deal with, because it has technical implications and limitations and forces you to think (like boxing) about concepts that you don't have to with concrete types.
You can't always do that, that was my point. I am going to stop replying, the discussion isn't constructive. |
This comment has been minimized.
This comment has been minimized.
H2CO3
commented
Jan 3, 2019
No need to yell. I'm not suggesting that this "rarely occurs", it was just unclear what you precisely meant by "knowing the concrete type". Your example is legitimate. However, it still does need some sort of runtime check. If the returned concrete type depends on a parameter that is only known at runtime, there is no way a runtime check could be avoided.
Agreed. |
Centril
assigned
nrc
Jan 3, 2019
varkor
force-pushed the
varkor:enum-variant-types
branch
from
03d2402
to
9e34d30
Jan 11, 2019
This was referenced Jan 15, 2019
Centril
added a commit
to Centril/rust
that referenced
this pull request
Jan 17, 2019
Centril
added a commit
to Centril/rust
that referenced
this pull request
Jan 17, 2019
Centril
added a commit
to Centril/rust
that referenced
this pull request
Jan 17, 2019
Centril
added a commit
to Centril/rust
that referenced
this pull request
Jan 18, 2019
Centril
added a commit
to Centril/rust
that referenced
this pull request
Jan 18, 2019
Centril
added a commit
to Centril/rust
that referenced
this pull request
Jan 19, 2019
This comment has been minimized.
This comment has been minimized.
CAD97
commented
Feb 6, 2019
•
|
ping from not-triage-but-this-was-mentioned-on-internals: what's the status of this RFC? It seems that rust-lang/rust#57644 is a future-compat lint for syntax space for this (or an equivalent) feature; what's between this and proposal-merge? |
This comment has been minimized.
This comment has been minimized.
ExpHP
commented
Feb 6, 2019
•
|
Okay, let's take a look at the new stuff about type inference.
I suspect this can't work for the same reason that we get type annotations needed all the time when trying to call methods; the type checker works strictly in order. It wants to be able to resolve the type of Without that restriction, we get the following madness: #[derive(Debug)]
struct Struct {
field: i8,
}
#[derive(Debug)]
enum Enum {
A { field: Struct },
B { field: Struct },
}
impl std::ops::Deref for Enum {
type Target = Struct;
fn deref(&self) -> &Struct {
match self {
Enum::A { field } => field,
Enum::B { field } => field,
}
}
}
fn main() {
let mut a = Enum::A { field: Struct { field: 0 } };
println!("`{:?}`", a.field);
// Commenting out the following line changes the output of the
// above println from: `0`
// to: `Struct { field: 0 }`
a = Enum::B { field: Struct { field: 0 } };
}(by the way, because the above snippet does currently compile and print |
This comment has been minimized.
This comment has been minimized.
(Edited: see @alercah's comment below.) // Commenting out the following line changes the output of the
// above println from: `0`
// to: `Struct { field: 0 }`
|
This comment has been minimized.
This comment has been minimized.
|
That doesn't sound right to me; types can be inferred due to unification which may well occur several statements later: fn main() {
let x = vec![1, 2, 3].into_iter().collect();
let y = &x;
let _z : &Vec<u64> = y;
} |
This comment has been minimized.
This comment has been minimized.
golddranks
commented
Mar 2, 2019
•
|
@alercah I think @ExpHP meant cases like this, since they are talking about "trying to call methods":
|
This comment has been minimized.
This comment has been minimized.
ExpHP
commented
Mar 3, 2019
•
|
@golddranks is right. It is a common misconception that type inference can use information later in the function body to resolve types earlier in the body. Type checking works almost entirely strictly forwards in a function body. The only thing that type inference actually does is allow the compiler to reason about types generically, using type inference variables to delay filling in the details that it does not yet need to know. @alercah your example goes like this: let x = vec![1, 2, 3].into_iter().collect();
let y = &x;
let _z : &Vec<u64> = y;
If a Here's some more fun examples to help convince you: Example 1
Example 2
Example 3
So auto-deref takes precedence over "inherent" fields of the variant? If so that's quite surprising and should be mentioned in the RFC. |
This comment has been minimized.
This comment has been minimized.
|
Yes, I believe you're correct in general, but unless I'm mistaken, nothing
prevents the type checker from letting `x.0` be typed as a variable until
later.
…On Sun., Mar. 3, 2019, 14:24 Michael Lamparski, ***@***.***> wrote:
@golddranks <https://github.com/golddranks> is right.
It is a common misconception that type inference can use information later
in the function body to resolve types earlier in the body. Type checking
works almost entirely strictly forwards in a function body. The only thing
that type inference actually does is allow the compiler to reason about
types generically, using type inference variables for details that it does
not yet *need* to know.
@alercah <https://github.com/alercah> your example goes like this:
let x = vec![1, 2, 3].into_iter().collect();let y = &x;let _z : &Vec<u64> = y;
1. The temporary vec![1, 2, 3] receives a type of Vec<?int_0>, where
?int_0 is an integer-flavored type inference variable
<https://github.com/rust-lang/rust/blob/c0086b9e8972fef9fd4af24bae20d45021ed06c6/src/librustc/ty/sty.rs#L1257>
2. .into_iter() and .collect() are resolved to IntoIter::into_iter and
Iterator::collect because there exist general enough impls for these
traits to apply to Vec<?int_0>.
3. x ends up with type ?1, with the obligation ?1: FromIterator<?int_0>
.
4. y gets type &?1.
5. _z has type &Vec<u64>. When the compiler unifies the type of the
expression y with the type of _z, it determines that ?int_0 = u64.
If a x.push() is inserted before let _z = then the compiler rejects it,
because no methods are known for ?1. (it has no outermost type
constructor, so inherent methods cannot be sought; and it is not known to
satisfy any traits with a push associated method)
Here's some more fun examples to help convince you:
Example 1
struct Wrapped<T>(T);
impl Wrapped<Vec<u8>> {
fn boo(&self) { println!("Vec<u8>") }
}
trait Boo {
fn boo(&self) { println!("Trait method") }
}
impl<T> Boo for Vec<T> {}
fn main() {
let mut a = Wrapped(vec![]);
a.boo();
a.0.push(());
}
Does this compile? If so, what does it print? If not, what is the error?
Solution
Compiling playground v0.0.1 (/playground)
error[E0308]: mismatched types
--> src/main.rs:17:14
|17 | a.0.push(());
| ^^ expected u8, found ()
|
= note: expected type `u8`
found type `()`
Somebody who thinks type inference can flow information backwards might
think that a will have type Vec<()> and that this should therefore
compile and print "Trait method". Such is not the case.
a initially has type Wrapped<Vec<?0>>. At a.boo(), the compiler needs to
know immediately what method is being called. It searches inherent methods
of Wrapped<Vec<_>> first because these take precedence over trait
methods. There is a single match, <Wrapped<Vec<u8>>>::boo, so it selects
this, and determines that ?0 = u8.
Example 2
struct Wrapped<T>(T);
impl Wrapped<Vec<u8>> {
fn boo(&self) { println!("Vec<u8>") }
}
// !!!!! This was added to example 1impl Wrapped<Vec<()>> {
fn boo(&self) { println!("Vec<())>") }
}
// !!!!!! Trait was removed// (it was already shown to be irrelevant)
fn main() {
let mut a = Wrapped(vec![]);
a.boo();
a.0.push(());
}
Solution
Compiling playground v0.0.1 (/playground)
error[E0034]: multiple applicable items in scope
--> src/main.rs:21:7
|21 | a.boo();
| ^^^ multiple `boo` found
|
note: candidate #1 is defined in an impl for the type `Wrapped<std::vec::Vec<u8>>`
--> src/main.rs:4:5
|
4 | fn boo(&self) { println!("Vec<u8>") }
| ^^^^^^^^^^^^^
note: candidate #2 is defined in an impl for the type `Wrapped<std::vec::Vec<()>>`
--> src/main.rs:9:5
|
9 | fn boo(&self) { println!("Vec<())>") }
| ^^^^^^^^^^^^^
Type checking here begins similarly to problem 1, but when it searches the
inherent methods of Wrapped<Vec<_>>, it finds two candidates and refuses
to continue.
Why does it give up? Well, let's consider a more sinister example. Above,
the signatures just so happen to match, but what if they didn't?
struct Wrapped<T>(T);
impl Wrapped<Vec<u8>> {
fn boo(&self) -> Self { Wrapped(self.0.to_vec()) }
}
impl Wrapped<Vec<()>> {
fn boo(&self) -> (u32, u32) { (2, 3) }
}
fn main() {
let mut a = Wrapped(vec![]);
println!("{:?}", a.boo().0.rotate_left(3));
// a.0.push(()); // (1)
// a.0.push(1u8); // (2)
}
If the compiler did not give up when type-checking a.boo(), then when it
checks the println there are two *completely* different interpretations:
(notice that one of these interpretations even inserts auto-refs and
coercions not present in the other)
// if a is Vec<u8>let tmp: &Wrapped<Vec<u8>> = &a;let tmp: Wrapped<Vec<u8>> = <Wrapped<Vec<u8>>>::boo(&a);let tmp: () = <&mut [u8]>::rotate_left(&mut *tmp.0, 3_usize);println!("{:?}", tmp);
// if a is Vec<()>let tmp: (u32, u32) = <Wrapped<Vec<()>>>::boo(&a);let tmp: u32 = <u32>::rotate_left(tmp.0, 3_u32);println!("{:?}", tmp);
Before reaching either (1) or (2), how would it decide which
interpretation to use for this line? Scenarios like this are likely why the
compiler gives up so willingly.
Example 3
struct Wrapped<T>(T);
trait Boo {
type Assoc;
fn boo(&self) -> Self::Assoc;
}
// !!!!!! The inherent methods from Problem 2// have been replaced with trait implsimpl Boo for Wrapped<Vec<u8>> {
type Assoc = Self;
fn boo(&self) -> Self { Wrapped(self.0.to_vec()) }
}
impl Boo for Wrapped<Vec<()>> {
type Assoc = (u32, u32);
fn boo(&self) -> (u32, u32) { (2, 3) }
}
fn main() {
let mut a = Wrapped(vec![]);
println!("{:?}", a.boo());
// println!("{:?}", a.boo().0.rotate_left(3));
a.0.push(());
}
Does this compile? If so, what does it print? If not, what is the error?
What if we uncomment the second println!?
(note: for now just assume that println!("{:?}", x) expands to code
containing a call to fmt::Debug::fmt(&x))
Solution
Compiling playground v0.0.1 (/playground)
Finished dev [unoptimized + debuginfo] target(s) in 0.58s
Running `target/debug/playground`
(2, 3)
In contrast to the multiple inherent methods in Problem 2, the compiler
allows multiple impls from the same trait to match. Basically:
- a has type Wrapped<Vec<?0>>
- The output of a.boo() can be written generically as <Wrapped<Vec<?0>>
as Boo>::Assoc.
- This is borrowed and fed to Debug::fmt, creating the obligation that <Wrapped<Vec<?0>>
as Boo>::Assoc: Debug. *Trait obligations are checked lazily,* so it's
okay that we still don't know this type!
- a.0.push(()) tells it that ?0 = ().
- At some point later it verifies that <Wrapped<Vec<()>> as Boo>::Assoc
implements Debug.
Of course, if you uncomment the second println, you get
error[E0282]: type annotations needed
--> src/main.rs:25:22
|
25 | println!("{:?}", a.boo().0.rotate_left(3));
| ^^^^^^^ cannot infer type
|
= note: type must be known at this point
for reasons already discussed in the solution to problem 2.
------------------------------
Enum::A still implements Deref, so I would expect this to print 0,
regardless of whether that line was commented out or not.
So deref coercions take precedence over "inherent" fields of the variant?
If so that's quite surprising and should be mentioned in the RFC.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#2593 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AT4HVY-FPPexMZRaRRBBhmtgsbhfGZ8sks5vTCFQgaJpZM4YYFVh>
.
|
This comment has been minimized.
This comment has been minimized.
ExpHP
commented
Mar 3, 2019
•
Even for usage as simple as I've long lost count of how many times I've responded to github issues or posts on URLO to explain my understanding of what's happening behind the scenes in these apparent "bugs" of type inference.... and I am thoroughly convinced at this point that if there was a reasonable and scalable solution, then it'd already be implemented and I wouldn't be here playing the role of the antagonist! |
This comment has been minimized.
This comment has been minimized.
I imagine the right solution, then, is to require explicit annotations for variants and fallback on the general |
varkor commentedNov 10, 2018
•
edited
Enum variants are to be considered types in their own rights. This allows them to be irrefutably matched upon. Where possible, type inference will infer variant types, but as variant types may always be treated as enum types this does not cause any issues with backwards-compatibility.
Rendered
Thanks to @Centril for providing feedback on this RFC!