Skip to content

Instantly share code, notes, and snippets.

@tema3210
Last active May 12, 2020 13:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tema3210/564d791e4d8a8c5e7a6a1d40306ad831 to your computer and use it in GitHub Desktop.
Save tema3210/564d791e4d8a8c5e7a6a1d40306ad831 to your computer and use it in GitHub Desktop.
Typeset Rust proposal

Summary

Typeset is a closed set of types, usable for limiting the generic implementation, description of variadic generics and tuples, producing new types. This is necessary to properly support more complex features.

Motivation

We are doing this to extend our typesystem, to allow efficient description of closed sets of types, which can be found in:

  • enums
  • structs
  • sealed trait patterns
  • variadic generics proposals
  • n-ary tuples
  • horizontal specialization*

* By horizontal specialization i mean ability to implement same method for different types multiple times as long as all written implementations\methods fit generic signature. (This can be considered a form of overloading and should have its own RFC.) This potentially allow us to write nicer implementations of complex traits.

Guide-level explanation

Typeset without anotation

Typeset is a closed set of types denoted via syntax of array such as: typeset TS = [type1,type2,type3,...].

These items don't share any semantics with bindings, it's more like type aliases - name of typeset (TS in example) represents couple of distinct types.

They can be both asociated and standalone items. In case of asociated item typeset can be overloaded by trait implementors according to specialization rules.

Typesets can be used to build a new type via syntax like type NewStruct = TS::struct or type NewEnum = TS::enum.

Typesets can be destructured inside of each other: typeset Join = [TS1...,TS2...]. Typeset can provide number of current members by syntax: TS::len //here len is const {integer}.

Types one can specify in impl block can be constrained in declaration of typeset by syntax similar to one from GATs: typeset TS: Trait = [...]. Impotant that typeset doesn't expose any constrains other than ones from declaration even if all specified types share property not mentioned in typeset declaration.

If one have some typeset TS then he\she can constrain generic type to be one from typeset: impl<T> ... where T: TS in long syntax or impl<T: TS> ... in short.

Thus we can naturally write impl for bunch of types:

trait TraitA: TraitB {...}
typeset TS: TraitB = [type1,type2]

impl<T> TraitA for T: TS {...} // here T is also known to implement TraitB

One also is able to be generic over a varying number of types (variadic generics):

struct St<A,B,typeset TS1, typeset TS2>(){
  ...
  typeset enumTs = [TS1...,TS2...];
  match enumTs::len {
    0 => {//some usage},
    1 => {another usage},
    _ => {...},
  }
  ...
}

//usage:
...
let a: St<u8,u8,[type1,type2],[type3]> = ...

Sealed trait pattern used to look like:

mod Private {
  trait M;
}
struct A;
struct B;
impl M for A {};
impl M for B {};
trait SomethingUsingSealed{
  fn work<t: Private::M>();
}

Now it looks like:

struct A;
struct B;
typeset M = [A,B];
trait SomethingUsingTypeset{
  fn work<t: M>();
}

Horizontal specialization example:

 trait Try {
   type Ok;
   typeset Errors;
   fn into_result(self)->Result<Self::Ok,Self::Errors::enum>;
   fn from_ok(v: Self::Ok)->Self;
   fn from_error<T: Self::Errors>(v: T)->Self;
 }
 impl Try for T {
   ...
   typeset Errors = [Err1,Err2];
   fn from_error(v: Err1) -> Self{...}
   fn from_error(v: Err2) -> Self{...}
   //*Overloads* must cover every type from typeset, thus typeset can be elided
   ...
 }

Here Err variant of Result<T,E> is built from typeset. Names of variant of this enum is names of types from typeset.

Typeset with anotation

Typeset with anotation has similar syntax: typeset TS: Bound = [type1: annotation, type2: another_one,...], annotation can be either number or ident.

All anotations in typeset must have same "type" - all of them are idents OR all of them are numbers.

While staying the same in most cases, anotations come in play when constructing a type, so annotation is actually a label of enum\struct member.

typeset TS = [u8: byte, String: str];
TS::enum //produces anonymous enum{byte(u8),str(String)}
TS::struct //produces anonymous struct{byte: u8, str: String}

Anotated typeset can also produce a tuple in case of number anotations. Syntax is similar: TS::tuple;

While typeset without anotation can hold only distinct types, e.g following is not possible: typeset TS = [i8,i8], with anotations however typeset will hold all unique pairs of type and its data, e.g: typeset TS2 = [u8: byte, i64: bytes, u8: another_byte].

Acessing typeset members by anotations is done by dot operator: . .

Variadic examples:

fn e1<typeset Var>(){
  match Var::len {
    1 =>{println!("Provided 1 type argument")},
    _ =>{...}
  }
}
fn fst<typeset Var>(fst: Var::tuple) -> Var.1
  where Var::len >= 1
{
  let (v,...) = fst;
  v
}

While working with typestate it can be required to extract part of it into a separate typeset:

trait t<typeset TS>() where TS::len>=3 {
...
typeset Inner = [[f,s,t,...] @ TS...] -> [f,s,t];
...
}

In the example above the first three types of set was extracted from TS and converted to another one -> [f,s,t]. Not sure if this syntax is efficient, easy readable and doesn't confuse people.

As a programer you supposed to think about typesets like they are abstract closed set of types you can both supply and get.

Teaching this to experienced Rust programer shouldn't take much time, if one is familar with sealed trait pattern; possible pain points - variadics.

Teaching this to new Rust programers could start after type aliases, traits, generics, begin with sealed trait pattern and progressing to show typeset like generalized alias(?).

Reference-level explanation

Drawbacks

This feature adds complexity to both learning and developing Rust, so if we consider that effort don't worse it, it won't be added.

Rationale and alternatives

Acessing typeset members by anotations can be done in different ways:

  • by dot operator: .
  • by indexing: []
  • by using namespaces: ::

I prefer using dot operator in order not to mix up with type construction (TS::enum, etc) and not to mess up with arrays (TS[ident] looks like array).

Prior art

Unresolved questions

  • Should we consider typesets without horizontal specialization and variadic generics?
  • What alternative syntax we could want for aquiring a type constructed from typeset?
  • Are there enough benefits to add this into a language?
  • Do we want to have custom annotation types?
  • Which kinds of types we want to construct from typesets? Do we really want enums and structs and why not to add union? How it could interact with custom anotation types?
  • What is best way to present feature to newcomers?
  • How should we denote fact of presense of anotations in typeset? How to show kind of anotations? And should we at all?
  • Possibly better syntax for type(sub)set extraction?

Future possibilities

  • I don't know, MVP of the feature seems pretty useless so what i originally wanted to write there is inside RFC.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment