Develop with Go

Updated

Create TEN applications using Go language binding, including setup, building, debugging, and CGO integration.

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:

tman create app my_go_app --template default_app_go
cd my_go_app

Install dependencies

Install the required TEN packages and extensions:

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

Install a default TEN extension

To install a default Go TEN extension:

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:

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

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:

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:

./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:

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

C code debugging

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

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

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:

mkdir out
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:

├── _cgo_export.c
├── _cgo_export.h
├── _cgo_flags
├── _cgo_gotypes.go
├── _cgo_main.c
├── _cgo_.o
├── cmd.cgo1.go
├── cmd.cgo2.c
├── error.cgo1.go
└── 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:

    typedef signed char GoInt8;
    typedef unsigned char GoUint8;
    typedef short GoInt16;
    typedef unsigned short GoUint16;
    typedef int GoInt32;
    typedef unsigned int GoUint32;
    typedef long long GoInt64;
    typedef unsigned long long GoUint64;
    typedef GoInt64 GoInt;
    typedef GoUint64 GoUint;
    typedef size_t GoUintptr;
    typedef float GoFloat32;
    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:

    package ten
    
    type _Ctype_ten_go_status_t = _Ctype_struct_ten_go_status_t
    
    type _Ctype_struct_ten_go_status_t struct {
        err_no   _Ctype_int
        msg_size _Ctype_uint8_t
        err_msg  [256]_Ctype_char
        _        [3]byte
    }
  • 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:

    package ten
    
    func NewCmd(cmdName string) (Cmd, error) {
        // ...
        cStatus := func() _Ctype_struct_ten_go_status_t {
            var _cgo0 _cgo_unsafe.Pointer = unsafe.Pointer(unsafe.StringData(cmdName))
            var _cgo1 _Ctype_int = _Ctype_int(len(cmdName))
            _cgoBase2 := &bridge
            _cgo2 := _cgoBase2
            _cgoCheckPointer(_cgoBase2, 0 == 0)
            return _Cfunc_ten_go_cmd_create_custom_cmd(_cgo0, _cgo1, _cgo2)
        }()
        // ...
    }
  • cmd.cgo2.c: C wrapper for the actual C function
    Provides the bridge between Go's calling convention and the native C function:

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

Call sequence

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

_Cfunc_ten_go_cmd_create_custom_cmd (auto-generated Go)
           |
           V
   _cgo_runtime_cgocall (Go)
           |
           V
      entrysyscall     // switch Go stack to C stack
           |
           V
_cgo_cb1b98e39356_Cfunc_ten_go_cmd_create_custom_cmd (auto-generated C)
           |
           V
   ten_go_cmd_create_cmd (C)
           |
           V
      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:

common.h
typedef struct ten_go_msg_t ten_go_msg_t;

Generated incomplete type in Go:

_cgo_gotypes.go
type _Ctype_ten_go_msg_t = _Ctype_struct_ten_go_msg_t
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:

    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
// Correct way to represent a null pointer
var nullPtr C.uintptr_t = 0

// Incorrect - nil cannot be used with uintptr
// var badPtr C.uintptr_t = nil  // Compilation error

Was this page helpful?