Skip to main content
Forst transpiles to Go. You keep the same module graph, deployment, and ecosystem. See Language overview for syntax. This page covers import paths, Go call lowering, and mixed modules.

Transpile to idiomatic Go

Every .ft file compiles to Go source in your module. The output is an ordinary, readable Go source file. Struct fields, error returns, and control flow follow familiar Go patterns. Shape fields lower to unexported Go identifiers by default. For JSON marshaling (sidecar invoke, HTTP handlers), enable -export-struct-fields or compiler.exportStructFields in ftconfig.json to emit exported fields with json tags. Compile a .ft file to Go:
Run the result with the standard toolchain:

Aligned with Go

Much of Forst mirrors Go syntax: package, import, func, if/for/range, defer, go goroutines. A minimal program can be identical in Forst and Go. See Language overview. Forst adds structural typing, constraints, ensure, and nominal errors on top of that base.

Import Go packages

Call into the standard library and third-party modules with qualified names:
When an import has no alias, the compiler uses the Go package name from go/packages (or the last path segment as a fallback before load). That local name must be a valid Forst identifier and cannot be a Forst or Go keyword. Add an explicit alias when the default would be invalid, for example import typePkg "fmt" instead of relying on a path segment named type. The alias node is allowed on Go imports (import node "fmt") even though node is reserved for TypeScript opt-in imports. The compiler loads Go packages via go/packages and type-checks Forst↔Go calls when imports resolve. Supported mappings include primitives, slices, pointers, error, interface{} (including variadic calls), and Forst func literals at Go func parameters. Calls with several return values become a Tuple when captured in one variable. Split the values and use ensure !err or err when your function should expose an exclusive Result.
Keeping Result and Tuple separate prevents useful values from being ignored and keeps success checks reliable. Read why the types stay separate for the common pitfalls and the rules Forst uses to avoid them.

Slice subslices

Forst slice values ([]T) support three-index subslice syntax, lowered to Go slice expressions:
  • xs[low:high] — bounded range
  • xs[low:] — from index through end
  • xs[:high] — from start through index
Given a slice, subslicing looks like this:
See examples/in/slices.ft. Subslices are a language feature on Forst slice values. They are distinct from variadic spread at Go call sites (next section).

Variadic spread into Go calls

When a Go function takes a variadic parameter, spread a Forst slice subslice at the call site with expr[low:].... The compiler lowers this to a Go variadic argument list.
See examples/in/go_interop/cli.ft and helpers.go.

Fields and methods on Go values

After a Go call or a local binding typed from Go, use field access and methods with dotted paths:
  • cmd.Run() — method on a *exec.Cmd binding
  • cmd.ProcessState.ExitCode() — field then method
Qualified import calls such as exec.Command(...) are checked when the stdlib package loads from your module workspace (go.mod walk-up sets GoWorkspaceDir).

Go function callbacks

Pass a Forst function literal where Go expects a concrete func type—for example http.HandleFunc. See examples/in/go_interop/http_handle.ft.

Forst packages in the same module

An import "module/path" can refer to another Forst package in the same Go module—not only hand-written Go. The compiler resolves these as Forst siblings from .ft sources and the module import map (modulecheck). Cross-package calls type-check against the sibling package’s Forst signatures. This matters for Providers cross-package wiring: api can call auth.LogEvent without a committed Go stub. forst test emits ephemeral lib shims under .forst/gen/test/; the LSP uses the same module-level pass so editor diagnostics match compile-time arity. See the cross-package provider example examples/in/rfc/providers/cross_pkg/ (auth + api). Do not confuse sibling Forst imports with Go package imports: import "fmt" loads Go via go/packages; import "yourmod/auth" loads a Forst package when auth/ contains .ft files declaring package auth.

Mixed packages

Place .ft files alongside .go in the same module:
Use -root to merge same-package .ft files that share one directory under a tree, matching discovery for forst run, forst build, and the dev server:
Each directory holds one Forst package name, matching Go. The same package name in sibling directories is rejected. Put every Forst package in its own directory when you split files. The typechecker walks up from your .ft file to find go.mod (GoWorkspaceDir). Exported funcs from co-located .go files resolve without an import line (same package). Example: examples/in/go_interop/helpers.go called from custom.ft / cli.ft.

Project layout profiles

Forst supports three common Go module layouts. Pick one and stay consistent within a project. forst run transpiles your entry, writes a temp sandbox under .forst/run/ with its own go.mod, and executes via go run. Go-native projects (root go.mod, not only .forst-gomod/) use Mode B: an auto-managed .forst/go.work workspace instead of a fragile replace in the sandbox. Node-primary projects keep Mode A (replace forst => … in the sandbox go.mod). For mixed .ft + hand-written .go you want to commit or inspect, use forst generate with generate.go.entry / generate.go.out (or --go-entry / --go-out) and the normal Go toolchain. Examples: examples/in/go_interop/ (Go-native), examples/in/rfc/bridge-interop/remix-serve/ (Node-primary with .forst-gomod/go.mod).

Runtime validation

Type constraints in Forst emit runtime checks in generated Go. Untrusted input is validated at the boundary before your handler body executes. See Shapes and constraints.

Providers

Forst Providers (use / with) declare shared runtime services at function boundaries and wire implementations at entry points. See Providers (DI). See the provider examples on GitHub:

When to keep hand written Go

Forst supports gradual adoption. In some cases, you may need to keep hand-written .go when the compiler cannot yet type-check or lower a call site, or when your workflow needs committed Go next to generated output.
  • Operations that require unsafe or pointer arithmetic
  • Generic Go functions such as slices.Contains or maps.Clone
  • Go interfaces such as io.Reader or http.Handler
  • Same package .go helpers when running with forst run
  • Large existing packages or hot paths already tested in Go
See below for details.

Caveats

Go package importing is experimental. The compiler loads packages through go/packages and checks types when imports resolve. Many standard library paths work well. Full support for every Go package is still evolving.

Generic Go APIs

Functions with type parameters are rejected. Calls to slices.Contains or maps.Clone produce a go call diagnostic message. Full support requires generic type parameters in Forst.

Unsafe package

Qualified calls to unsafe.* and conversions to unsafe.Pointer are not supported. Put low level pointer code in hand written .go files.

Interface satisfaction

Forst shape types do not satisfy Go interfaces like io.Reader or http.Handler automatically. Write a thin Go adapter or a type with explicit Go methods when a Go function expects an interface parameter.

Opaque structs and unexported fields

Go structs with unexported or embedded fields map to Implicit at the type boundary. You can call methods on tracked values. You cannot construct or inspect those internal fields directly inside Forst.

Function and method values

Passing or storing package functions without calling them directly is limited. Function literals passed as arguments work well, such as http.HandleFunc. Storing function values in variables can lose signature details.

Same package Go files with forst run

The forst run command executes code in a temporary sandbox. It does not copy co-located .go files into that sandbox. Use forst generate along with go run or go test when mixing .ft and .go files in the same package.

Builtin make and new calls

Use Forst type syntax with make and new. Write make(Array(Int), 10) or new(Int).

JSON field export

Shape fields lower to unexported Go names by default. Enable exported struct fields in configuration when you need JSON marshaling.

CLI reference

run, build, -root, and compile flags.

Editor workflow

LSP hover for Go imports, godoc, and cross-package Forst siblings.

JavaScript overview

Share types, call Forst from Node, or call JavaScript from Forst.

Call JavaScript from Forst

Call legacy .ts/.js from Forst at runtime.