Skip to content

Go and Hybrid Packages

Register app-specific packages with app.AddPackage. Callers use the same import syntax whether the implementation is Go-only or combines Go and Nubo.

type Package struct {
Source string
Files fs.FS
Funcs map[string]any
}
FieldMeaning
SourceInline Nubo entry source.
FilesFilesystem for an entry file and/or relative helper files.
FuncsGo functions using the reflection bridge’s supported signatures.

The public registration API currently requires @std/... names. Use a name such as @std/myapp/text, not a bare Go module path or an @app prefix. Duplicate registrations and built-in name collisions are rejected.

Inside a host with app := nubo.New():

err := app.AddPackage("@std/myapp/text", nubo.Package{
Funcs: map[string]any{
"upper": strings.ToUpper,
},
})

Import strings in Go and check err. The registered function is a public package member:

import text from "@std/myapp/text"
println(text.upper("hello"))

A Go-only package does not need a package.nubo file.

Use Nubo to define public method names, signatures, defaults, validation, and structs. Keep the necessary host operations in Go.

package main
import (
"fmt"
"log"
"strings"
"github.com/nubolang/nubo"
)
func main() {
app := nubo.New()
err := app.AddPackage("@std/myapp/text", nubo.Package{
Source: `import native from "@native"
fn shout(value: string, suffix: string = "!") string {
return native.upper(value) + suffix
}`,
Funcs: map[string]any{
"upper": strings.ToUpper,
},
})
if err != nil {
log.Fatal(err)
}
result, err := app.ExecString(`import text from "@std/myapp/text"
return text.shout(value: "hello", suffix: "!!")`)
if err != nil {
log.Fatal(err)
}
if result != nil {
fmt.Println(result.Value())
}
}

This returns HELLO!!. Callers see shout; the Go upper binding is accessed through the module’s private @native namespace.

RegistrationEntry
Only FuncsGo package; no source entry.
Nonempty SourceInline string served as virtual package.nubo.
Only Filespackage.nubo must exist in that filesystem.
Both Source and FilesInline entry, with relative helper files from Files.

No disk file is created for inline source. To keep Nubo code in a real file during development, embed its contents:

//go:embed package.nubo
var packageSource string

This requires importing _ "embed". Then pass Source: packageSource. To embed several files, use embed.FS and pass the filesystem rooted at the package entry, using fs.Sub when needed.

@native is available only to the owning registered source package and its relative helpers. Application scripts cannot import that namespace directly. Another source package has its own bindings. Native imports are omitted from normal package exports.

The runtime initializes a source package on its first import and caches successful exports. Failed initialization can be retried. Circular package initialization reports an error instead of waiting forever.

Public exports omit private declarations and interpreter bookkeeping names. Module initialization runs as dependent execution: __entry__ is false. File imports remain file imports; registering a hybrid package does not change their syntax.

For a real standard-library example, see internal/packages/hex in the Nubo repository.