Go Platform

A minimal go platform.

Full Code

main.roc:

app [main] { pf: platform "platform/main.roc" }

# Can segfault on some Ubuntu 20.04 CI machines, see #164.
main = "Roc <3 Go!\n"

platform/main.roc:

platform "go-platform"
    requires {} { main : Str }
    exposes []
    packages {}
    imports []
    provides [mainForHost]

mainForHost : Str
mainForHost = main

platform/go.mod:

module roc-with-go

go 1.21.5

platform/main.go:

package main

/*
#cgo LDFLAGS: -L. -lapp
#include "./host.h"
*/
import "C"

import (
	"fmt"
	"os"
	"unsafe"
)

func main() {
	var str C.struct_RocStr
	C.roc__mainForHost_1_exposed_generic(&str)
	fmt.Print(rocStrRead(str))
}

const is64Bit = uint64(^uintptr(0)) == ^uint64(0)

func rocStrRead(rocStr C.struct_RocStr) string {
	if int(rocStr.capacity) < 0 {
		// Small string
		ptr := (*byte)(unsafe.Pointer(&rocStr))

		byteLen := 12
		if is64Bit {
			byteLen = 24
		}

		shortStr := unsafe.String(ptr, byteLen)
		len := shortStr[byteLen-1] ^ 128
		return shortStr[:len]
	}

	// Remove the bit for seamless string
	len := (uint(rocStr.len) << 1) >> 1
	ptr := (*byte)(unsafe.Pointer(rocStr.bytes))
	return unsafe.String(ptr, len)
}

//export roc_alloc
func roc_alloc(size C.ulong, alignment int) unsafe.Pointer {
	return C.malloc(size)
}

//export roc_realloc
func roc_realloc(ptr unsafe.Pointer, newSize, _ C.ulong, alignment int) unsafe.Pointer {
	return C.realloc(ptr, newSize)
}

//export roc_dealloc
func roc_dealloc(ptr unsafe.Pointer, alignment int) {
	C.free(ptr)
}

//export roc_dbg
func roc_dbg(loc *C.struct_RocStr, msg *C.struct_RocStr, src *C.struct_RocStr) {
	locStr := rocStrRead(*loc)
	msgStr := rocStrRead(*msg)
	srcStr := rocStrRead(*src)

	if srcStr == msgStr {
		fmt.Fprintf(os.Stderr, "[%s] {%s}\n", locStr, msgStr)
	} else {
		fmt.Fprintf(os.Stderr, "[%s] {%s} = {%s}\n", locStr, srcStr, msgStr)
	}
}

platform/host.h:

#include <stdlib.h>

struct RocStr {
    char* bytes;
    size_t len;
    size_t capacity;
};

extern void roc__mainForHost_1_exposed_generic(const struct RocStr *data);

Build Instructions

The Roc compiler can't build a go platform by itself so we have to execute some commands manually. In the future there will be a standardized way for a platform to define the build process.

  1. Let's make sure we're in the right directory:
$ cd examples/GoPlatform
  1. We turn our Roc app into a library so go can use it:
$ roc build --lib main.roc --output platform/libapp.so
  1. We build a go package using the platform directory. pie is used to create a Position Independent Executable. Roc expects a file called dynhost so that's what we'll provide.
$ go build -C platform -buildmode=pie -o dynhost
  1. We use the subcommand preprocess-host to make the surgical linker preprocessor generate .rh and .rm files.
$ roc preprocess-host platform/dynhost platform/main.roc platform/libapp.so
  1. With our platform built we can run our app:
$ roc main.roc

Publish the platform

  1. Make sure you've built the platform first.

  2. We'll create a compressed archive of our platform so anyone can use it easily. You can use tar.br for maximal compression or tar.gz if you're in a hurry:

$ roc build --bundle .tar.br platform/main.roc
  1. Put the created tar.br on a server. You can use github releases like we do with basic-cli.

  2. Now you can use the platform from inside a Roc file with:

app [goUsingRocApp] { pf: platform "YOUR_URL" }

When running with a platform from a URL, the --prebuilt-platform flag is not needed.

‼ This build procedure only builds the platform for your kind of operating system and architecture. If you want to support users on all Roc supported operating systems and architectures, you'll need this kind of setup.