Type Mapping
Your TypeScript types become C++ types. Here’s how the common ones line up. You rarely need to think about this — it just works — but the table below is handy when you want to know exactly what’s happening.
Primitive Types
| TypeScript | C++ | Notes |
|---|---|---|
number | int | Default; widened to long if value exceeds int range |
boolean | bool | |
string | const char* | On Zephyr, std::string is normalized to const char* — no heap |
void | void | |
double | double | |
long | long | |
uint8_t | uint8_t | Direct pass-through |
int16_t | int16_t | Direct pass-through |
size_t | size_t | Direct pass-through |
Type Aliases from cuttlefish-env.d.ts
Scaffolded projects include C-style numeric type aliases available in TypeScript:
const count: uint8_t = 42;
const bigValue: int32_t = 100000;These map directly to their C++ equivalents — no conversion needed.
Composite Types
Arrays
Array<T>, ReadonlyArray<T>, and T[] become plain C-style arrays on Zephyr (small and predictable, the way embedded code expects):
// TypeScript
const values: number[] = [1, 2, 3];// C++ (Zephyr)
int values[] = {1, 2, 3};On the native (desktop) target, they become std::vector<T>.
Negative numeric literals inside typed arrays are compile-time-safe and stay as initializers — they are not migrated into setup().
Objects
Object literals become anonymous C structs with generated type names:
// TypeScript
const config = { baud: 9600, parity: 'none' };// C++
struct _config_t { int baud; const char* parity; };
_config_t config = {9600, "none"};Nested objects generate recursively defined structs (deepest first). Top-level object literals used as destructuring sources are emitted as globals.
Enums
// TypeScript
enum PinMode { Input, Output, InputPullup }// C++
enum class PinMode { Input, Output, InputPullup };Collections
| TypeScript | C++ |
|---|---|
Set<T> / ReadonlySet<T> | std::set<T> |
Map<K,V> / ReadonlyMap<K,V> | std::map<K,V> |
Record<K,V> | std::map<K,V> |
Functions
const handler: (pin: number) => void = (pin) => { /* ... */ };// C++
std::function<void(int)> handler = [](int pin) { /* ... */ };On Zephyr, std::function is normalized to C function pointer syntax.
Unions
Union types map to std::variant:
type Result = string | number;// C++
std::variant<std::string, int> result;Union with null or undefined strips the null side:
type MaybeNumber = number | null;
// C++: int (null handled via cuttlefish_nullish)Template Literals
A string with ${...} placeholders becomes a small text buffer filled with the formatted result:
// TypeScript
const msg = `Temperature: ${temp}C`;// C++
char __cuttlefish_str_1[17];
snprintf(__cuttlefish_str_1, sizeof(__cuttlefish_str_1), "Temperature: %dC", temp);The + operator on strings works the same way:
const msg = "Value: " + String(value) + " units";Nullish Coalescing (??)
The ?? operator becomes a safe check that treats 0 and false as real values (so 0 ?? 42 is 0, not 42):
// TypeScript
const value = input ?? 42;// C++
int value = cuttlefish_nullish(input, 42);Optional Chaining (?.)
The ?. operator becomes a safe guard:
// TypeScript
const len = obj?.length;// C++
auto len = cuttlefish_exists(obj) ? obj.length : /* default */;Equality Operators
TypeScript === and !== become C++ == and !=:
// TypeScript
if (value === 42) { /* ... */ }// C++
if (value == 42) { /* ... */ }Ownership Type Erasure
Owned<T>, Shared<T>, and Mutable<T> are unwrapped during type resolution — the inner T is used as the C++ type. Shared<T> adds const in C++ for compiler-enforced read-only access. No wrappers are generated. (See the Ownership docs.)
Classes
new ClassName() instances become value types or pointers depending on context. Cuttlefish generates proper C++ class definitions with constructors matching the TypeScript source.
Forward Declarations
When top-level code calls helper functions defined later in the file, Cuttlefish automatically inserts forward declarations before main(). Default arguments appear on the declaration, not the definition:
// Forward declaration (before main)
int calculateAverage(int a, int b = 0);
int main() { /* ... */ }
// Definition
int calculateAverage(int a, int b) {
return (a + b) / 2;
}Control Flow
for-of
// TypeScript
for (const item of items) { /* ... */ }// C++
for (const auto item : items) { /* ... */ }for-in
When object keys are known at compile time:
const char* _ki_obj_keys[] = { "name", "value" };
for (int _ki_obj = 0; _ki_obj < 2; _ki_obj++) { /* ... */ }try/throw
On Zephyr, throw maps to an infinite halt loop since exceptions are disabled:
for (;;) { k_msleep(1000); }A diagnostic is emitted if try-catch is used on a platform with -fno-exceptions.
Modulo on Float
The % operator on floating-point values is rewritten to fmod():
const remainder = value % 3.14;double remainder = fmod(value, 3.14);⚙️ Advanced details — framework overrides and formatting internals
How std::string is handled on no-STL targets
The framework strategy may override the default type mapping. On heap-free targets the strategy normalizes std::string to const char*, so string variables never allocate — and interpolated text is formatted into stack char buffers with snprintf instead of building heap std::string objects.
Format specifier inference
The transpiler automatically picks the right printf format based on the expression type:
| Expression type | Format specifier |
|---|---|
Integer literal / int | %d |
Float literal / float | %g or %.Nf |
| Boolean | %s with "true" / "false" ternary |
| String | %s |
long | %ld |
The cuttlefish_nullish helper
The helper template behind ??:
template<typename T, typename U>
inline T cuttlefish_nullish(const T& a, U b) {
return !cuttlefish_is_nullish(a) ? a : (T)b;
}This preserves 0 as a real value (only a nullish sentinel triggers the fallback).
On This Page