Edit source

Applications and libraries

Tsonic can generate an executable application or a library. The target decides the exact native shape.

TargetApplicationLibrary
C#outputType: "Exe"outputType: "Library"
RustoutputType: "bin"outputType: "lib"

C# applications

A C# executable runs the TypeScript entry module. Put startup work at top level, or call your own function from top level:

import { Console } from "@tsonic/dotnet/System.js";

export function run(): void {
  Console.WriteLine("Hello from C#");
}

run();

Tsonic generates TsonicEntrypoint.Main, initializes imported modules in ESM order, and executes the entry module. An exported function named main is an ordinary function; C# does not call it by name.

Top-level await produces an asynchronous generated Main when the selected runtime contract supports it.

Rust applications

A generated Rust binary requires the entry module to export main with a unit result:

export function main(): void {
  const values = [1, 2, 3];
  if (values.length !== 3) {
    throw new Error("invalid length");
  }
}

Tsonic generates native Rust main, performs required module initialization, then calls the exported function. An async entry may return Promise<void>. Fallible and asynchronous entry contracts are lowered only when analysis proves the complete native path.

Libraries

Libraries export declarations instead of owning a process entrypoint:

import type { int32 } from "@tsonic/core/types.js";

export interface Point {
  x: int32;
  y: int32;
}

export function distanceSquared(point: Point): int32 {
  return point.x * point.x + point.y * point.y;
}

The target emits public native declarations for supported exports. Native consumers reference the generated assembly or crate through the normal native project system.

Avoid top-level side effects in reusable libraries. C# can preserve synchronous entry-module initialization with a CLR module initializer, but an asynchronous library initializer is rejected. Rust preserves only module initialization that its closed initialization plan can represent.

Share code between C# and Rust

Put portable behavior in a source package:

// packages/core/src/index.ts
export function greeting(name: string): string {
  return `Hello, ${name}!`;
}

Use a C# entry module:

import { Console } from "@tsonic/dotnet/System.js";
import { greeting } from "@acme/core/index.js";

Console.WriteLine(greeting("C#"));

Use a Rust entry module:

import { greeting } from "@acme/core/index.js";

export function main(): void {
  if (greeting("Rust") !== "Hello, Rust!") {
    throw new Error("unexpected greeting");
  }
}

Each application gets its own tsonic.json. Both consume the same source package. This keeps target entry rules out of shared code.

Generated or user-owned project

Use a generated project for a normal console application or library. Use a user-owned project when the native project itself is part of the product:

  • ASP.NET Core, desktop, test, or custom-SDK C# projects;
  • Rust workspaces with direct crate dependencies;
  • cross compilation, WebAssembly, embedded, kernel, or bare-metal Rust;
  • custom signing, linker, build-script, packaging, or deployment policy.

The target-specific project guides contain complete examples: