[Pre-RFC] enum_exchange ( was: Structural enum types implemented in enum exchange)

This is the latest( third ) version of the RFC

The following is the second version of the proposal.

Summary

Add macros and traits in std library, for minimal support of structural enum.

The new constructs are:

  • Four traits FromVariant, IntoEnum, ExchangeFrom, ExchangeInto.

  • A new proc-macro derive Exchange applicable for enums, generating type convertion impls defined in these four traits.

  • A declarative macro Enum! accessing to predefined Exchangeable enum.

Motivation

Programmers are used to divide a task into its compositional sub-tasks which are more detailing and easier to understand and implement. It makes programs organized as a hierarchy of functions. Some of the functions are dealing with the input directly, then sending the intermediate results to the functions focusing on a higher level. Then the latter ones accept the intermediate outputs as their inputs, and so forth.

This “output as input” data flow leads to one question: what is the type of a function’s output, if it accepts different kinds of intermediate results?

Two obvious answers to this question:

  1. Using enum.

The actual input types are reserved and reflected by the output type.

  1. Using trait object.

The actual input types are erased, and the output type is a public interface.

These two methods have significant differences:

  1. Changes in upstream functions may propagate to downstream if using enum, while it won’t for trait object.

  2. It is easy to do down-casting if using enum, while not the case for trait object.

  3. All the possible actual input types must support the functions in the trait object, potentially leading to a fat interface.

Both methods are reasonable and have use cases which are most suitable for.

Lacking of structural enum type in Rust, it causes some unfavorable results:

  1. Suffering from boilerplate code for enum convertion, either handwritten or macro generated. But such convertions are non-trival to implement, in its general form. Further more, rustaceans are developing such non-trival equivalents in specific domains.

A notable example is “error-chain”. It had reinvented the wheel for some kind of structural enum in the form of ErrorKind.

  1. Tempting programmers to use trait object instead, in the case for which using enum is most suitable for.

The shortcommings are

  • unneccesary boxing and 'static lifetime bounds

  • unneccesary trait methods, aka fat interface

    Programmers will get a mandatory presumption that all types flowing from lower-level functions to high-level ones must have common methods predefined in a trait, which is not always true. It effectively leads to difficulties in defining the trait methods. Once the trait has been defined, programmers will eventualy find some useless methods in the trait. On the other hand, lacking some methods in the trait will ask for down-casting, but

  • non-straigthforward down-casting

Supporting some kind of structural enum type, even in its minimum form, will bring signficant benifits:

  • directly expressing the concept which is easily and well understood, rather than learning various equivalents in domain specific libraries resolving the same problem time and time again. And the library authors could focus on domain specific stuffs, not to reinvent the wheel for infrastructures.

  • aware of the abusing of trait object, and mind changing to get better designs.

  • its ability to reflecting function shapes helps programmers do local lookups rather than recursively tracing into inner functions causing a global search.

    A concrete use case is error-handling. Applying structrual enums in error-handling reslults putting all error types in function signatures, aka throws.

However, introducing a fully-supported structural enum type tends be big changes in the language leading to lots of work. And it is not confident to decide what a structrual enum type ought to be the most ideal for Rust, without full experience. This proposal focuses on a minimum support for structural enum type, without actually introducing a new type concept. Instead, a set of traits and macros are proposed.

Guide-level explanation

A user-defined enum tagged #[derive(Exchange)] is considered as an exchangeable enum.

#[derive(Exchange)]
enum Info {
    Code(i32),
    Text(&'static str),
}

An exchangeable enum can be constructed from one of its variant:

let info: Info = 42.into_enum();
let info = Info::from_variant(42);

An exchangeable enum can be exchanged from/into another exchangeable one, as long as one has all the variant types appear in the other one’s definition.

#[derive(Exchange)]
enum Data {
    Code(i32),
    Text(&'static str),
    Flag(bool),
}

let info: Info = 42.into_enum();
let data: Data = info.exchange_into();

let info = Info::from_variant(42);
let data = Data::exchange_from(info);

Note that all variants must be in the form of “newtype”.

#[derive(Exchange)]
enum Info {
    Text(String),  // ok, it is newtype
    Code(i32,i32), // compile error
}

should cause an error:

1926 | Code(i32,i32),
     | ^^^^^^^^^^^^^ all variants of an exchangeable enum must be newtype

Predefined exchangeable enums

The Enum!( T0, T1, .. ) macro defines an predefined exchangeable enum composed of variant types T0, T1, … etc.

let info = <Enum!(i32,&'static str)>::from_variant(42);

is essentially equvalent to the following code:

let info = __Enum2::from_variant(42);

while __Enum2 is predefined but not exposed to rustaceans:

#[derive(Exchange)]
enum __Enum2 {
    _0(i32),
    _1(&'static str),
}

Two Enum!()s with identical variant type list are identical types.

<Enum!( T0, T1, .. )>::_0 for the first variant in pattern matching, and so forth.

match info {
    <Enum!(i32,&'static str)>::_0(i) => (),
    <Enum!(i32,&'static str)>::_1(s) => (),
}

Predefined enums are also Exchangeable enums, being the destinations of from_variant()/into_enum(), and having exchange_from()/exchange_into() methods. By now, we call these four methods as “enum exchange methods”.

Predefined enums are superior as notations, comparing to user-defined ones. From now on, we will prefer using predefined enums in examples.

Convertion rules

The following 2 rules are considered as the minimum support of structural enum:

  1. variant <=> exchangeable enum

from_variant()/into_enum() serve for it.

  1. exchangeable enum => exchangeable enum composed of equal or more variants

exchange_from()/exchange_into() serve for it.

The following rules are considered perculiar to exchangeable enums, which distinguish them from “union types” in Typed Racket.

  1. Uniqueness of variant type requirement in monomorphisation

An exchangeable enum composed of duplicated variant types is a valid type. However, a compiler error will occur on its instantiation via enum exchange methods:

9 | let a = <Enum!(i32,i32)>::_0( 3722 );
  |         ^^^^^^^^^^^^^^^^ variants of an exchangeable enum must be unique.
  1. No automatic flattening

For example, Enum!(A,Enum!(B,C)) can not be converted to Enum!(A,B,C) via enum exchange methods. Further more, making these two equal types will need changes in type systems, which is not possible for a proc-macro derive.

Reference-level explanation

The definition of enum exchange traits are

pub trait FromVariant<Variant,Index> {
    fn from_variant( variant: Variant ) -> Self;
}

pub trait IntoEnum<Enum,Index> {
    fn into_enum( self ) -> Enum;
}

pub trait ExchangeFrom<Src,Indices> {
    fn exchange_from( src: Src ) -> Self;
}

pub trait ExchangeInto<Dest,Indices> {
    fn exchange_into( self ) -> Dest;
}

Notice that all traits have a phantom index type Index or Indices in their generics to hold positional information to help compiler accomplishing type inference.

Distinguish from std::convert

Since standard From/Into does not have such index type, it is not feasible to implement enum exchange methods in From/Into. On the other hand, trying to implement in From will cause compile error because we need to impl multiple From<Variant> but the generic Variant type in different impls could be of the same actual type, resulting in overlap impls.

Distinguish between Index and Indices

Consider the convertion from Enum!(A,B) to Enum!(A,B,C,Enum!(A,B)).

Since flattening is not supported by enum exchange, there are two possible ways for this convertion:

  1. making the former as the forth variant of the latter.

  2. matching the former to get an A or B, then making it as the first or second variant of the latter.

This is the root cause we distinguish between FromVariant and ExchangeFrom.

Interaction with other feature

The library implementation of enum exchange, aka EnumX, is a proof that the proposed construct will left Rust syntax and its type systems as untouched.

Enum variant types, if available, will relax the “newtype limit” in user-defined exchangeable enum.

Drawbacks

Increasing the size of std library.

Rationale and alternatives

Two alternatives to supporting structural enum:

  1. Making it a native support type, aka “sum type”.

As mentioned previously, it might be too big changes for Rust community to agree on, and may bring lots of work.

  1. Implementing it as a third party library.

While the using of structural enums was more adopted as it ought to be, a third library mimicing such language features will suffer from leaking implementation details or unfriendly compile errors, both of which confusing the programmers.

Comparson to using trait object

Issues such as fat interface, down-casting and unnecesseary 'static lifetime bounds, have been dicussed for using trait object, in motivation chapter.

To solve fat interface problem, traits should be splitted into categories and linked using “trait Concrete: Abstract” syntax.

To address difficulties in down-casting, the trait as the root of hierarchy should provide methods for explicit convertion and force to implement.

The unnecessary 'static lifetime bounds seems infeasible to resolve for trait object because the actual types have been erased.

These three are all non-issues for structural enums.

While using trait objects will make programmers to design the hierarchy of traits, using enums does not mean to force programmers not to implement necessary traits. It just provides a different but also fine grain control. These trait bounds could be applied to certain variants rather than the enum.

It is a mistake to force rustaceans to design a hierarchy of traits, just to reflect the shape of hierarchy of functions. Using structural enums is a natural solution in such cases, without all these pitalls mentioned above.

Prior art

Concepts similar with structural enum exist in other language. One example is union types in Typed Racket. However, it supports more powerful type inferences such as Enum!(A) => A, Enum!(A,A) => Enum!(A), Enum!(A,Enum!(B,C)) => Enum!(A,B,C). All these seems to bring significant changes to Rust’s interals.

Unresolved questions

A particular use case, in which structural enums are collecting variants implementing the same trait and act if the enums themselves had implement it, may require more elegant solution, than macro-generated impls.

Future possibilities

It seems to be a compatible, fine grained, and practical way to introducing separate derives. Other proc-macro derives may be proposed. For example, to support automatic flattening or variant type duplication, a #[derive(Sum)] could be introduced in the future.

———————————————

The following is the origin proposal.

———————————————

The features suggested here have been implemented as a Rust crate, enumx, and ready for use with stable Rust.

It is a separation from a previous thread, Checked exception simulation in Rust, but focusing on the topic of structural enum types.

Summary

Add types, traits, macros for providing structural enum types in Rust.

The new constructs are:

  • An Enum!(T0,T1..) macro to define a structrual enum type composed of variant types T0,T1… etc.

  • A set of predefined enum types with the names Enum0, Enum1,… etc to notate the structural enums of different amount of variants.

  • An Exchange trait for deriving user-defined sructural enum types.

Motivation

By definition, structural enum types can be converted to each other if their variant types are compatible. If provided, they will help programmers to directly express a sum type concept and keep them from hand-writing or macro-generating boilerplate code for convertion impls.

One of an important use cases, putting error types in function signatures, aka throws, will be discussed in its own thread.

Overview

Definition of structrual enum type

A structural enum type of n variant types is notated as Enum!( T0, T1, .., T(n-1) ), the actual type name of which is Enum$n<T0,T1,..,T(n-1)>, while $n means the digit number n appended to Enum as a complete identity. Its variants are named as _0, _1, … _(n-1). The first variant can be pattern matched as <Enum!( T0, T1, .., T(n-1) )>::_0 or Enum$n::_0, and so forth.

For example, Enum!( i32, &'static str ) is a structural enum type of 2 variants, which is essentially Enum2<i32,&'static str>. Two of its variants are Enum2::_0 and Enum2::_1.

Enum0 is also defined as a never type.

Construct a structrual enum

It is obvious that any variant can be converted to the structral enum type containing it.

  • Type annotation
let enum2: Enum!(i32,&'static str) = 42.into_enum();
  • Type inference
let enum2 = <Enum!(i32,&'static str)>::from_variant( 42 );

Convertion between structrual enums

A structural enum could be converted to another one, if all its variants are in the latter.

For example, Enum!(i32,&'static str) could be converted to Enum!(&'static str,i32), Enum!(i32,bool,&'static str), but not Enum!(u32,&'static str) nor Enum!(i32,String).

  • Type annotation
let enum2: Enum!(i32,&static str) = 42.into_enum();
let enum2: Enum!(&'static str,i32) = enum2.into_enumx();
let enum3: Enum!(i32,bool,&'static str) = enum2.into_enumx();
  • Type inference
let enum2 = <Enum!(i32,&static str)>::from_variant( 42 );
let enum2 = <Enum!(&'static str,i32)::from_enumx( enum2 );
let enum3 = <Enum!(i32,bool,&'static str)::from_enumx( enum2 );

Define a user-defined structrual enum

A user-defined structrual enum should be tagged with #[derive(Exchange)], and is able to be exchanged with others of compatible variant types.

To distinguish a user-defined structrual enum from Enum!(), , by now, we call the former “exchangeable enum”, and the latter “ad-hoc enum”.

#[derive(Exchange)]
enum Info {
    Code(i32),
    Text(&'static str),
}

#[derive(Exchange)]
enum Data {
    Code(i32),
    Text(&'static str),
    Flag(bool),
}

#[derive(Exchange)]
enum Datum {
    Code(u32),
    Text(String),
    Flag(bool),
}

Construct a exchangeable enum

It is obvious that any variant can be converted to the exchangeable enum containing it.

  • Type annotation
let info: Info = 0xdeadbeef.into_enum();
  • Type inference
let info = Info::from_variant( 0xdeadbeef );

Convertion between exchangeable enums

A exchangeable enum could be converted to another one, if all its variants are in the latter.

For example, Info could be converted to Data, but not Datum.

  • Type annotation
let info: Info = 0xdeadbeaf.into_enum();
let data: Data = info.exchange_into();
  • Type inference
let info = Info::from_variant( 0xdeadbeaf );
let data = Data::exchange_from( info );

Detailed Design

Predefined ad-hoc enum types

A set of predefined enum types with the names Enum0, Enum1,… are defined in the form of:

pub enum Enum0 {}
pub enum Enum1<T0> { _0(T0) }
pub enum Enum2<T0,T1> { _0(T0), _1(T1) }
/* omitted */

Translating Enum!() to the actual ad-hoc enum type

The purpose for Enum!() is to keep programmers from manually counting variants to pick the right number as a suffix to Enum.

It could be implemented as a declarative macro, counting its arguments to find the corresponding predefined enum type:

macro_rules! Enum {
    ( $t0:ty ) => { Enum1<$t0> };
    ( $t0:ty, $t1:ty ) => { Enum2<$t0,$t1> };
    ( $t0:ty, $t1:ty, $t2:ty ) => { Enum3<$t0,$t1,$t2> };
    /* omitted */
}

Type convertion implementations NOT to use From

This is the most interesting part in implementation. In general, we cannot generate such implementations by implementing std::convert::From trait, due to the potential overlapping of impls, which is not allowed in Rust. Simple demonstration of convertion between two Enum2s:

impl<T0,T1,U0,U1> From<Enum2<T0,T1>> for Enum2<U0,U1> { /* omitted */ }

If T0 equals to U0 and T1 equals to U1, we are doing impl From<T> for T now, which will result in compiler error. Further more, we are going to meet this again and again no matter what tricks we play as long as sticking in impl From for generic types.

A sound method is developing our own traits rather than using standard From trait to do the convertion. These traits should be able to encode structural information to map variant(s) to its(their) proper positions.

What we need to do is to check the equality of types in trait bounds, which is not directly supported in Rust. We should do some transformations to make rustc happy.

The phantom index

We will introduce a ZST named Nil, a set of index types which are ZSTs and named as V0,V1,…, V(n-1) to reflect the position of the type list T0,T1,..,T(n-1), and a recursive struct pub struct LR<L,R>( pub L, pub R );, to transform the positions as a “cons of car/cdr”: LR(V0, LR(V1, .. LR(V(n-1),Nil))..).

Construct a structrual enum

We will introduce two traits: FromVariant<Variant,Index> and IntoEnum<Enum,Index>:

pub trait FromVariant<Variant,Index> {
    fn from_variant( variant: Variant ) -> Self;
}

pub trait IntoEnum<Enum,Index> {
    fn into_enum( self ) -> Enum;
}

And a blanket impl:

impl<Enum,Variant,Index> IntoEnum<Enum,Index> for Variant
    where Enum: FromVariant<Variant,Index>
{
    fn into_enum( self ) -> Enum { FromVariant::<Variant,Index>::from_variant( self )}
}

Mapping a variant to the enum can be done in a declarative macro.

Convertion between structrual enums

We will introduce two traits: IntoEnumX<Dest,Indices> and FromEnumX<Src,Indices>:

pub trait IntoEnumX<Dest,Indices> {
    fn into_enumx( self ) -> Dest;
}

pub trait FromEnumX<Src,Indices> {
    fn from_enumx( src: Src ) -> Self;
}

And a blanket impl:

impl<Src,Dest,Indices> FromEnumX<Src,Indices> for Dest
    where Src: IntoEnumX<Dest,Indices>
{
    fn from_enumx( src: Src ) -> Self { src.into_enumx() }
}

For demonstrating the key idea, the following code snippet is quoted from EnumX:

impl<L,R,T0,$($descent_generics),+,$($dest_generics),+> IntoEnumX<$dest_enum<$($dest_generics),+>,LR<L,R>> for $src_enum<T0,$($descent_generics),+>
    where $dest_enum<$($dest_generics),+>       : FromVariant<T0,L>
        , $descent_enum<$($descent_generics),+> : IntoEnumX<$dest_enum<$($dest_generics),+>,R>

T0 is the first variant type of the source enum.

What we are doing is essentially check if the dest enum can be constructed from T0, and if not, try converting the rest variant types in source enum into the dest. The L is the first index and the R is the rest indices. Notice: the two where clauses are not possible to be true both with non-Nil at the same time.

Define a user-defined structrual enum

We will introduce an Exchange trait to reflect the prototype of an exchangeable enum, that is, an ad-hoc enum of the same variant types but renaming the variant names as _0,_1,… accordingly.

pub trait Exchange {
    type EnumX;
}

For example, the Exchange::EnumX of the Info defined in previous section is Enum2<i32,&'static str>.

Construct an exchangeable enum

It is obvious for #[derive(Exchange)] to generate impl Exchange, impl From EnumX, impl Into EnumX. All that we need to do is naming/renaming.

To impl FromVariant<Variant,Index> for ExchangeableEnum, first convert the variant to Exchange::EnumX, then convert it Into ExchangeableEnum.

To impl IntoEnumX<AdhocEnum,Indices> for ExchangeableEnum, first convert the ExchangeableEnum Into Exchange::EnumX, then convert it IntoEnumX AdhocEnum.

Convertion between exchangeable enums

We will introduce two traits: ExchangeFrom<Src,Indices> and ExchangeInto<Dest,Indices>:

pub trait ExchangeFrom<Src,Indices> {
    fn exchange_from( src: Src ) -> Self;
}

pub trait ExchangeInto<Dest,Indices> {
    fn exchange_into( self ) -> Dest;
}

We convert the source enum to its ad-hoc enum, then to the dest’s ad-hoc enum, then to the dest enum.

impl<Src,SrcAdhoc,Dest,DestAdhoc,Indices> ExchangeFrom<Src,Indices> for Dest
    where Dest      : Exchange<EnumX=DestAdhoc> + From<DestAdhoc>
        , Src       : Exchange<EnumX=SrcAdhoc>  + Into<SrcAdhoc>
        , DestAdhoc : FromEnumX<SrcAdhoc,Indices>
{
    fn exchange_from( src: Src ) -> Self {
        Dest::from( FromEnumX::<SrcAdhoc,Indices>::from_enumx( src.into() ))
    }
}

And a blanket trait implementation.

impl<Src,Dest,Indices> ExchangeInto<Dest,Indices> for Src
    where Dest: ExchangeFrom<Src,Indices>
{
    fn exchange_into( self ) -> Dest {
        Dest::exchange_from( self )
    }
}

Drawbacks

  1. The various kind of From/Into-alike traits may confuse users, and losing the chance in the situation that accepts standard From/Into only.

  2. Predefined ad-hoc enums are a subset of possible ad-hoc enums. What if the programmer want an ad-hoc enum composed of 65535 variants?

Rationale and alternatives

  1. The EnumX v0.2 is inspired by frunk_core::coproduct, which provides another ad-hoc enum implementation. It uses the recursive enum as a public interface, getting rid of the variants count limit, at the cost of not supporting native pattern matching syntax on enums. And the enum in recursive form may occupy more spaces than the flattern one proposed by this article.

  2. The EnumX v0.1 generates the convertion in standard From/Into trait, at the cost of not supporting generics, and heavy hacks in proc-macro attribute #[enumx].

Prior art

frunk_core::coproduct, and EnumX v0.1 just mentioned.

Unresolved questions

Exchangeable enum is missing FromEnumX in the derive. As a result, there is no way to convert an ad-hoc enum to an exchangeable one automatically.

Future possibilities

Make exchangeable enum a first-class structrual enum type.

3 Likes

How does it deal with multiple variants having the same type?

What is the purpose of the exchange_from and exchange_into methods? Why not just From/Into?

You can define an Enum!(T,T), but a compile error will stop you from constructing it using from_variant()/into_enum(), or from_enumx()/into_enumx().

The reason why it cannot be implemented in the standard From trait has already been stated in the post, I will put it here again:

For generic enums, it is infeasible to write trait bounds in impl From to make the compiler feeling safe for no overlapping impls.

You could have a try to write such implements, and will probably find the cause yourself.

And I do not think we have a chance to convert exchangeable enums in standard From trait, since they are isomorphic with ad-hoc enums.

How are you planning to implement the compiler error without language support? And how does it work in the presence of generics, when the compiler can’t know before generic instantiation time that two type parameters won’t be bound to the same concrete type?

For something that is supposed to “simplify” matters, this sounds like it’s adding more and more complexity.

1 Like

I am not planning to implement the compiler error without language support, in fact, I have done it in enumx. Code for demonstration:,

|9527 |     let e2 = Enum2::<i32,i32>::from_variant( 3722 );
|               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot infer type for `Index`

However, if the language would support structural enum, a better error message can be generated, something like:

|9527 |     let e2 = Enum2::<i32,i32>::from_variant( 3722 );
|               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ variant types of a structural enum must be unique.

I have descripted the mechanism breifly in the "Detailed design" chapter. You could look into the project source code for more details. Sorry for not having done a polished document that explained every important tricks in detail.

The concept of structural enum type is very easy to understand and learn, even for beginners. However, current implementation is a bit tricky and complex( but sound ). Without supporting this useful feature, the programmers will be forced to implement their own equivalents if they found themselves in the situation requiring such constructs.

1 Like

I’ve got an idea to make exchangeable enum a first-class structural enum type, which will get rid of ExchangeFrom/ExchangeInto( and “_ex”/“_named” suffix in CeX API ). Version 0.3 is probably on its way. :thinking:

To be clear, I have read the whole document text. It is very long, so I might have missed the exact mechanism by which problems around T + T are resolved. Sorry for the reiteration if that is the case.

However, I searched for "same" and "identical" before asking my question (for the lack of a better idea on my part), and there hasn't been any results. I'd like to get a basic, plaintext explanation to a central issue that seems to have spun off much controversy around previous proposals for anonymous sum and union types, without having to read all details of the code.

I'm sorry but I beg to differ. I don't doubt that the concept is easy to learn; however, from what I can tell, the implementation is both very complicated and requires language support for certain parts at the moment. Eliminating at least one of these would be desirable before considering such an addition. As is, it seems very hard to justify the increased complexity.

Relatedly, I also disagree that anonymous variant types are "required". There are many features that could be added to the language because they are useful in some situations. However, neither "it's useful" nor "if we don't have this, programmers will need to write more code" in themselves are sufficiently strong arguments in favor of a language or even a standard library change, especially when said change is of an additive nature.

There are tradeoffs, and changing the language or std has a learning, implementation, and maintenance complexity cost. The type system is a cornerstone of the language, therefore modifying it will interact with many other parts thereof. This increases the risk of (soundness and other) bugs in the implementation, and (even if it is, or is perceived as, easy to learn) incurs an additional requirement on the learners of an already complex language.

In particular, I feel that you are attributing such a high cost to the act of creating a custom error type that is not justified in the face of such a significant change. Creating new types is an everyday activity in the life of a Rust programmer, and it should not be considered a burden that we should aim to get rid of, especially not by means of extending the core language or the standard library.

5 Likes

I still don’t understand the point of this. Do you have at least three real world examples of where such a proposal would be useful and explicitly what code would be used to replace them? I often find these proposals help in one specific circumstance that doesn’t happen often enough to warrant massive changes to the language or a whole new concept to understand, or the boiler plate required is of O(1) and thus not an issue.

4 Likes

Thanks for your thoroughly response making me aware the main issue: justification for the requirement of introducing a new type.

I am planning to reduce the origin proposal to a minimum a few days later, and make it more reasonable, more readable and more likely for us to agree on.

2 Likes

In fact, structural enums are infrastructures not to serve for domain-specifc problem. I’ll explain it in the second version. Coming soon.

The second version has been prepened to the origin one. You can find it here

Sorry, I don't get what this exactly means. Does this mean that you want to look at instantiations of generic types and function post-monomorphization, and perform another round of type checking afterwards, in order to enforce uniqueness? E.g.:

fn generic<T, U>() -> Enum!(T, U) {
    …
}

generic::<i32, String>(); // compiles
generic::<i32, i32>(); // error

Thanks. Perhaps I should rewritten it in “Enum exchange methods require the uniqueness of variant types”.

As promised in the proposal, Rust’s type systems will be untouched, so Enum!(A,A) is a valid type, and generic::<A,A>() is valid too. Compile error occurs while you are actually using one of from_variant(), into_enum(), exchange_from(), exchange_into().

Summary: Enum!(A,A) is a valid type, but is useless because you cannot use its enum exchange methods.

This is the latest( third ) version of the RFC

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.