Osy#betaa language · its runtime Osyrin · a hosted platform
Why Osy#Built for agentsAgents as declarationsWorkflows that waitRuns exactly onceSecure by defaultNothing to mockThe editor is the compilerUI in the languageDocuments are dataOne program

Reference / Function

Typed locals

int x = 5; · Order o = …; · List<int> xs = …; · x = 6;

Locals can declare an explicit type instead of var; the declared type pins the binding, and every later assignment to the name is checked against it by the same rule. Literal initializers apply the C# constant conversion; non-literals widen by numeric rank but never across decimal↔double.

stable2 examples compiled by CIfunctiontypesauthoring

Summary#

Locals can declare an explicit type instead of varint x = 5;, Order o = …;, List<int> xs = …;. The declared type pins the binding, exactly C#: literal initializers apply the C# constant conversion, non-literal initializers must be implicitly assignable, and every typed local must be initialized at its declaration. The same rule governs every later x = … — the declaration and the assignment on the next line are checked by one function, so they cannot disagree.

Signature#

<Type> <name> = <initializer>;
<name> = <value>;                     // …and every later assignment, by the same rule
// Type: a scalar keyword, entity (incl. namespaced), Type?, Type[], Type[][],
//       List<T>/HashSet<T>/Dictionary<K,V>

Description#

  • A typed local must be initialized at its declarationint x; is refused (there is no definite-assignment analysis; initialize where you declare).
  • Literal initializers use the C# constant conversion: the literal is re-kinded when widening (int → long/decimal/double, decimal → double) — so double h = 2.5; compiles even though a bare 2.5 is decimal (Numeric types & literal suffixes). A non-representable constant refuses: int x = 2.5m;cannot implicitly convert 'decimal' to 'int'.
  • Non-literal initializers widen by numeric rank (int → long → decimal/double) but never across decimal↔double in either direction — C# has no implicit conversion between them (double x = someDecimal; is a pointed error).
  • null needs a nullable declared typeint? x = null;, not int x = null;.
  • The pinned type drives downstream resolution — an entity-typed local navigates members (Order o = Order.First(); o.Total).
  • Decompile normalizes to var with the re-kinded literal (decimal d = 5;var d = 5m;) — semantically identical; the same normalization const-inlining uses.

Assigning to it afterwards#

Every assignment is checked against the slot, by the rule above. int x = 1; x = "hello"; is refused, and the message names the variable, what it holds, what you tried to store and the line the type was decided on. This holds wherever the value lands and however it is spelled:

The slotExample
a typed local, or one inferred by varint x = 1; x = "hello";
a parametervoid F(int p) { p = "hello"; }
a for / foreach / catch variableforeach (var i in xs) { i = "hello"; }
a compound assignmentx += "hello";
a chaina = b = "hello";
a component field, with or without this.counter = "hello"; inside an action or a render lambda
a dictionary, list or array element — and the keyd[42] = 1; on a Dictionary<string, int>
a class indexer's setbag["a"] = "hello"; on int this[string key]

The conversions it ACCEPTS are the same ones a declaration, an argument and a return accept: the C# constant conversion for a numeric literal (decimal d = 0; d = 1; stores a decimal), numeric widening by rank, and the implicit upcast to a base type. decimal ↔ double is refused in both directions, as in C#.

Examples#

entity Order { decimal Total; }

decimal Examples() {
  int x = 5;                         // pins int
  long big = 5;                      // constant conversion: the int constant becomes long
  decimal d = 5;                     // → 5m
  double h = 2.5;                    // works — the constant converts (a bare 2.5 is decimal)
  int? maybe = null;                 // nullable declared type accepts null
  Order? o = Order.FirstOrDefault(); // entity-typed local — `?`, because …OrDefault() may answer null
  if (o != null) { return o.Total + d; }
  return d + x + big + maybe ?? 0;
}
decimal Later() {
  int x = 1;
  x = 2;                             // fine — same type
  decimal d = 0;
  d = 1;                             // fine — the int constant becomes a decimal, as at a declaration
  long big = 0;
  big = x;                           // fine — int widens to long
  // x = "hello";                    // refused: cannot assign 'string' to `x`, which holds 'int'
  // d = 1.5;   ⟵ fine too; but `double h = 0; h = d;` is refused — no decimal↔double conversion
  return d + big;
}

The ? on o is the example, not a typo. A …OrDefault() read answers null when nothing matches, so a non-nullable Order o would be holding a null the moment the table is empty — and every later read of it would be an unguarded one. The compiler refuses that declaration and names both honest choices: Order? o if absent is a case you handle, or First() if it is not, which fails loudly at the read instead of handing you a null that surfaces somewhere else. This page carried the non-nullable form until 2026-08-27, when the check that catches it landed.

See also#

Related

Numeric types & literal suffixes

The platform numeric types are int, long, decimal, and double. Literals follow C# exactly, suffixes (L, m, d) included:…

const

A value fixed at compile time and folded into the places it is used. Declare one at the TOP LEVEL to share it across…

var

Declares a local whose type is inferred from its initializer, exactly as in C#. The local is still statically typed —…