Literals and variables
Literals create primitive values directly in source code. Variables give those values a name and a static type.
Literals
int decimalValue = 42;
int hexadecimal = 0x2A;
int binary = 0b101010;
float ratio = 0.5f;
double distance = 12.75;
bool ready = true;
char latin = 'A';
char cyrillic = 'Ж';
char emoji = '😀';
A character literal contains exactly one decoded Unicode scalar. It supports \0, \n, \r, \t, \\ and \'. Empty literals, multiple scalars, malformed escapes, surrogate values and unterminated literals are errors. One non-BMP scalar such as '😀' is one char, even though its source representation uses multiple UTF-8 bytes and .NET uses a UTF-16 surrogate pair internally.
A string literal produces an immutable, null-terminated UTF-8 byte sequence with type readonly byte*.
readonly byte* message = "Hello from Xenon";
The null literal receives a concrete pointer type from its surrounding declaration or call.
Numeric literal suffixes
A numeric suffix determines the literal expression's own type, including when selecting an overload. It is part of the literal, with no intervening whitespace.
| Suffix | Literal type |
|---|---|
f, F | float |
d, D | double |
u, U | uint |
l, L | long |
ul | ulong |
n, N | nint |
un | nuint |
Suffix matching is case-insensitive. The canonical multi-character forms are ul and un; UL, Ul, uL, UN, Un and uN have the same meanings. Order matters: lu and nu are invalid. Unsupported or combined suffixes such as uu, ull and fu produce a compile-time error.
An unsuffixed integer literal has type int if its value fits, otherwise long if it fits, otherwise ulong. Values above ulong's maximum are errors. An unsuffixed floating-point literal has type double.
The f and d suffixes apply to decimal literals with or without a fractional part or exponent: 100f, 100.0f, 1e2f, 100d and 100.0d. Integer suffixes apply to decimal, hexadecimal and binary integer literals. Hexadecimal digits are read before the suffix, so 0xFF is an unsuffixed integer and 0xFFu is a uint; hexadecimal d and f are digits.
uint flags = 0xFFu;
long count = 100l;
ulong maximum = 18446744073709551615ul;
nint offset = 100n;
nuint mask = 0b1010un;
A suffixed literal must fit its requested type. For example, 4294967295u is valid and 4294967296u is an error, even when assigned to a wider type. The ul suffix permits the full range from zero through 18446744073709551615. Native integer ranges follow the target pointer width: nint is signed and nuint is unsigned, with 32 or 64 bits according to the target. Floating-point suffixes reject values that overflow their type's finite range; normal floating-point rounding applies.
A leading minus is a unary operator: -10l negates a positive long literal. Range checking applies to that positive literal before negation. Thus -9223372036854775808l contains an out-of-range literal; the minimum long value can be expressed as -9223372036854775807l - 1l.
Local variables
Variable declarations use type-first syntax. A local becomes readable after a value has been assigned to it.
int count = 10;
float speed;
speed = 4.5f;
Each declaration introduces one local name into the current block scope. Nested blocks may introduce their own locals.
Local type inference
The var keyword declares a local variable whose type is inferred from its initializer. An initializer is required. Inference uses the initializer's static type and does not depend on later assignments, calls or other uses.
var count = 10; // int
var flags = 10u; // uint
var ratio = 10f; // float
var distance = 10.0; // double
var size = GetSize();
var ptr = GetPointer();
count = 20;
readonly var limit = 100;
const var bufferSize = 64u * 1024u; // uint, compile-time constant
Numeric literals retain their normal default and suffixed types. The declaration supplies no target type to the initializer. The full static type is preserved, including pointer and reference qualifiers and unique<T>, shared<T> or weak<T> ownership wrappers. A function returning T& produces a reference local when used as a var initializer.
A var local is mutable unless an existing modifier such as readonly says otherwise. Assignment compatibility, copy and move rules, borrowing, lifetime checks and destruction follow the same rules as an explicit declaration of the inferred type.
const var declares a local compile-time constant whose type is inferred from its initializer. The initializer is required and must be a constant expression; the constant cannot be reassigned. For example, const var count = 10; has the same meaning as const int count = 10;. Unlike const var, readonly var permits an initializer evaluated at runtime.
var is a reserved declaration keyword, not a type. It is valid only for local declarations, including a for initializer. It cannot appear in fields, parameters, return types, generic arguments, casts, type-layout operations or pointer/reference type syntax. var value;, var value = null; and an initializer returning void produce compile-time errors. Expressions that need a contextual type, such as an unresolved function name or an implicitly typed lambda, require an explicit declaration type.
Compile-time constants and runtime immutability are separate concepts. See const and readonly.