Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / Function

function

<ReturnType> <Name>(<params>) { … }

A function is a top-level unit of work, written like a C# method — a return type, a name, typed parameters, a body. It runs transactionally: the rows it writes are committed together when it finishes.

stable5 examples compiled by CIfunctionlogic

Summary#

A function is the unit of work: a return type, a name, typed parameters and a body, written exactly like a C# method but declared at the top level of a file. It is where you create rows, query them and decide things.

It runs transactionally. The rows a function writes are committed together when it returns, so a function that fails half-way leaves nothing behind. There is no Save() and no Commit() to remember.

That is true of a FUNCTION and not of a component action. An action runs in the page's optimistic overlay, where a write renders immediately and is not yet persisted — so an action ends with UnitOfWork.Commit(), and the examples on this page do not because they are functions. If you are looking at a body inside a component, see creating & saving data; the rule there is the opposite of the one here, and knowing which body you are in is the whole of it.

Signature#

<ReturnType> <Name>(<Type> <param>, …) {
  <statements>
}

Description#

How do I declare a function?#

void when it returns nothing, a type when it returns something. Parameters are typed and camelCase; the function name is PascalCase:

entity Order {
  [Required] string Code;
  decimal Total;
}

void PlaceOrder(string code, decimal total) {
  var o = new Order { Code = code, Total = total };
}

decimal OrderTotal(string code) {
  var o = Order.Single(x => x.Code == code);
  return o.Total;
}

It commits as one unit#

Everything a function writes lands together, or not at all. That is what lets you write the obvious thing:

entity AuditLine {
  [Required] string Message;
}

void PlaceAndLog(string code, decimal total) {
  var o = new Order { Code = code, Total = total };
  var a = new AuditLine { Message = "placed " + code };
  // both rows commit together — there is no state where the order exists and the log line does not
}

If the function faults — a constraint violation, an invariant, a division by zero — neither row is written. You do not have to unwind anything by hand.

There is no async#

A function that reaches outside the database — an HTTP call, an LLM completion — is written exactly like any other function. There is no async, no Task<T>, and no colour to keep track of:

app Shop {
  model "model/**/*.osy";
  use Osyrin.Http;
}

string Fetch(string url) {
  var r = Http.Get(url);           // just a call — the engine suspends and resumes around it
  return r.IsSuccess ? r.Body : "";
}

Those outward calls are effects, and the platform handles them: when a function hits one, the engine suspends it, performs the effect, and resumes the function where it left off — even if that means surviving a process restart in between. You do not have to mark the function, and neither does its caller.

This is why C#'s async is absent rather than merely optional. async exists to colour a function so its callers know to await it, and that colour spreads until it has infected everything it touches. Here the durability is the engine's job, not the signature's, so there is nothing to spread.

await appears in exactly one place in the language — Workflow.Run() — where you are genuinely waiting for another long-running thing to finish, and want to say so. If you are coming from C#, async / await — why Osy# has neither is the page to read: it is the first habit to unlearn.

Where may a function be declared?#

At the top level of a file — that is what a function is. It is not nested in anything, and there is no namespace, module or class you have to put it inside first. A file may hold as many as you like, beside its entities and components. Not inside an entity, though: an entity body holds data, and behaviour sits beside it.

Two things that LOOK like the same question are not, and the difference is what each one commits:

you write it…what it iswhere it runswhat saves it
at the top level of a filea functioninferred from its body — see execution sideitself, on return
inside a componenta method of that componentwith the componentthe page's UnitOfWork.Commit()
inside a classa method of that classwherever it is called fromits caller

All three are written identically — a return type, a name, a typed parameter list, a body — so the enclosing declaration is the only thing that decides which you have. There is no function keyword to write and no method keyword either; see Writing a component — what differs from C# for why.

int Doubled(int n) { return n * 2; }                            // a function

class Rates { public decimal WithVat(decimal net) { return net * 1.25m; } }   // a class method

[Page("/counter")] [AllowAnonymous] [Render(CSR)]
component Counter() {
  int n = 1;
  int Quadrupled(int x) { return x * 4; }                       // a component method
  action Bump() { n = Quadrupled(n); }
  render { Text("n=" + n); }
}

And the top-level form beside the data it works on:

entity Product {
  [Required] string Name;
  decimal Price;
}

decimal PriceWithVat(Product p, decimal rate) {
  return Math.Round(p.Price * (1 + rate), 2);
}

If you want behaviour attached to a type — a method with a receiver — that is a class method.

See also#

Related

var

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

async / await — why Osy# has neither

Osy# has no async and no Task. A function that calls out to the world is written like any other function — the engine…

if / else

Conditional branching, exactly as in C#. The condition must be a bool — there is no truthiness, so a null or a number…

foreach

Walks a collection — a query result, a list, or a parent's children. The normal way to iterate; reach for a for loop…

class methods

Behaviour attached to a class — a method with a receiver, called as value.Method(). Classes are in-memory values, so a…

entity

Declares a persisted type — a table of rows the app stores, queries and secures. Every entity gets an Id and audit…

execution side

Where a function runs. Osy# infers it from the body: a function that reads data runs on the server, a function that…

Writing a component — what differs from C#

Osy# is C# almost everywhere, which is what makes the handful of deliberate differences worth knowing before you hit…