Cω - Samples

Anonymous Struct Types Tutorial

This tutorial shows in a simple Cω program how to work with anonymous structs (Cω's tuple types).

Sample Files

To run a similar sample, you may use the following project and source files:

These files are located in the \samples\Basics\Tuples subdirectory under the path where you installed Cω, which by default is C:\Program Files\Comega.

In Visual Studio, open the Cω project (.cwproj) file and then press F5 to compile and run the sample code.


The best way to think about anonymous structs is as implicitly defined C# structs with public fields. Thus the anonymous type struct{ int x; int y; } matches with the nominal type Point that has the same fields:

  struct Point{ public int x; public int y; }

The important correspondence between anonymous and named C# structs is that both have no referential identity. That means that the assignment of q.x = 13 in the example below has no effect on variable p

  Point p = new Point(47,11);
  Point q = p;
  q.x = 13;

In XSD terms, an anonymous struct corresponds to a sequence particle. The difference is that Cω does not impose complicated constraints on structs as there are in XSD. In particular anonymous structs can have unlabelled and/or duplicated fields. Hence the type struct{ int; bool b; struct{ Button b; int; }} is perfectly valid in Cω but a matching type cannot be defined using XSD Schema.

Struct members can be accessed by name or by type as we have seen before. In addition, members of an anonymous struct can also be accessed via their relative position.

  struct{ int; bool b; struct{ Button b; int; }} x = new {47, b=false, new {b=null, 11}};
  int a = x[0];
  choice{bool; Button;}+ b = x.b;
  int+ c = x.int::*;

For anonymous structs, name and type-based access are just (convenient) sugar for positional access. In the above example x.b is a shorthand for the generator expression { yield return x[1]; yield return x[2][0]; } that enumerates all fields labeled b and x.int::* is a shorthand for the generator expression {yield return x[0]; yield return x[2][1];} that enumerates all fields of type int.

We can forget labels, nesting, and the exact static type (covariance, just like arrays and streams) when converting one anonymous struct to another. This means that we can assign our example struct x to another y of different type struct{ object; bool; object; int } where we have dropped the labels, forgotten the nesting, and implicitly converted type Button to Control:

  struct{int; bool; Control; int } y = x;

The implicit conversion from x to y constructs a new struct of the appropriate shape by copying and possibly converting all the fields of x when creating y:

  y = new{ x[0], x[1], (Control)x[2][0], x[2][1] };

It is even possible to implicitly convert an anonymous struct to a stream by forgetting labels and the ordering of the fields. For our example, this means that we can convert struct y to a stream z that contains items of type int, bool and Control:

  choice{int;bool;Control;}* z = y;

The implicit conversion from y to z effectively generates the stream by enumerating all fields of y:

  z = {yield return z[0]; yield return z[1]; yield return z[2]; yield return z[3];};

Just like the rules for lifting member access over streams, we have to be a bit careful when defining the result type of converting anonymous structs to streams. It can happen that a struct itself is not null, but that the corresponding stream is empty because all the field in the struct are null.

  struct{Button; Button;} bt = new{null, null};
  Button* bs = bt; // bs is empty

Only if we know that at least one of the fields in not null (for example because it is a value type, or a non-empty stream), we can safely assume that the corresponding stream is non-empty.

There is an implicit conversion from a singleton anonymous struct to its raw underlying field type. This avoids introducing a gratuitous stream type that we would then have to cast away.

string msg = new{Email = "A lonely string"};

Even though struct and stream types are closely related, there is an important difference between struct values and stream generators. Creating a stream by upcasting a struct is eager, which means that all the fields in the struct are evaluated before the stream is generated. On the other hand generators are lazy, and stream elements are created on demand.

  int* xs = new{ 1, 2/0 };
  int* zs = {yield return 1; yield return 2/0;}; 

In this example, the first assignment will throw a DivideByZeroException exception immediately. The second assignment will succeed, and the exception is only thrown when and if the second element of the stream accessed.


The following is a complete Cω program that demonstrates working with anonymous struct types.

using System;
using System.Windows.Forms;

public class Test {

    static void Main() {

      struct{ int; bool b; struct { Button b; int;};} x = new{47, b=false, new{b=null, 11}};
      int a = x[0];
      choice{bool; Button;}+ b = x.b;
      int+ c = x.int::*;

      struct{ int; bool; struct{ Button; int;};} y = x;

      choice{int ; bool ; Button;}* z = y;

      struct{ Button; Button;} bt = new{ null, null };
      Button* bs = bt; // bs is empty

      string msg = new{ Email = "A lonely string" };

      // throws immediately
      int zero = 0;
      try {
        int* xs = new { 1, 2/zero };
      } catch(DivideByZeroException e){
        Console.WriteLine("Division by 0");

      // prints 1 then throws
      int* zs = {yield return 1; yield return 2/zero;};
      try {
        zs.{ Console.WriteLine("it = {0}",it); };
       } catch(DivideByZeroException e){
        Console.WriteLine("Division by 0");




Division by 0
it = 1
Division by 0