Beginner

Variables and Types in Carbon

Learn about variable declarations, data types, type inference, and constants in Carbon with practical Docker-ready examples

Carbon is a statically typed language with partial type inference, designed to feel familiar to C++ developers while offering a cleaner, more modern syntax. Understanding Carbon’s type system is foundational because the language was designed with explicit, verifiable types at its core—unlike C++’s template system, where types are often checked only at instantiation.

Carbon uses var for mutable variable declarations and let for immutable bindings. This distinction is built directly into the language syntax, making mutability intent explicit at every declaration. The type system is nominative and static—the compiler knows every type at compile time and checks type safety rigorously.

In this tutorial you will declare variables of Carbon’s primitive types, use type inference with :! and auto, define constants, and see how Carbon’s integer and boolean types compare to their C++ equivalents.

Variable Declarations: var and let

Carbon separates mutable and immutable bindings at the syntax level. The var keyword declares a mutable variable; let declares an immutable binding. Both require (or infer) a type.

Create a file named variables_basic.carbon:

import Core library "io";

fn Run() {
  // Mutable variable: var <name>: <type> = <value>;
  var x: i32 = 42;
  var y: f64 = 3.14;
  var greeting: String = "Carbon";
  var flag: bool = true;

  Core.Print(x);
  Core.PrintStr("\n");
  Core.PrintStr(greeting);
  Core.PrintStr("\n");
}

Key points from this example:

  • var declares a mutable variable that can be reassigned later
  • Types are written after the name, separated by : — this is Carbon’s postfix type syntax
  • i32 is a 32-bit signed integer; f64 is a 64-bit floating-point number
  • String is Carbon’s built-in string type
  • bool holds true or false

Immutable Bindings with let

Create a file named variables_let.carbon:

import Core library "io";

fn Run() {
  // Immutable binding: let <name>: <type> = <value>;
  let max_retries: i32 = 5;
  let pi: f64 = 3.14159;
  let language_name: String = "Carbon";

  Core.PrintStr("Language: ");
  Core.PrintStr(language_name);
  Core.PrintStr("\n");
  Core.PrintStr("Max retries: ");
  Core.Print(max_retries);
  Core.PrintStr("\n");
}

let bindings cannot be reassigned after initialization. This is analogous to const in C++ or Rust’s default bindings. Prefer let whenever a value does not need to change — the compiler enforces it.

Integer and Floating-Point Types

Carbon provides explicit-width numeric types, similar to C++’s <cstdint> types but as first-class language types rather than typedefs.

Create a file named variables_numeric.carbon:

import Core library "io";

fn Run() {
  // Signed integers
  var a: i8  = 127;
  var b: i16 = 32767;
  var c: i32 = 2147483647;
  var d: i64 = 9223372036854775807;

  // Unsigned integers
  var ua: u8  = 255;
  var ub: u16 = 65535;
  var uc: u32 = 4294967295;

  // Floating point
  var f: f32 = 3.14;
  var g: f64 = 2.718281828;

  Core.PrintStr("i32 max: ");
  Core.Print(c);
  Core.PrintStr("\n");

  Core.PrintStr("u8 max: ");
  Core.Print(ua as i32);
  Core.PrintStr("\n");
}

Carbon’s numeric types:

TypeDescriptionRange
i88-bit signed integer-128 to 127
i1616-bit signed integer-32,768 to 32,767
i3232-bit signed integer-2,147,483,648 to 2,147,483,647
i6464-bit signed integer-9.2×10¹⁸ to 9.2×10¹⁸
u88-bit unsigned integer0 to 255
u1616-bit unsigned integer0 to 65,535
u3232-bit unsigned integer0 to 4,294,967,295
u6464-bit unsigned integer0 to 1.8×10¹⁹
f3232-bit IEEE 754 float~±3.4×10³⁸, 7 decimal digits
f6464-bit IEEE 754 float~±1.8×10³⁰⁸, 15 decimal digits

Reassigning Mutable Variables

var variables can be reassigned after declaration. The type is fixed at declaration — Carbon is statically typed, so you cannot change a variable’s type through reassignment.

Create a file named variables_mutate.carbon:

import Core library "io";

fn Run() {
  var count: i32 = 0;

  Core.PrintStr("Initial count: ");
  Core.Print(count);
  Core.PrintStr("\n");

  count = 10;

  Core.PrintStr("Updated count: ");
  Core.Print(count);
  Core.PrintStr("\n");

  count = count + 5;

  Core.PrintStr("Final count: ");
  Core.Print(count);
  Core.PrintStr("\n");
}

Running with Docker

Carbon’s nightly toolchain runs on Linux. Use the Ubuntu Docker image to compile and run any of these examples.

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

# Run the basic variables example
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_toolchain-${VERSION}/bin/carbon compile --output=variables_basic.o variables_basic.carbon && ./carbon_toolchain-${VERSION}/bin/carbon link --output=variables_basic variables_basic.o && ./variables_basic'

To run the mutation example:

1
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_toolchain-${VERSION}/bin/carbon compile --output=variables_mutate.o variables_mutate.carbon && ./carbon_toolchain-${VERSION}/bin/carbon link --output=variables_mutate variables_mutate.o && ./variables_mutate'

Note: The first run downloads approximately 200 MB for the toolchain. Subsequent runs reuse the cached Ubuntu image.

Expected Output

For variables_basic.carbon:

42
Carbon

For variables_let.carbon:

Language: Carbon
Max retries: 5

For variables_numeric.carbon:

i32 max: 2147483647
u8 max: 255

For variables_mutate.carbon:

Initial count: 0
Updated count: 10
Final count: 15

Key Concepts

  • var vs letvar declares a mutable variable; let declares an immutable binding. Prefer let when the value will not change; the compiler enforces it.
  • Postfix type syntax — Types appear after the name with a colon (var x: i32), not before as in C++ (int x). This is more readable with complex types.
  • Static, nominative typing — Every variable has a fixed type known at compile time. Carbon does not allow implicit type coercion between numeric types.
  • Explicit-width integers — Carbon uses i8, i16, i32, i64, u8, u16, u32, u64 rather than platform-dependent int or long. This eliminates a common class of portability bugs from C++.
  • f32 and f64 — Floating-point types use explicit sizes matching IEEE 754 single and double precision.
  • bool for booleans — Carbon uses lowercase bool with literals true and false, unlike C++ where true/false are also available but int is often used for booleans in legacy code.
  • No implicit narrowing — Carbon does not silently truncate integers when assigning between different-width types. Explicit conversion is required, preventing a widespread C++ bug class.
  • Experimental language — Carbon is pre-0.1; some type features (e.g., richer auto inference, user-defined type aliases) are still under active development. The concepts shown here reflect the current nightly toolchain.

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