Skip to main content
Version: 0.3

Tutorial: Control flow

In this tutorial, you will write G# branches, loops, switches, and patterns. G# keeps compact if and for syntax, adds expression switches, and deliberately does not support fallthrough.

Prerequisites​

  • A working G# project.
  • Familiarity with arrays, slices, and data structs from Data and types.

1. Count with for​

G# supports for, while, and do-while. The for family remains the most common choice for indexed loops; use a C-style header when you need init and post clauses:

Loop.gs
// file: Loop.gs
// Demonstrates implicit `import System`, string interpolation,
// the C-style `for init; cond; post { … }` clause form, and
// the `i--` decrement statement.

package GSharp.Example.Loop

import System

var count = 5

for var i = count; i > 0; i-- {
Console.WriteLine("Count value: $i")
}

Expected output:

Count value: 5
Count value: 4
Count value: 3
Count value: 2
Count value: 1

A range-like integer loop can use start ... end, as shown in the slice and array samples.

2. Iterate collections with for in​

The canonical collection iteration form is for value in collection. Dictionaries can bind key and value:

ForIn.gs
import System
import System.Collections.Generic

class NumberEnumerator(Index int32, Current int32) {
func MoveNext() bool {
Index = Index + 1
if Index <= 3 {
Current = Index * 2
return true
}

return false
}
}

class Numbers {
func GetEnumerator() NumberEnumerator {
return NumberEnumerator(0, 0)
}
}

var nums = []int32{1, 2, 3}
for v in nums {
Console.WriteLine(v)
}

var dict = Dictionary[string, int32]()
dict["one"] = 1
dict["two"] = 2
for k, v in dict {
Console.WriteLine(k)
Console.WriteLine(v)
}

var list = List[int32]()
list.Add(4)
list.Add(5)
for v in list {
Console.WriteLine(v)
}

for v in Numbers{} {
Console.WriteLine(v)
}

Expected output:

1
2
3
one
1
two
2
4
5
2
4
6

3. Use if for local decisions​

The CountWords sample combines if, dictionary lookup, and loops to count words:

CountWords.gs
// file: CountWords.gs
// Exercises dictionary construction, indexer read/write, collection iteration,
// and string interpolation in one program.

package GSharp.Example.CountWords

import System
import System.Collections.Generic

var words = [12]string{
"the", "quick", "brown", "fox", "jumps", "over",
"the", "lazy", "dog", "the", "quick", "fox",
}

var counts = Dictionary[string, int32]()

for w in words {
if counts.ContainsKey(w) {
counts[w] = counts[w] + 1
} else {
counts[w] = 1
}
}

for k, v in counts {
Console.WriteLine("$k: $v")
}

Expected output:

the: 3
quick: 2
brown: 1
fox: 2
jumps: 1
over: 1
lazy: 1
dog: 1

4. Use if as a value​

When a branch picks a value rather than a side-effecting action, if can also sit in expression position. The form supports else if chains and multi-statement blocks whose trailing expression is the branch value:

let pct = 85
let grade = if pct >= 90 { "A" }
else if pct >= 80 { "B" }
else if pct >= 70 { "C" }
else { "F" }
Console.WriteLine(grade)

Rules:

  • A value-position if MUST end in else (GS0276). If you do not need a value, use the statement form (if cond { … }).
  • Each branch is a block. The block's last expression becomes its value — there is no yield keyword. An empty block reports GS0277.
  • Branch tails are unified by the same common-type rule as ternary expressions; mismatched branches report GS0263.
  • Only the chosen branch runs — the other arms are not evaluated.

The complete worked example lives under samples/IfExpression.gs.

5. Use switch statements when each arm performs work​

Switch cases have block bodies and never fall through. If two cases should do the same work, factor the body into a helper or repeat it deliberately.

PatternSwitch.gs
// file: PatternSwitch.gs
//
// Phase B (close interpreter/emit gap): pattern-switch *statement* emit.
// Exercises constant, discard, relational, type, property, and list patterns
// end-to-end through gsc.

package GSharp.Samples.PatternSwitch

import System

open class Animal { var Name string }
class Dog : Animal { var Bark int32 }
class Cat : Animal { var Purr int32 }

func describe(n int32) {
switch n {
case 0 { Console.WriteLine("zero") }
case < 0 { Console.WriteLine("negative") }
case > 100 { Console.WriteLine("huge") }
default { Console.WriteLine("positive small") }
}
}

func name(a Animal) {
switch a {
case d is Dog { Console.WriteLine("dog ${d.Name} barks ${d.Bark}") }
case c is Cat { Console.WriteLine("cat ${c.Name} purrs ${c.Purr}") }
default { Console.WriteLine("unknown") }
}
}

func shape(xs []int32) {
switch xs {
case [1, _, 3] { Console.WriteLine("bookended-3") }
case [_] { Console.WriteLine("singleton") }
default { Console.WriteLine("other") }
}
}

data struct Point { var X int32 var Y int32 }

func origin(p Point) {
switch p {
case { X: 0, Y: 0 } { Console.WriteLine("origin") }
case { X: > 0, Y: > 0 } { Console.WriteLine("Q1") }
default { Console.WriteLine("elsewhere") }
}
}

describe(-5)
describe(0)
describe(7)
describe(250)
name(Dog{Name: "rex", Bark: 9})
name(Cat{Name: "ed", Purr: 4})
shape([]int32{1, 2, 3})
shape([]int32{42})
shape([]int32{9, 9})
origin(Point{X: 0, Y: 0})
origin(Point{X: 3, Y: 4})
origin(Point{X: -1, Y: 1})

Expected output:

negative
zero
positive small
huge
dog rex barks 9
cat ed purrs 4
bookended-3
singleton
other
origin
Q1
elsewhere

Pattern switch statements support constants, relational patterns, type patterns, list patterns, discard patterns, and data-struct property patterns.

6. Use switch expressions when each arm returns a value​

Switch expressions use -> arms:

SwitchExpression.gs
// file: SwitchExpression.gs
//
// Phase C (close interpreter/emit gap): switch *expression* emit.
// Exercises constant, discard, type, property, relational, and list
// pattern arms all returning a unified result type.

package GSharp.Samples.SwitchExpression

import System

open class Shape { var Name string }
class Circle : Shape { var Radius int32 }
class Square : Shape { var Side int32 }

func areaTag(s Shape) string {
return switch s {
case c is Circle: "circle"
case sq is Square: "square"
default: "shape"
}
}

data struct Pair { var A int32 var B int32 }

let nums = []int32{-3, 0, 1, 5, 101}
for n in nums {
let label = switch n {
case 0: "zero"
case < 0: "neg"
case > 100: "huge"
default: "small-pos"
}
Console.WriteLine("$n -> $label")
}

Console.WriteLine(areaTag(Circle{Name: "c", Radius: 1}))
Console.WriteLine(areaTag(Square{Name: "s", Side: 2}))

let xs = []int32{1, 2, 3}
let listLabel = switch xs {
case [1, _, 3]: "bookended"
case _: "other"
}
Console.WriteLine(listLabel)

let p = Pair{A: 7, B: 7}
let pairLabel = switch p {
case { A: 0, B: 0 }: "origin"
case { A: 7, B: 7 }: "diag77"
default: "other"
}
Console.WriteLine(pairLabel)

Expected output:

-3 -> neg
0 -> zero
1 -> small-pos
5 -> small-pos
101 -> huge
circle
square
bookended
diag77

A default arm is the easiest way to make the result total.

7. Try a compact pattern example​

Some pattern features originated on the interpreter path. The Patterns sample remains useful as the shortest expression-switch walkthrough:

Patterns.gs
// file: aspirational/Patterns.gs
//
// Short expression-switch walkthrough using relational and list patterns.

package GSharp.Samples.Patterns

import System

let number = 7
let numericLabel = switch number {
case < 0: "negative"
case > 0: "positive"
default: "zero"
}

let values = []int32{1, 2, 3}
let listLabel = switch values {
case [1, _, 3]: "bookended"
case _: "other"
}

Console.WriteLine("$numericLabel / $listLabel")

Expected output:

positive / bookended

What you learned​

  • Use for for infinite, condition, counted, and collection loops.
  • Use for x in xs for collection iteration.
  • Switch cases do not fall through, and fallthrough is reserved only for a clear diagnostic.
  • Patterns work in both switch statements and switch expressions.