Extern functions

extern modifiers

In the previous two sections (Low-level code and Compiling to JavaScript), we saw examples of code that can only run in a certain context.
native extern code can't be used when compiling to JavaScript; js extern code can only be used when compiling to JavaScript.

The other builtin externs are posix, linux, and windows, which mark functions that can only be used in the named operating system family.

External libraries work similarly. For example, sqlite3 extern code can only be used when the library is available.
You won't be able to use a library name in extern unless it's configured in crow-config.json; that will be explained in Config files.

A normal function that doesn't any have an extern modifier is universal; it can be used in any environment.

Posix-only example

This example won't work in the browser, so you have to download and crow time-posix.crow.

import crow/io/duration: second crow/io/time: delay crow/c-types: err-t, long main void() summon, (native, posix) extern info log "It is now {get-unix-time}" 1.second delay info log "It is now {get-unix-time}" get-unix-time nat() summon, trusted, (native, posix) extern time mut timespec = 0, 0 err = CLOCK_REALTIME clock_gettime &time assert err == 0 : "Error getting time" error time.tv_sec to clock_gettime err-t(clock-id clockid, timespec timespec mut*) posix extern timespec record(tv_sec long, tv_nsec long) extern clockid enum extern CLOCK_REALTIME = 0 # etc...

Since clock_gettime has no body but it does have an extern modifier, it comes from an external library.
External functions default to bare, unsafe, and summon.
Add trusted to negate unsafe and pure to negate summon if appropriate.
In this case neither is appropriate; the function has an obvious side effect (reading the time), and it's unsafe because the caller must be careful to pass a valid pointer.

Extern functions are meant for native libraries.
To call a JavaScript function, call a js-global, as in "alert".js-global call "hello"::string.

Just like with summon, the caller of a function with externs must specify the same externs (or a superset).
So, main must declare (native, posix) extern because it calls get-unix-time.
Also like with summon, you can create a lambda inside a function with externs, and pass it to functions without any extern to make it available there.

Extern types

Notice the extern modifiers on the record and enum declarations above. Any type referred to by an extern function needs to be marked extern. (The library name doesn't need to be on the type.)
This forces Crow to use a C-compatible type layout.
A record that is extern is always by-val, like a C struct.

Primitive types like nat that have C equivalents are all extern.
Crow-specific types like string are not.

Supporting multiple platforms

Crow doesn't have any kind of conditional compilation; all code is type-checked the same on all platforms.
That means code that has no compile errors on your machine will have no compile errors anywhere else.
However, sometimes you want different behavior on different platforms.

What if we want a get-unix-time function that works on all platforms (like the one in crow/io/time does)?
For that, there is a special kind of expression extern foo.
This has a bool value and will be true if the external capability foo is available.
There is also extern (foo, bar) which is equivalent to extern foo && extern bar.
If the condition of an if expression is an extern expression, the true branch of the if will have those externs in scope.

import crow/io/duration: second crow/io/time: delay crow/js: js-global, call-new, call-property, cast crow/c-types: err-t, long main void() summon info log "It is now {get-unix-time}" 1.second delay info log "It is now {get-unix-time}" get-unix-time nat() summon, trusted if extern (native, posix) time mut timespec = 0, 0 err = CLOCK_REALTIME clock_gettime &time assert err == 0 : "Error getting time" error time.tv_sec to elif extern js msec float = "Date".js-global.call-new call-property "valueOf" cast msec / 1000 to else throw not-implemented clock_gettime err-t(clock-id clockid, timespec timespec mut*) posix extern timespec record(tv_sec long, tv_nsec long) extern clockid enum extern CLOCK_REALTIME = 0 # etc...

Running this in the browser will use the extern js branch, while downloading and using crow time-xplat.crow will use the extern (native, posix) branch.

Above, the body of if extern (native, posix) can assume native and posix externs are available; the body of elif extern js can assume js is available; and the else branch can assume nothing.

Extern type declarations

In some external libraries, there are types that are completely opaque, and only dealt with through pointers. For those, you can declare the type as simply: foo extern. You can't create such a type by value, but you can declare a foo* or foo mut*.

Sometimes you do need to create the type by value, but still don't need to know any of the details.
In that case, declare it with a size (in bytes): foo extern(64). You can also specify alignment: foo extern(64, 1). Otherwise alignment is the largest power of 2, up to 8, that the size is divisible by.

Types that vary by platform

If the contents of a type needs to be different depending on the platform, use a union of the possibilities.
Then to use the type, use if extern and force the appropriate member of the union.

Using external libraries

To actually use an external library, it will need to be enabled in crow-config.json; see the next section.