Z# (Zee-sharp)

A new .NET language

Structures

Structures are data records of fields.

A name and a set of fields is required in order to define a structure. Structures (that do not derive) cannot be empty - have no fields.

MyStruct
    field1: U8
    field2: Str

Note that the name of the structure has to start with a capital letter, because it is a new type. Also note the absence of any specific keywords like ‘struct’.

Here is how to create an instance of a structure and assign its fields with values.

MyStruct
    field1: U8
    field2: Str

s : MyStruct =
    field1 = 42
    field2 = "42"

// alternative
s : MyStruct = { field1 = 42, field2 = "42" }

// MyStruct : MyBase
s : MyBase = MyStruct
    field1 = 42
    field2 = "42"

A structure can have default values. If no value is explicitly specified each type is initialized with its #default value:

MyStruct
    field1: U8 = 42
    field2: U8 = 42

s : MyStruct =
    field2 = 101        // overwrites any value

// s.field1 = 42
// s.field2 = 101
MyStruct
    field1: U8
    field2: Str

fn: (p: MyStruct): U8
    ...

// syntax?
fn({field1 = 42, field2 = "42"})
fn(MyStruct 
    field1 = 42
    field2 = "42")

Composition

New structure types can be made from other structure types by the following methods:

…or a combination of these.

Here is an example of inheritance:

MyStruct
    field1: U8

MyDerived: MyStruct
    field2: Str

MyDerived has two fields: field1 and field2. An instance of the MyDerived structure can be treated as an instance of the MyStruct structure.

Inheritance makes the structure bigger.

Multiple inheritance is not supported at this moment.

TBD: default to ‘closed’ structured and explicitly ‘opening’ them up for inheritance. Syntax is iffy.

// Any looks weird
MyStruct: Open<>
MyStruct: Open<Any>
    ...

OpenDerived: Open<MyStruct>
    ...

// closed even if MyStruct is open
ClosedDerived: MyStruct
    ...

Here is an example of containment:

MyStruct
    field1: U8

MyContainer
    cnt: MyStruct
    field2: Str

The MyContainer structure still has the field1 from MyStruct but it has to be reached by the cnt field. Here is how the structure would be initialized:

s : MyContainer =
    cnt.field1 = 42
    field2 = "OK"

Unions in structures are not supported at this moment.


Bit Fields

This needs to be revised for .NET interop.

A structure can contain bit fields using the Bit<T> type.

So the following example will only take up 1 byte:

MyStruct
    field1: Bit<2>      // bit 0-1
    field2: Bit<3>      // bit 2-4
    field3: Bit<3>      // bit 5-7

Bit fields can be combined with other types.

MyStruct
    field1: Bit<2>
    field2: Bit<4>
    field3: Bit<4>
    other: U8
    field4: Bit<4>
    field5: Bit<4>

The resulting structure has 3 bytes worth of bit fields and one byte for the other field.

What will be the #offset for a bit field? (byte-offset/bit-offset)

Should this be a union type?


Nested Declaration

Structures can be declared in a nested fashion:

Struct
    nested: NestedStruct
        fld1: U8
        fld2: Str
    name: Str

However the NestedStruct type is not available outside the structure it was declared in.

TBD: Can the nested type be anonymous?

Struct
    nested
        fld1: U8
        fld2: Str
    name: Str

Nested inside a function? (yes)

someFn: (p: U8)
    LocalStruct     // only visible inside this function
        fld1: U8
        fld2: Str
    
    s : LocalStruct =    // use it
        fld1 = 42
        fld2 = "42"

    ...

Field Indexing

Ways in which to access fields for reading or writing.

MyStruct
    fld1: U8
    fld2: Str

s = Mut<MyStruct> =      // mutable
    fld1 = 42
    fld2 = "42"

f1 := s.fld1             // dot notation
f2 := s[MyStruct#fld2]   // indexed by name at compile time (metadata)

f1 := s["fld1"]          // indexed by name at run time
// Error or Opt, if field with 'name' does not exist? What if the field type is incompatible with the variable type? Any?
name = ...
f2 := s[name]          // indexed by name at runtime time/reflection (variable)
s[name] = 42
// Error if field with 'name' does not exist? What if the field type is incompatible with the value type? Auto-Convert?

Syntax should be the same as with the Dynamic Type.


Structure Layout

The fields of a structure are layed out in the most optimal order. Every field’s datatype has a specific byte alignment and intrinsic size. Ordering fields randomly may lead to excessive filler bytes in order to meet alignment requirements. The Z# compiler will generate a StructLayoutAttribute(LayoutKind.Auto) attribute for each struct. A StructLayoutAttribute can be used in the code to disable / override this reordering.

The .NET compiler does the actual layout in memory.


Tables

Find a way to allow to easily define tables of data using struct types in an array.

MyStruct
    fld1: U8
    fld2: Str

arr: Array<MyStruct> = (
    { fld1 = 42, fld2 = "42" },     // by name
    { 101, "101" },                 // in field order
)

If we allow this, then we can also move towards easy structure instantiation (ala JS/Json).

MyStruct1
    fld1: U8
    fld2: Str
MyStruct2
    first: MyStruct1
    second: MyStruct1

// TODO: a different syntax is used here!

// in order
v : MyStruct2 =
    { 42, "42" }
    { 101, "101" }

// by name
v: MyStruct2 =
    first = { fld1 = 42, fld2 = "42" }
    second = { fld1 = 101, fld2 = "101" }

// structured by name
v: MyStruct2 =
    first =
        fld1 = 42
        fld2 = "42"
    second =
        fld1 = 101
        fld2 = "101"

// or any combination of these??

Anonymous Structures

Also known as Tuples.

.NET makes a distinction between (two types of) tuples and anonymous types - Z# does not. We do need to choose how Z# is going to leverage these .NET Tuples in the code generation.

Use {} for object creation syntax.

a := { Fld1 = 42, Fld2 = "42" }

// a is a tuple with two fields
x := a.Fld1  // U8
y := a.Fld2  // Str

// deconstruct - creates new vars
(fld1, fld2) := a        // -or-
fld1, fld2 := a
fld1, fld2 := ...a       // spread operator?

// build new tuple from vars
b := { fld1, fld2 }

same := (a = b)
// true: compared on value semantics

Preferred is to use field names for tuples, but even those can be omitted but then deconstruction has to be used to unpack.

// no structure type name, no field names
x := { 42, "42" }
// C# does this (not for ValueTuple though):
x.Item1 // Error: Item1 does not exist

// to use, must deconstruct in order
(n, s) := x      // -or-
n, s := x
// n = 42 (U8)
// s = "42" (Str)

This is how you reference anonymous types in code.

// have to repeat the structure of the type
anoStructFn: (s: (number: U8, name: Str))
// if the anonymous type also has no field names
anoStructFn: (s: (U8, Str))

We want to line up the syntax (and semantics) with the parameters of a function call. Adopting a global rule that ‘field-lists’ (tuples, function params, deconstructs etc) can be build in-order or named or a combination (see function parameters). Array initialization too? arr = (1, 2, 3, 4) - has no field names!


Transformation (Mapping)

TBD

Allow fields of one structure to be mapped easily to fill fields of another structure.

What operator to use? <=?

s1 : Struct1
// manual
s2 : Struct2 =
    fld1 = s1.x
    fld2 = s1.y

// by convention
s2: Struct2 <= s1

Transform using a custom Type and rules/constraints?

Requires some library plumbing code.

// marker type for compiler
Transform<T1, T2>
    ...
// these functions are templates where the mapping rules are compiled into
transform: <T1, T2>(self: Transform<T1, T2>, source: T1): T2
    ...
reverseTransform: <T1, T2>(self: Transform<T1, T2>, source: T2): T1
    ...
// Transform is the compiler supported marker type.
MapS1ToS2: Transform<Struct1, Struct2>
    #Struct2.fld1 = #Struct1.x
    #Struct2.fld2 = #Struct1.y

// transform: <T: Transform<S, D>, S, D>(self: T, source: S): D
s2 := MapS1ToS2.transform(s1)
// reverseTransform: <T: Transform<S, D>, S, D>(self: T, source: D): S
s1 := MapS1ToS2.reverseTransform(s2)

// additive api: allows parameters to be passed to multiple transformations

// transform: <T: Transform<S, D>, S, D>(self: T, source: S, destination: D)
MapS1ToS2.transform(s1, s2)
// reverseTransform: <T: Transform<S, D>, S, D>(self: T, source: D, destination: S)
MapS1ToS2.reverseTransform(s2, s1)
// Transform could support all kinds of hooks (overrides)
afterTransform: (self: MapS1ToS2)
    self.Target.fld3 = self.Source.z.Str()

// execution of these hooks would not be obvious!

Rule base mapping could use specific operators to indicate what rules to use for normal or reverse mapping.

// Transform<Source, Destination>
MapStruct1Struct2: Transform<Struct1, Struct2>
    #Struct2.fld1 <= #Struct1.x     // src => dest
    #Struct2.fld2 <=> #Struct1.y    // src => dest and reverse
    #Struct2.fld3 => #Struct1.z     // dest => src

TBD: More complex mappings:

CustomFn1: (Struct1 self): U16
    ...
Deconstruct2
    x: U16
    z: U16
CustomFn2: (Struct2: self): Deconstruct2
    ...
CustomFn3: (Struct1: self, x: U16, y: U16)
    ...

// Transform<Source, Destination>
MapStruct1Struct2: Transform<Struct1, Struct2>
    // CustomFn1 yields mapping result
    #Struct2.fld1 <= #Struct1.CustomFn1
    // CustomFn2's result is deconstructed onto target fields
    #Struct2.CustomFn2 => #Struct1.x, Struct1.z
    // CustomFn3 receives 2 parameters
    #Struct2.CustomFn3 <= #Struct1.x, Struct1.y
MapStruct1Struct2: Transform<Struct1, Struct2>
    // match fields by name (2-way)
    #Struct1 <=> #Struct2

If no mapping rule structure is defined the field names are matched on a 1:1 basis in both directions using Identifier name matching rules.


Dependent Field Validation

(Dependent Types)

Add validation rules between fields. For example: #fld1 > fld2

When/how are these rules enforced?

MyStruct
    fld1: U8
    fld2: Str
    // the length of the string must be less than value of fld1
    #fld2.Length < fld1

TBD