Beginner

Control Flow in Carbon

Learn how to direct program execution in Carbon with if/else conditionals, the if/then/else expression, match statements, while loops, and loop control using break and continue

Control flow is what turns a sequence of statements into a program that makes decisions and repeats work. As a multi-paradigm language blending imperative, object-oriented, and generic programming, Carbon offers a familiar-but-modernized set of control structures aimed at C++ developers: if/else for branching, match for multi-way dispatch, and while for looping. The syntax is deliberately consistent and explicit, reflecting Carbon’s goal of being readable and toolable.

One design choice immediately distinguishes Carbon from C and C++: Carbon has a genuine bool type, and conditions must be boolean. There is no “non-zero is true” rule — if (count) is a type error if count is an integer; you write if (count != 0) instead. This follows from Carbon’s static, nominative type system, which prefers explicit intent over implicit conversions.

Carbon also treats if/then/else as an expression that produces a value, not just a statement. You saw this in the Min function on the Carbon overview page (return if x <= y then x else y;). That expression form sits alongside the statement form, giving you a concise way to choose between two values inline.

In this tutorial you will branch with if/else if/else, choose values with the if/then/else expression, dispatch on a value with match, loop with while, and steer loops using break and continue. Every example uses only Core.PrintStr so the output is fully deterministic, and all four compile and run with the nightly toolchain inside Docker.

Conditionals: if, else if, and else

The if statement evaluates a boolean expression in parentheses and runs a block when it is true. Chaining with else if tests conditions in order, and a final else catches everything that falls through. The last line below also shows the if/then/else expression, which yields a value passed straight to Core.PrintStr.

Create a file named conditionals.carbon:

import Core library "io";

fn Classify(n: i32) {
  if (n > 0) {
    Core.PrintStr("positive: ");
  } else if (n < 0) {
    Core.PrintStr("negative: ");
  } else {
    Core.PrintStr("zero: ");
  }

  // if/then/else is an expression that produces a value
  Core.PrintStr(if (n >= 0) then "non-negative\n" else "negative\n");
}

fn Run() {
  Classify(7);
  Classify(-3);
  Classify(0);
}

For 7, the first branch prints positive: and the expression yields non-negative. For -3, the else if branch runs and the expression yields negative. For 0, the final else runs and, because 0 >= 0 is true, the expression yields non-negative.

Multi-way dispatch with match

When you need to branch on the specific value of something, match is Carbon’s structured alternative to a long if/else if chain. Each case carries a pattern; execution runs the first matching case and then exits the match — there is no fall-through like C’s switch, so no break is needed. A default case handles any value that no pattern matched.

Create a file named matching.carbon:

import Core library "io";

fn NameDay(n: i32) {
  match (n) {
    case 1 => {
      Core.PrintStr("Monday\n");
    }
    case 6 => {
      Core.PrintStr("Saturday\n");
    }
    case 7 => {
      Core.PrintStr("Sunday\n");
    }
    default => {
      Core.PrintStr("a weekday\n");
    }
  }
}

fn Run() {
  NameDay(1);
  NameDay(4);
  NameDay(7);
}

NameDay(1) matches case 1 and prints Monday. NameDay(4) matches none of the literal cases, so the default case prints a weekday. NameDay(7) matches case 7 and prints Sunday. match is more powerful than this — its patterns can also bind variables and destructure values — but matching on literal values is the most common starting point.

Looping with while

Carbon’s while loop tests a boolean condition before each iteration and runs its body as long as the condition stays true. Because var bindings are mutable, the loop body can update the counter that the condition checks.

Create a file named while_loop.carbon:

import Core library "io";

fn Run() {
  var count: i32 = 3;
  while (count > 0) {
    Core.PrintStr("tick\n");
    count = count - 1;
  }
  Core.PrintStr("liftoff\n");
}

The loop starts with count equal to 3 and prints tick on each pass, decrementing count each time. When count reaches 0 the condition count > 0 becomes false, the loop ends, and liftoff prints once. Note that count is declared with var (mutable); an immutable let binding could not be reassigned inside the loop.

Loop control with break and continue

break exits the nearest enclosing loop immediately, while continue skips the rest of the current iteration and jumps back to re-test the condition. Together they let you steer a loop without extra flag variables. The second loop below uses while (true) — an intentionally infinite loop that relies on break to terminate.

Create a file named loop_control.carbon:

import Core library "io";

fn Run() {
  // continue: skip the third pass, run all the others
  var i: i32 = 0;
  while (i < 5) {
    i = i + 1;
    if (i == 3) {
      continue;
    }
    Core.PrintStr("step\n");
  }

  // break: leave an otherwise-infinite loop once a condition is met
  var j: i32 = 0;
  while (true) {
    if (j == 2) {
      break;
    }
    Core.PrintStr("loop\n");
    j = j + 1;
  }
}

The first loop counts i from 1 to 5 but uses continue to skip printing when i is 3, so step prints four times. The second loop would run forever, but break stops it as soon as j reaches 2, after loop has printed twice.

Running with Docker

Carbon’s nightly toolchain runs on Linux. The command below downloads the toolchain once inside an Ubuntu container, compiles and links all four examples, then runs each one with a labeled separator.

1
2
3
4
5
# Pull the Ubuntu image
docker pull ubuntu:22.04

# Download the toolchain once, then compile, link, and run all four examples
docker run --rm -v $(pwd):/app -w /app ubuntu:22.04 bash -c "apt-get update -qq && apt-get install -y -qq wget libgcc-11-dev > /dev/null 2>&1 && VERSION=0.0.0-0.nightly.2026.02.07 && wget -q https://github.com/carbon-language/carbon-lang/releases/download/v\${VERSION}/carbon_toolchain-\${VERSION}.tar.gz && tar -xzf carbon_toolchain-\${VERSION}.tar.gz && CARBON=./carbon_toolchain-\${VERSION}/bin/carbon && for f in conditionals matching while_loop loop_control; do \$CARBON compile --output=\$f.o \$f.carbon && \$CARBON link --output=\$f \$f.o; done && echo '--- conditionals ---' && ./conditionals && echo '--- matching ---' && ./matching && echo '--- while_loop ---' && ./while_loop && echo '--- loop_control ---' && ./loop_control"

Note: The first run downloads roughly 200 MB for the toolchain, so it takes a few minutes. You can also paste any single example into Compiler Explorer to run it instantly in the browser.

Expected Output

--- conditionals ---
positive: non-negative
negative: negative
zero: non-negative
--- matching ---
Monday
a weekday
Sunday
--- while_loop ---
tick
tick
tick
liftoff
--- loop_control ---
step
step
step
step
loop
loop

Key Concepts

  • Conditions are strictly boolean — unlike C, Carbon requires if, else if, and while conditions to be bool. There is no “non-zero is true” shortcut; compare explicitly, as in if (count != 0).
  • if/then/else is an expression — the expression form yields a value (if c then a else b) and can be used inline, while the braced statement form (if (c) { ... } else { ... }) controls execution. They are distinct tools.
  • match does not fall through — Carbon runs only the first matching case and then exits, so no break is required. Use a default case to handle unmatched values.
  • var is mutable, let is not — loop counters and any value reassigned inside a loop must be declared with var; an immutable let binding cannot be updated.
  • break and continue steer loopsbreak leaves the loop entirely and continue jumps to the next condition check. Both act on the nearest enclosing loop, and while (true) plus break is a common pattern for loops with a mid-body exit.
  • Patterns power match — beyond the literal cases shown here, Carbon’s match patterns can bind variables and destructure values, making match a central tool as the language matures.
  • Still experimental — Carbon is pre-0.1, so control-flow syntax and library functions (including the richer for/in iteration the design envisions) continue to evolve with each nightly toolchain release.

Running Today

All examples can be run using Docker:

docker pull ubuntu:22.04
Last updated:

Comments

Loading comments...

Leave a Comment

2000 characters remaining