Declarations and packages
Declarations define the package-level and type-level shape of a G# program. This guide summarizes the current parser and binder behavior; the full EBNF is in the language specification.
Packages
A file may begin with a package declaration:
package Company.Product.Feature
The compiler supports multi-file and multi-package builds in the emit path. Top-level statements are allowed, but mixing top-level statements with an explicit Main entry point is diagnosed.
Imports and aliases
Imports bring packages or CLR namespaces into scope. Use an alias when two imports would otherwise compete or when a long CLR namespace needs a local name.
import System
import Text = System.Text
The compiler can add an implicit System import by default. Use /noimplicitimports when documenting or testing examples that should show every dependency explicitly.
A non-alias import whose full path names a type also acts like C# using static: the type's shared / static members are available as an unqualified fallback. Namespace imports and aliases do not hoist statics, and ambiguous static imports are diagnosed at the use site.
import System.Math
let hypotenuse = Sqrt(3.0 * 3.0 + 4.0 * 4.0)
Visibility
G# accepts public, internal, and private in the grammar positions where declarations support accessibility. Defaults are context-specific. In guides, prefer explicit visibility for public API examples and omit it for local or private examples. A top-level private func is emitted as assembly-internal so sibling types in the same assembly can call it, but it is not exported to consumers; private members inside user types remain true CLR private members.
Variables and constants
Use const, let, and var declarations at package scope or statement scope. let and const require initializers; var can be initialized or declared with a type for the default value.
const answer = 42
let greeting = "hello"
var count int32
var total = 0
Deconstruction is available in let forms, and multi-target assignment supports identifier lists.
let (x, y) = pair
let { Name = n, Age = a } = person
left, right = right, left
Functions and methods
A function declaration starts with func. async func declares an async function. Receiver clauses attach behavior to a receiver type and are the canonical extension-function style for types this package does not own (imported CLR types, BCL primitives, types from referenced packages); methods on owned classes should be declared inside the class body. The same-package receiver-clause form emits the soft GS0314 warning.
func Add(x int32, y int32) int32 {
return x + y
}
func Square(x int32) int32 -> x * x
class Point {
var X int32
var Y int32
func LengthSquared() int32 {
return X * X + Y * Y
}
}
// Extension on a type this package does not own (a BCL primitive):
func (value int32) Abs() int32 {
if value < 0 { return -value }
return value
}
Generic functions use bracketed type parameters and bracketed type arguments.
Parameters may carry a ref-kind modifier (ref, out, in, or scoped) and may declare a compile-time-constant default value to become optional. Two functions sharing a name are overloads when they differ by parameter types, arity, or ref-kinds; differing by return type alone is not a distinguishing signature. See the feature matrix for the full capability table.
func greet(name string = "world", excited bool = false) string {
return excited ? "hi, $name!" : "hi, $name"
}
// Overloads — differ by arity.
func area(width int32, height int32) int32 { return width * height }
func area(side int32) int32 { return side * side }
// Ref-kind parameters.
func swap[T](ref a T, ref b T) {
let t = a
a = b
b = t
}
func tryParse(s string, out value int32) bool {
value = 0
if Int32.TryParse(s, out value) { return true }
return false
}
A function can declare a managed-pointer return with ref before the return type clause — func at(arr []int32, i int32) ref int32 { return ref arr[i] }. Diagnostics GS0248–GS0255 cover the supporting rules.
Expression-bodied members use the G# arrow ->, not C# =>. The form is available for free functions, methods, read-only properties, accessors, indexers, operators, and conversion operators; constructors, finalizers, and local functions keep block bodies.
A named delegate type is a top-level type alias whose RHS is delegate func(...):
type Handler = delegate func(sender Object, e EventArgs)
Named delegates emit as real CLR MulticastDelegate-derived types so C# consumers see a conventional handler type and G# events can carry first-class custom delegate types.
Type declarations
The aggregate keyword (class, struct, enum, interface) is the declaration head. data adds structural synthesis (equality, with-copy, deconstruction). inline struct declares a single-field value wrapper. partial class, partial struct, and partial interface split one type across files or generated sources; every duplicate part must carry partial, and partial enums are not supported. sealed class / sealed interface declare Kotlin-style closed hierarchies. Payload-bearing enums (enum Shape { Circle(r float64); Square(s float64) }) are discriminated unions. The type keyword is retained for aliases (type Count = int32) and named delegates (type Greeter = delegate func(name string)).
data struct Point {
X int32
Y int32
}
open class Service(name string) {
prop Name string { get; }
}
enum Result { Ok, Failed }
Classes can have primary constructors, explicit init constructors, base clauses, fields, methods, properties, events, and shared static members. Base classes must be open to derive from; overriding uses override.
Classes and structs can declare nested class, struct, interface, or enum types. User-declared nested types currently resolve by simple name in the compilation; Outer.Inner qualification is not added for user types.
A primary-constructor parameter list accepts a trailing variadic name ...T parameter (class, struct, data class, data struct, inline struct). The variadic param promotes to a []T auto-field with the same name and call binding follows the standard variadic pack / pass-through rules.
class Tags(name string, tags ...string) { }
let t = Tags("project", "a", "b", "c") // t.tags is []string{"a", "b", "c"}
Properties, events, and static members
Properties use contextual prop and may have accessors. Events use contextual event and may declare add, remove, and raise accessors. Static members live in a contextual shared block.
class Counter {
shared {
var Created int32
}
prop Value int32 { get; }
event Changed () -> void
}
Indexers are properties named this[...] and lower to CLR Item default members:
class Buffer {
private var data []int32 = [4]int32
prop this[i int32] int32 {
get -> data[i]
set -> data[i] = value
}
}
Static initialization logic lives in shared { init { ... } }. Static field initializers run first; one or more init blocks are concatenated in source order into the type initializer and run once before first static access.
class Tables {
shared {
private let Values []int32 = [4]int32
init {
Values[0] = 1
}
}
}
User-defined conversion operators are declared as static operator functions on owned structs. Implicit conversions apply in target-typed contexts; explicit conversions use the target-type call form.
struct Bytes {
var Value []uint8
}
func operator implicit (b Bytes) []uint8 -> b.Value
func operator explicit (value []uint8) Bytes -> Bytes{Value: value}
Annotations
Annotations start with @ and may include a use-site target such as @field: or @return:. They are part of declaration syntax and map toward CLR metadata attributes. A function annotated with @DllImport("libname", ...) whose body is a single ; is bound as a P/Invoke stub; see CLR interop > Unmanaged interop (P/Invoke).