Skip to main content

Develop with Go

This guide shows you how to create and build Go applications using the TEN Framework's Go language binding.

This guide shows you how to create and build Go applications using the TEN Framework's Go language binding.

Create a TEN Go application

Create a new TEN application using Go with the following steps:

Generate the application

Use TEN Manager to create a new Go application from the default template:


_2
tman create app my_go_app --template default_app_go
_2
cd my_go_app

Install dependencies

Install the required TEN packages and extensions:


_3
tman install protocol msgpack
_3
tman install extension_group default_extension_group
_3
tman install extension default_extension_go

Install a default TEN extension

To install a default Go TEN extension:


_1
tman install extension default_extension_go

Configure auto-start graph

Configure your application to automatically start the installed extensions by adding a predefined graph to the property.json file:


_19
"predefined_graphs": [
_19
{
_19
"name": "default",
_19
"auto_start": true,
_19
"nodes": [
_19
{
_19
"type": "extension_group",
_19
"name": "default_extension_group",
_19
"addon": "default_extension_group"
_19
},
_19
{
_19
"type": "extension",
_19
"name": "default_extension_go",
_19
"addon": "default_extension_go",
_19
"extension_group": "default_extension_group"
_19
}
_19
]
_19
}
_19
]

Build the application

TEN Go applications require CGO integration, which needs specific environment variables and build configuration. Use the build script provided in the TEN runtime to handle these requirements automatically:


_1
go run ten_packages/system/ten_runtime_go/tools/build/main.go

The build process creates a compiled binary named main in the bin/ directory.

Run the application

Use the provided start script to launch your application with the correct environment variables:


_1
./bin/start

The start script automatically configures the necessary library paths and environment settings for the TEN runtime.

Debug your application

Set up debugging configurations for Visual Studio Code to debug both Go and C code in your TEN application.

Go code debugging

Add this configuration to your .vscode/launch.json file to debug the Go portions of your application:


_19
{
_19
"version": "0.2.0",
_19
"configurations": [
_19
{
_19
"name": "Go app (launch)",
_19
"type": "go",
_19
"request": "launch",
_19
"mode": "auto",
_19
"program": "${workspaceFolder}",
_19
"cwd": "${workspaceFolder}",
_19
"output": "${workspaceFolder}/bin/tmp",
_19
"env": {
_19
"LD_LIBRARY_PATH": "${workspaceFolder}/lib",
_19
"DYLD_LIBRARY_PATH": "${workspaceFolder}/lib",
_19
"CGO_LDFLAGS": "-L${workspaceFolder}/lib -lten_runtime_go -Wl,-rpath,@loader_path/lib"
_19
}
_19
}
_19
]
_19
}

C code debugging

Add this configuration to your .vscode/launch.json file to debug the C/CGO portions of your application:


_17
{
_17
"version": "0.2.0",
_17
"configurations": [
_17
{
_17
"name": "C (launch)",
_17
"type": "lldb",
_17
"request": "launch",
_17
"program": "${workspaceFolder}/bin/main",
_17
"cwd": "${workspaceFolder}",
_17
"env": {
_17
"LD_LIBRARY_PATH": "${workspaceFolder}/lib",
_17
"DYLD_LIBRARY_PATH": "${workspaceFolder}/lib",
_17
"CGO_LDFLAGS": "-L${workspaceFolder}/lib -lten_runtime_go -Wl,-rpath,@loader_path/lib"
_17
}
_17
}
_17
]
_17
}

CGO integration

Understand generated code

The TEN Framework's Go binding relies on CGO to interface between Go and C code. When you call C functions from Go, the cgo tool automatically converts your Go source files into multiple output files containing both Go and C code. These generated C files are then compiled using standard C compilers like gcc or clang.

You can manually generate and examine these intermediate files using:


_2
mkdir out
_2
go tool cgo -objdir out cmd.go error.go

This creates several generated files that bridge Go and C:

The CGO generation process creates several key files that enable Go-C interoperability:


_10
├── _cgo_export.c
_10
├── _cgo_export.h
_10
├── _cgo_flags
_10
├── _cgo_gotypes.go
_10
├── _cgo_main.c
_10
├── _cgo_.o
_10
├── cmd.cgo1.go
_10
├── cmd.cgo2.c
_10
├── error.cgo1.go
_10
└── error.cgo2.c

Key generated files

  • _cgo_export.h: Enables C code to call Go functions
    Contains declarations for Go functions exported with the //export directive, plus C type definitions that correspond to Go types:


    _13
    typedef signed char GoInt8;
    _13
    typedef unsigned char GoUint8;
    _13
    typedef short GoInt16;
    _13
    typedef unsigned short GoUint16;
    _13
    typedef int GoInt32;
    _13
    typedef unsigned int GoUint32;
    _13
    typedef long long GoInt64;
    _13
    typedef unsigned long long GoUint64;
    _13
    typedef GoInt64 GoInt;
    _13
    typedef GoUint64 GoUint;
    _13
    typedef size_t GoUintptr;
    _13
    typedef float GoFloat32;
    _13
    typedef double GoFloat64;

  • _cgo_gotypes.go: Contains Go representations of C types
    When you use C types like C.ten_go_error_t in Go, this file provides the corresponding Go struct definitions:


    _10
    package ten
    _10
    _10
    type _Ctype_ten_go_status_t = _Ctype_struct_ten_go_status_t
    _10
    _10
    type _Ctype_struct_ten_go_status_t struct {
    _10
    err_no _Ctype_int
    _10
    msg_size _Ctype_uint8_t
    _10
    err_msg [256]_Ctype_char
    _10
    _ [3]byte
    _10
    }

  • cmd.cgo1.go: Go wrapper for C function calls
    Replaces direct C function calls with CGO-generated wrappers that handle type conversion and memory management:


    _14
    package ten
    _14
    _14
    func NewCmd(cmdName string) (Cmd, error) {
    _14
    // ...
    _14
    cStatus := func() _Ctype_struct_ten_go_status_t {
    _14
    var _cgo0 _cgo_unsafe.Pointer = unsafe.Pointer(unsafe.StringData(cmdName))
    _14
    var _cgo1 _Ctype_int = _Ctype_int(len(cmdName))
    _14
    _cgoBase2 := &bridge
    _14
    _cgo2 := _cgoBase2
    _14
    _cgoCheckPointer(_cgoBase2, 0 == 0)
    _14
    return _Cfunc_ten_go_cmd_create_custom_cmd(_cgo0, _cgo1, _cgo2)
    _14
    }()
    _14
    // ...
    _14
    }

  • cmd.cgo2.c: C wrapper for the actual C function
    Provides the bridge between Go's calling convention and the native C function:


    _17
    CGO_NO_SANITIZE_THREAD void _cgo_cb1b98e39356_Cfunc_ten_go_cmd_create_custom_cmd(void *v) {
    _17
    struct {
    _17
    const void* p0;
    _17
    int p1;
    _17
    char __pad12[4];
    _17
    ten_go_msg_t** p2;
    _17
    ten_go_error_t r;
    _17
    } __attribute__((__packed__, __gcc_struct__)) *_cgo_a = v;
    _17
    char *_cgo_stktop = _cgo_topofstack();
    _17
    __typeof__(_cgo_a->r) _cgo_r;
    _17
    _cgo_tsan_acquire();
    _17
    _cgo_r = ten_go_cmd_create_cmd(_cgo_a->p0, _cgo_a->p1, _cgo_a->p2);
    _17
    _cgo_tsan_release();
    _17
    _cgo_a = (void*)((char*)_cgo_a + (_cgo_topofstack() - _cgo_stktop));
    _17
    _cgo_a->r = _cgo_r;
    _17
    _cgo_msan_write(&_cgo_a->r, sizeof(_cgo_a->r));
    _17
    }

Call sequence

When calling C.ten_go_cmd_create_cmd() from Go, the execution follows this path:


_16
_Cfunc_ten_go_cmd_create_custom_cmd (auto-generated Go)
_16
|
_16
V
_16
_cgo_runtime_cgocall (Go)
_16
|
_16
V
_16
entrysyscall // switch Go stack to C stack
_16
|
_16
V
_16
_cgo_cb1b98e39356_Cfunc_ten_go_cmd_create_custom_cmd (auto-generated C)
_16
|
_16
V
_16
ten_go_cmd_create_cmd (C)
_16
|
_16
V
_16
exitsyscall

Work with incomplete types

The cgo tool generates corresponding Go types based on C header files imported via import "C". When a C struct is opaque (only declared, not defined), CGO creates an incomplete type in Go.

Example of an opaque C struct:


_1
typedef struct ten_go_msg_t ten_go_msg_t;

Generated incomplete type in Go:


_2
type _Ctype_ten_go_msg_t = _Ctype_struct_ten_go_msg_t
_2
type _Ctype_struct_ten_go_msg_t _cgopackage.Incomplete

Incomplete types have the following limitations:

  • Cannot be allocated directly
    Go's garbage collector cannot manage incomplete types, so standard allocation fails:


    _1
    msg := new(C.ten_go_msg_t) // Error: can't be allocated in Go; it is incomplete (or unallocatable)

  • Cannot pass pointers directly to C
    CGO rules require pointers to incomplete types to be "pinned" for garbage collector safety, making direct pointer passing impossible.

Workaround: Use C.uintptr_t

Instead of working with pointers to incomplete types, use C.uintptr_t to represent C pointers as integers.

This approach has the following benefits:

  • No allocation overhead: uintptr values can be passed directly between Go and C
  • GC-safe: No pointer tracking required since it's an integer value
  • Direct conversion: Represents the exact memory address as an integer

Important limitations

  • No pointer arithmetic: uintptr is just an integer representing an address
  • Cannot dereference: Converting to unsafe.Pointer can break GC assumptions
  • Use 0 for null: Since uintptr is an integer, use 0 instead of nil for null pointers

_5
// Correct way to represent a null pointer
_5
var nullPtr C.uintptr_t = 0
_5
_5
// Incorrect - nil cannot be used with uintptr
_5
// var badPtr C.uintptr_t = nil // Compilation error