Osy#betaRun it on Osyrin: osyrin.com

Reference / Function

The C# Osy# leaves out — and why

// no unsafe, pointers, fixed, stackalloc, function pointers · no float · no Thread, SynchronizationContext, TaskScheduler, AsyncLocal

Osy# is C#-shaped, and leaves out three things on purpose: unsafe code (pointers, fixed, stackalloc, function pointers, native interop), float, and four threading primitives (Thread, SynchronizationContext, TaskScheduler, AsyncLocal). Each is refused by name with its reason and what to write instead. async, await, Task, cancellation, Parallel and SemaphoreSlim are IN, with C#'s meaning.

stable1 example compiled by CIfunctioncsharpcompatibility

Summary#

Osy# reads like C#, and most C# you write compiles and runs with C#'s answer. Two things are left out on purpose, and each is refused by name, with the reason and what to write instead — never with a parse error about a stray {:

Left outWhyWrite instead
unsafe code — pointers, &x, *p, fixed, stackalloc, function pointers, extern / [DllImport]the runtime is managed and durable: a body can suspend and resume later, on another machine, and no pointer or stack buffer survives thatan array or a List<T> and an index; an HTTP call or a kit for an outside service
float (1.5f)there is no single-precision type: decimal and double cover every stored numberdecimal (1.5m) or double (1.5d)

Signature#

// Each of these is refused BY NAME, with its reason:
unsafe { int* p = &x; }            // Osy# has no `unsafe` code …
var buf = stackalloc int[3];       // Osy# has no `stackalloc` …
float f = 1.5f;                    // float literals are not supported …

Description#

Why is there no unsafe code?#

A pointer names a place in ONE process's memory. An Osy# function body is durable: when it reaches an effect it can suspend, be persisted, and resume — after a redeploy, on another machine. A pointer cannot make that trip, and neither can a stack buffer, a pinned array or a native library handle. So the whole family is out: unsafe blocks and methods, int*, &x, *p, fixed, stackalloc, delegate* function pointers, extern methods, [DllImport]/[LibraryImport], and the CLR knobs that only mean something to .NET's JIT — [SkipLocalsInit], [ModuleInitializer], [InlineArray].

What you would do with a pointer, you do with an index:

int SumEvery(int[] xs, int step) {
  int total = 0;
  for (int i = 0; i < xs.Length; i += step) { total += xs[i]; }
  return total;
}

What about async?#

It is IN, with C#'s meaning: async Task<T> methods, await, Task<T> and ValueTask<T> values, Task.WhenAll and Task.WhenAny, Task.Run, .Result and .Wait(), ContinueWith, await foreach over an IAsyncEnumerable<T>, CancellationToken and TaskCompletionSource (Stop a task (CancellationToken) and complete one by hand (TaskCompletionSource)), Parallel and SemaphoreSlim (Do the same work for many items at once (Parallel, SemaphoreSlim)) — and an un-awaited call really does run beside its caller (async / await and Task).

What about threads?#

Four threading primitives are left out, each refused by name: Thread (a body never owns a thread — run work as a task with Task.Run, wait with Task.Delay), SynchronizationContext (there is no UI thread to return to, so an await continues where it was), TaskScheduler (the platform schedules every task; limit how many run with a SemaphoreSlim) and AsyncLocal<T> (a value flowing invisibly along a call chain cannot survive a durable pause — pass it as a parameter).

What about using and nested types?#

Both are IN, exactly as C# has them. using (var r = …) { … } and using var r = …; call Dispose() on every way out of the block, over a class that implements IDisposable — for what lives outside Osy#, like an external database session; the platform's own values need none (using / IDisposable). A class may declare a class, an interface or an enum inside it, named Outer.Inner from outside and bare from inside ([[class-index#nested-types]]). using X; at the TOP of a file is still the import it looks like ([[types-use#dotnet-namespaces]]).

And no float?#

Every number the platform stores is an int, a long, a decimal or a double. A single-precision type would be a fifth that rounds differently from all four. Write decimal (1.5m) for money and anything exact, double (1.5d) for measurement.

Is anything else deliberately different?#

One answer differs from C# on purpose, stated where it happens: integer arithmetic is checked — an overflow raises OverflowException rather than wrapping to a wrong number ([[function-throw#dotnet-exceptions]]). (A class compares by identity and a record by its members, as in C# — Comparing classes.) So checked(…) and checked { … } are accepted and change nothing (they ask for what is already true), while unchecked is refused by name with that reason. Everything else a C# developer writes is meant to give C#'s answer; where it does not yet, that is a gap, not a choice.

See also#

Related

async / await and Task

C#'s async and await, with C#'s meaning. A call to a Task-returning method that you do not await where you make it…

Stop a task (CancellationToken) and complete one by hand (TaskCompletionSource)

C#'s cancellation, with C#'s meaning. A CancellationTokenSource hands out a Token; cancel the source and every task…

Do the same work for many items at once (Parallel, SemaphoreSlim)

Parallel.For, Parallel.ForEach and Parallel.ForEachAsync run one body per item, each as a task beside the others, and…

function

A function is a top-level unit of work, written like a C# method — a return type, a name, typed parameters, a body. It…

use

Declares a capability your app depends on, written inside the `app { }` manifest block. It provisions the capability…

throw

Raises a fault. It ends the function immediately, and the rows the function wrote are discarded rather than…

using / IDisposable

C#'s `using`, exactly: `Dispose()` is called on every way out of the block — normal exit, return, break, continue, or…