Foreign Function Interface (FFI)
Introduction
Chicory is designed to be a safe and maintainable language, but it also recognizes the need to interact with the vast ecosystem of existing JavaScript libraries and browser APIs. The Foreign Function Interface (FFI) is the mechanism that allows Chicory code to call JavaScript functions and access JavaScript objects in a type-safe way.
By defining clear type signatures for external JavaScript code, Chicory can ensure that these interactions uphold its safety guarantees, bridging Chicory’s statically-typed world with the dynamic nature of JavaScript.
Binding to Global Variables
Often, you’ll need to interact with objects available in the global JavaScript scope, such as window
, document
, or console
in a browser environment. Chicory uses the global
keyword for this.
You declare the global variable and provide a type signature for the parts of it you intend to use, typically using a record type.
Syntax:
global <globalName> as <TypeSignature>
Example:
To safely use console.log
from Chicory, you can define its type:
global console as {
log: (string) => void, // Log a string (console.log will only accept a single string if typed like this)
warn: (T) => void // Log any value (using generics)
}
// Now you can call it:
console.log("Hello from Chicory!")
console.warn(123)
console.warn("also accepts strings")
In this example:
console
is the name of the global JavaScript object.- The
as
keyword is followed by a record type defining the expected shape ofconsole
. log: (string) => void
specifies thatconsole
has alog
method which takes astring
argument and returns nothing (void
).warn: (T) => void
shows a generic function, indicatingconsole.warn
can take an argument of any typeT
.
Binding to External Modules
To use functions or values exported from JavaScript modules (e.g., NPM packages), Chicory provides the bind ... from
syntax. This is similar to ES6 imports.
Syntax:
bind { <exportName> as <TypeSignature>, ... } from "<module-path>"
You can bind multiple exports from the same module within a single bind
block.
Example:
Let’s say you want to use the useState
hook from React:
bind {
useState as (T) => [T, (T) => void]
} from "react"
// This makes `useState` available in your Chicory code
// with the specified type signature.
// const [count, setCount] = useState(0)
In this example:
useState
is the name of the function being imported from the “preact/hooks” module.as (T) => [T, (T) => void]
defines the type ofuseState
.- It’s a generic function taking an
initialState
of typeT
. - It returns a tuple:
- The current state, also of type
T
. - A setter function that takes a
newState
of typeT
and returnsvoid
.
- The current state, also of type
- It’s a generic function taking an
from "react"
specifies the module from which to import.