Control Flow
Feng provides conditional branches, three loop forms, and break, continue, and return. Pattern matching is covered separately in Pattern Matching.
Conditional Branches
if score >= 90 {
println("excellent");
} else if score >= 60 {
println("pass");
} else {
println("retry");
}
A condition must be bool. Feng does not implicitly treat a number, string, or object as a Boolean value.
if can also be used as an expression:
let status = if ready {
"ready";
} else {
"waiting";
};
The expression form requires an else, and every normally completing path must reach the block's final result
expression. A path that returns from the current function, method, or lambda, or escapes through throw, produces no
result. When the context provides a target type, every normal result must fit that type. Without a contextual target,
Feng determines a target type from the normal results and requires the remaining normal results to fit it.
while
var index = 0;
while index < 3 {
println("{0}", index);
index += 1;
}
The condition is reevaluated before each iteration.
Three-Clause for
for var index = 0; index < 10; index += 1 {
if index == 3 {
continue;
}
if index == 8 {
break;
}
println("{0}", index);
}
The initializer, condition, and update clauses can each be omitted. for ;; { ... } is an infinite loop.
A binding can be declared only in the initializer clause. The update clause may modify an already visible binding or
call a function, but it cannot declare a new binding with let or var.
A nonempty initializer runs exactly once when control enters the loop. A binding declared by the initializer remains the same binding throughout the loop, and a binding declared outside the loop remains the original binding. The condition, update, and every execution of the body refer to those same bindings. An initializer binding belongs to the loop-header scope, and the braced body is its child block. A body-local binding may use the same name and shadows the initializer binding from its declaration onward.
Each entry into the body is a new execution of that block. Whenever a body-local declaration is executed, it creates a new binding. Closures created in different iterations therefore share a captured outer or initializer binding, but capture separate body-local bindings for their respective iterations.
for/in
let values = [10, 20, 30];
var total = 0;
for let value in values {
total += value;
}
for/in can iterate over arrays and over standard-library or user-defined iterators that implement the @iterable /
@iterator protocols. Before each entry into the body, Feng creates a new loop-variable binding initialized with the
current element. This is equivalent to executing the corresponding local declaration once per iteration immediately
before the body begins. The loop variable belongs to the current iteration's header scope, and the braced body is its
child block. A body-local binding may use the same name and shadows the loop variable from its declaration onward.
When each element is a named tuple, the loop can destructure it directly:
type Entry(string, int);
let apples: Entry = ("apples", 2);
let pears: Entry = ("pears", 3);
let entries: Entry[] = [apples, pears];
for let (name, count) in entries {
println("{0}: {1}", name, count);
}
for var (name, count) in entries {
count += 1;
}
The let or var keyword is required and applies to every named position. A pattern must have the same number of
positions as the tuple. An empty position skips that element, for example for let (name, ) in entries. Patterns are
one level only: nested tuple patterns, single-position patterns, and type annotations inside a pattern are not
supported. Each nonempty position is a separate per-iteration binding.
Names in one pattern must be distinct.
A let loop variable cannot be reassigned. A var loop variable can be modified during its iteration, but doing so
does not change the iterated sequence or the next iteration's initial value. A closure captures only the current
iteration's binding: closures created in the same iteration share that binding, while closures from different
iterations do not. If a closure is created before the same iteration's var is modified, it observes the modified
value. The same rules apply independently to every nonempty tuple-pattern position.
Control Transfer
breakexits the nearest loop immediately. It does not run the pending iterator clause of a three-clauseforor acquire another element from afor/inloop.continueskips the remaining statements in the current iteration of the nearest loop. Awhilereevaluates its condition; a three-clauseforruns its iterator clause exactly once and then reevaluates its condition; afor/inloop acquires the next element. Multiplecontinuebranches still reach the continuation step only once on any executed path.returnends the current function and can carry a value when required by the function's return type.
Neither break nor continue accepts a label or nesting count. Loops can be nested, but each statement targets only
the nearest loop and cannot cross multiple nesting levels.
When if, match, or try/catch is used as an expression, each result branch is a control-transfer boundary: a
break or continue in that branch cannot target a loop outside the expression. A while, three-clause for, or
for/in loop declared inside the branch may still use break and continue, which target only that inner loop. The
same constructs do not impose this additional boundary when used as statements.
A result branch may use return to exit the current function, method, or lambda. Only paths that continue normally
must reach the block's final result expression; a return path produces no branch value and does not participate in
branch-result type inference. A return inside a nested lambda returns only from that lambda.
Writing a Custom Iterator
A container creates a cursor, and the cursor stores the current position. This example uses fit to add an @iterable entry to a container and a named (bool, int) tuple for each step result:
module manual_iterator;
import std.io;
import std.numeric;
/** Half-open range with reusable traversal state. */
type NumberRange { let start: int; let end: int; }
/** Indicates whether an element was produced. */
type RangeStep(bool, int);
/** Mutable state for one traversal. */
type RangeCursor {
var current: int;
let end: int;
/** Produces an element or the end marker. */
@iterator
func next(): RangeStep {
if self.current >= self.end { return (false, 0); }
let value = self.current;
self.current += 1;
return (true, value);
}
}
fit NumberRange {
/** Creates a new cursor for each traversal. */
@iterable
func cursor(): RangeCursor {
return RangeCursor { current: self.start, end: self.end };
}
}
/** Compares container traversal and direct cursor traversal. */
func main(args: string[]) {
let range = NumberRange { start: 0, end: 3 };
var total = 0;
for let value in range { total += value; }
let cursor = range.cursor();
var direct = 0;
for let value in cursor { direct += value; }
var remaining = 0;
for let value in cursor { remaining += 1; }
println<int>("{0} {1} {2}", total, direct, remaining);
}
The output is 3 3 0. Each container traversal calls @iterable once. Returning a new cursor here makes repeated or nested traversals independent. Direct cursor traversal uses its current state, and an exhausted cursor is not automatically reset.
Both annotations mark instance methods with no explicit parameters, declared either in the type or in a visible fit. A type’s visible member surface cannot contain both kinds of entry or multiple entries of one kind. @iterable must return a type with a valid @iterator; @iterator must return a named two-element tuple whose first element is bool. When returning false, provide the default second element; the loop does not consume it. Method names are your choice.