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 out | Why | Write 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 that | an 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 number | decimal (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#
- async / await and Task —
async,awaitandTask - use —
useandusing, and whichusing System.…lines compile unchanged - throw — the exception types, the common .NET ones included
- function — what a function is
- using / IDisposable —
usingandIDisposable, which are in