The Plugfy language

The object model a .NET developer recognizes instantly, the safety of Kotlin and Swift, the honesty of Rust — with one surface, one error model, one null model.

Philosophy

Every construct lowers to one typed IR (PIR), so the same source is fast compiled, fast as a script, and fast under debug — the three execution modes can never diverge.

Records and values

Records are field-complete by construction — there is no zero-initialized invalid state. Errors are ordinary values:

billing.plug
namespace System.Billing
import System.Collections

public record Money(amount: decimal, currency: string) {
    public fn add(other: Money) -> Result<Money, BillingError> {
        if other.currency != currency {
            return Err(BillingError.CurrencyMismatch(expected: currency, got: other.currency))
        }
        return Ok(Money(amount: amount + other.amount, currency: currency))   // checked decimal +
    }
}

public enum BillingError { CurrencyMismatch(expected: string, got: string), Overdrawn(by: decimal) }

Enums are real sum types

An enum is a closed algebraic data type: a discriminant tag plus typed payloads. There is no "cast an arbitrary int in" path, and match is genuinely exhaustive — a missing arm is a compile error:

payment.plug
enum Payment { Card(number: string), Pix(key: string), Cash }

fn describe(p: Payment) -> string => match p {
    Payment.Card(number) => "card ending " + number
    Payment.Pix(key)     => "pix " + key
    Payment.Cash         => "cash"
}

Add a variant later and every non-updated match fails to compile — the compiler finds the code you forgot.

No overloading. Named and default parameters.

Plugfy has no method overloading — the resolution minefield is replaced by named arguments with defaults, and factory functions where construction varies. Calls read like sentences: Money(amount: 10m, currency: "BRL").

Capabilities & effect rows

Reaching the outside world — printing, the clock, the environment, the filesystem, the network — is a capability: a named, declared, grantable permission. A capability enters the language as a bodyless host fn, the language↔world seam:

effects.plug
host fn print(s: string) -> int

fn shout(s: string) -> int => print(s)         // uses { print }
fn twice(n: int) -> int => n + n               // uses { }        (pure)
fn banner(s: string) -> int => shout(shout(s)) // uses { print }  (transitive)

fn apply(f: (int) -> int, x: int) -> int => f(x)   // uses { * } — depends on the closure you pass

The compiler tracks every function's effect row and publishes it in the .fy manifest. Rows compose across modules under separate compilation: a library that reaches print makes every caller's row reflect that, all the way up. Capability security holds across the module boundary — the same idea as Deno's permission flags or a WASM component's imports, made first-class in the type system.

Memory without a garbage collector

Plugfy manages memory by reference counting: deterministic release, no GC pauses, no stop-the-world. Values are freed the moment they become unreachable — predictable behavior for long-running services and tight loops alike.

One IR, three backends

Every .plug lowers to one typed intermediate representation (PIR), which targets three backends — a portable module (.fy), a native executable, and direct script execution. Same semantics, same behavior, everywhere. See Tools for the build model.