Features
Compare l with other languages across execution, memory, language features, and integrated tools.
l
Execution
Ahead-of-time compilation produces native machine code before a program runs, rather than executing bytecode or compiling hot paths at runtime. It matters for predictable startup, native deployment, and optimization before release. Packaging an interpreter inside an executable does not make the program native AOT.
Java · Supported
GraalVM Native Image supplies native AOT. Ordinary javac bytecode compilation alone does not.
Source and details
C# · Supported
.NET Native AOT produces native executables in supported publish configurations.
Compile-time execution runs ordinary user-written functions during compilation to compute values, types, or layouts before the program starts. It moves table generation, validation, and configuration out of runtime startup and lets the compiler check the results early. Folding literal constant expressions, or running a separate code generator, does not execute the program's own functions at compile time; restricting compile-time values to constant expressions is partial support.
C · Not supported
C23 constexpr objects and constant expressions are folded by the compiler, but user functions cannot run at compile time.
Rust · Supported
const fn, const items, and const generics evaluate user code at compile time within const-context restrictions.
Swift · Partial support
@const compile-time values are limited to constant expressions, and macros run compiler plugins over syntax; ordinary Swift functions are not evaluated at compile time.
Cross-compilation produces native code for a different operating system or processor than the build machine. It lets one development or CI environment build for servers, devices, and other targets without running a full compiler on each of them. Distributing portable bytecode is not the same as producing a target-specific native binary.
l · Supported
Target selection supports native code generation for other machines. Bundled linking covers Linux, macOS, and bare-metal ELF; other targets need external tools.
C · Supported
A target-configured GCC cross-compiler emits native code; target headers, libraries, and a linker may be needed.
C++ · Supported
A target-configured GCC C++ toolchain cross-compiles native code with matching target libraries and ABI.
Native AOT supports documented cross-architecture combinations, subject to OS and native toolchain restrictions.
Swift · Supported
Swift cross-compiles with an appropriate, matching Swift SDK and target toolchain, such as the Static Linux SDK.
Web Features
Web output compiles application code into a form that browsers and other web hosts execute directly: either JavaScript that an existing JavaScript runtime runs, or portable WebAssembly instructions for Wasm hosts. It matters for deploying to the web or integrating with web applications without rewriting the program. Full support includes a supported compile-and-link path to application modules in at least one of these forms; object-only emission or an experimental application path is partial. Hosting a Wasm module from another language, or porting only an interpreter to Wasm, does not compile the application's own code for the web.
l · Partial support
The wasm32-unknown-unknown backend emits real relocatable Wasm objects, including aggregate types, but external wasm-ld and host imports are needed and l build has no end-to-end Wasm linker workflow. A JavaScript backend is planned.
Source and details; Toolchain details
Clang plus external wasm-ld compiles and links C into Wasm modules; supply the target runtime/sysroot and host imports as needed. C application logic is not translated into JavaScript.
Clang plus external wasm-ld compiles and links C++ into Wasm modules with suitable runtime support and host imports. There is no JavaScript source backend.
rustc supports wasm32-unknown-unknown and WASI targets; the minimal target lacks many OS-backed standard-library operations. JavaScript binding glue is not JavaScript application output.
Zig · Supported
Zig 0.16 supports wasm32 targets and building Wasm modules; target/ABI choice determines available runtime and host services. Zig has no JavaScript source backend.
Go · Supported
GOARCH=wasm with GOOS=js or wasip1 produces Wasm applications. Browser builds need the matching wasm_exec.js host wrapper, which is not application logic in JavaScript.
Java · Partial support
Experimental Oracle GraalVM 25.1+ Web Image compiles applications to Wasm using --tool:svm-wasm and Binaryen behind a JavaScript launcher. There is no stable Wasm output and no JavaScript application output.
Supported Blazor WebAssembly AOT compiles application code to Wasm with the optional wasm-tools workload and RunAOTCompilation=true; the default IL interpreter alone would not qualify. The application is not translated into JavaScript.
TypeScript · Supported
tsc erases TypeScript types and emits JavaScript for the configured ECMAScript target. It does not compile TypeScript into Wasm; hosting a Wasm module from emitted JavaScript is different.
The official, matching Swift SDK for Wasm compiles and links applications, including a full-language SDK and an optional Embedded subset. It emits Wasm, not JavaScript application source.
Memory
This means ordinary memory management does not depend on a collector periodically tracing reachable objects to reclaim storage. It matters when collection pauses, memory headroom, or runtime size affect a system's design. It does not imply memory safety, free allocation, or zero runtime overhead; ownership, explicit deallocation, and reference counting have their own tradeoffs.
Bare-metal programs can run without a hosted operating system, using the target's own startup and hardware interfaces. This enables firmware, kernels, and tightly constrained devices where a conventional application runtime is unavailable. It is a deployment capability, not a promise that every library or language facility works without supporting runtime code.
no_std enables freestanding programs; target startup, panic handling, and any allocation support must be supplied.
Experimental Embedded Swift supplies a freestanding subset. The tick is for an available configuration, not every Swift library.
Low-level Features
First-class SIMD support gives application code vector types and element-wise operations that compile to the target's single-instruction, multiple-data hardware. It lets numeric, media, and parsing code process several lanes at once without hand-written assembly or per-architecture intrinsics. Full support means portable vector types in the language or standard library; architecture-specific intrinsics, compiler extensions, or experimental and incubating APIs are partial support.
C · Partial support
GCC vector extensions and target intrinsics expose SIMD, but standard C has no portable vector type or operations.
C++ · Partial support
std::simd arrives in C++26; the compared C++23 toolchain relies on GCC vector extensions and target intrinsics.
Rust · Partial support
Stable Rust exposes target intrinsics through core::arch; the portable std::simd module is nightly-only.
Go · Partial support
Go 1.26's simd/archsimd package is experimental, amd64-only, and requires GOEXPERIMENT=simd; the language has no vector types.
The Vector API remains an incubating jdk.incubator.vector module in JDK 26 and is not part of the standard API.
Advanced bit manipulation goes beyond basic bitwise and shift operators to operations such as rotation, population count, leading and trailing zero counts, bit reversal, byte swapping, and explicit bit-width integers or bit-fields. These are building blocks for hashing, compression, protocols, and hardware register access, and they map to single instructions on most targets. Offering only a subset, or only on arbitrary-precision integers, is partial support.
TypeScript · Partial support
JavaScript bitwise operators act on 32-bit integers and Math.clz32 exists, but rotate, population count, and wide fixed-width operations need manual code or BigInt.
Python · Partial support
int.bit_count and bit_length work on arbitrary-precision integers, but there are no rotate, leading-zero, or fixed-width integer types.
Types
Static type checking verifies that expressions and operations satisfy type rules before execution. Catching invalid combinations early makes refactoring safer and moves many failures out of production. Runtime checks and annotations that are not statically enforced do not provide the same assurance. Checking declaration compatibility without generally checking expression bodies is only partial static checking.
PHP · Partial support
PHP checks some declaration compatibility at compile time, but does not generally type-check expression bodies statically. Value type declarations are enforced at runtime.
Generics let functions or data types take user-defined type parameters, including template and compile-time type parameters. They make it possible to write reusable containers and algorithms without duplicating implementations for every element type or discarding type information.
Python · Supported
typing supports declared generic parameters. CPython does not statically enforce their use; generic declarations and static checking are separate rows.
Local inference derives a variable's static type from its initializer and surrounding constraints instead of requiring an explicit annotation at every declaration. It reduces repetitive type spelling while preserving static checking. This is different from a dynamically typed variable whose values can have unrelated types at runtime.
C23 adds auto inference for local declarations in supporting GCC modes; older C standards do not provide it.
Nominal typing distinguishes types by their declared identity rather than by their structure, so two declarations with identical members remain different types. It prevents accidental interchange of values that merely look alike, such as different units or identifiers with the same representation, and lets a type's name carry meaning and invariants. Structural typing, where compatible shapes are interchangeable, is the contrasting model; a language can use both for different constructs.
Defined types are nominal for identity and method sets, while interface satisfaction is structural by method set.
TypeScript · Not supported
TypeScript types are structural, so identically shaped types are interchangeable. Branding and private members only approximate nominal types.
Classes are nominal types for inheritance and isinstance checks. typing.Protocol adds opt-in structural contracts, and static enforcement is a separate row.
Compile-time reflection lets code inspect types, fields, variants, and other declarations during compilation and act on that information, for example to generate serializers, bindings, or validation without handwritten boilerplate. It keeps derived code in sync with the declarations it describes and avoids runtime reflection costs. Inspecting only syntax, only type-level properties, or only through a separate compiler plugin is partial support; runtime reflection is a different capability.
Templates and type traits inspect type properties at compile time. Member enumeration needs C++26 static reflection, which the compared C++23 toolchain lacks.
Derive and procedural macros see an item's syntax, including field names and written types, but no resolved type information is available at compile time.
Annotation processors inspect declarations and members through javax.lang.model during compilation; general reflection remains a runtime facility.
C# · Partial support
Source generators inspect the Roslyn semantic model at compile time; ordinary System.Reflection is a runtime facility.
keyof, mapped, and conditional types inspect type structure during checking, but types are erased and nothing reaches emitted code.
Swift macros inspect attached declarations' syntax during compilation; Mirror-based reflection is a runtime facility.
Object Oriented Programming Features
Methods associate operations with a type or a receiver value. They keep a type's behavior close to its data model and make its supported operations easier to discover. This organization is useful for data types as well as class-based objects, without requiring an inheritance hierarchy.
A declared interface specifies a named contract that implementations must satisfy, such as a set of required operations. Traits, protocols, and abstract base classes can serve this role. Explicit contracts let callers depend on capabilities instead of concrete implementations and make those requirements checkable; handwritten dispatch tables or incidental matching of operations alone do not establish a declared contract.
Interface-based polymorphism lets the same client code work with different implementations through a shared declared contract. It matters for substituting components, testing with alternative implementations, and extending behavior without rewriting callers. The shared interface defines what can be used even when concrete representation and dispatch strategy differ.
Functional Programming Features
A tagged sum represents one of a defined set of alternatives, each of which can carry its own payload, with type-checked selection of the active alternative. This makes states such as success or failure explicit and helps prevent invalid combinations of fields. Full support allows user-defined alternatives through the language or standard library; a fixed empty-or-value sum is a partial form. A manually maintained tag beside unchecked storage does not qualify.
Nullable<T> supplies a fixed empty-or-value sum. General user-defined tagged sums are absent; arbitrary alternatives need another representation.
A capturing closure combines callable behavior with values or references taken from its surrounding scope. It lets a callback carry the context it needs without globals or a manually threaded context parameter. This is especially useful for deferred work and customized transformations, while making the lifetime and ownership of captured state important.
A tail call returns the result of another call without further work in the caller. Tail-call optimization reuses or eliminates the caller's stack frame instead of retaining it. This can keep recursive processing and chains of delegated calls from consuming a growing call stack, making recursive formulations practical for long-running computations.
GCC can optimize sibling/tail calls with suitable optimization settings and targets; this is not a language guarantee.
GCC can optimize eligible tail calls; destructors, cleanup work, options, and target constraints can prevent it.
Ordinary tail calls may be optimized by the backend, but stable Rust has no general guaranteed tail-call facility.
Zig supports tail-call control for eligible calls; target and calling-convention restrictions still apply.
The .NET JIT can optimize eligible tail calls; support depends on runtime, target, and remaining caller work.
Emitted JavaScript can use qualifying strict-mode proper tail calls in JavaScriptCore. Node/V8 does not provide this support.
Optimized Swift can eliminate eligible tail calls; outstanding ARC or other cleanup can prevent it.
Effects
A static effect system records in a function's type which side effects it may perform, such as raising errors, suspending, performing I/O, or mutating state, and checks those effects at compile time. It lets callers see and constrain what a function can do, and makes pure code distinguishable from effectful code. Fixed, compiler-checked markers for a few effects, such as checked exceptions or async and throws specifiers, are partial support; a full system is general or user-extensible.
async, const fn, and unsafe are fixed, compiler-checked capability markers; there is no general or user-definable effect system.
Zig · Partial support
Error sets and inferred error unions statically track which errors a function can return; other effects are not tracked.
Checked exceptions statically track the throwing effect in method signatures; no other effects are tracked.
async, throws, and typed throws are fixed effect specifiers checked by the compiler; there is no general effect system.
Effect handlers let a program define its own effects, perform them from arbitrary code, and interpret them in an enclosing handler that can resume the computation where the effect was performed. They generalize exceptions, generators, and async into one mechanism, and let libraries such as schedulers and mock implementations be swapped by changing the handler. Exceptions that unwind without resumption and one-shot coroutines are not effect handlers.
Concurrency
Asynchronous programming lets a program start operations that wait on external events, such as I/O or timers, without blocking a thread, and resume where it left off when the result arrives. Language-level support such as async/await or coroutines lets that code read sequentially instead of as nested callbacks. Library-only futures, cooperative coroutines without asynchronous I/O, or runtime-managed blocking without asynchronous syntax are partial support.
C++20 coroutines provide co_await, co_yield, and co_return; the standard library supplies no default executor.
async fn and .await are language features; running futures requires an executor, typically from a library.
std.Io supplies io.async, await, and cancelation as library calls, but Zig 0.16 ships only the thread-backed Io.Threaded implementation and has no language-level async syntax.
Go has no async/await; goroutines, channels, and select express concurrent work with runtime-managed blocking.
Java has no async/await; CompletableFuture and virtual threads provide library and runtime asynchrony.
Fibers supply cooperative coroutines; asynchronous I/O and scheduling need an external event loop library.
Fearless concurrency means the compiler rejects data races on shared memory before the program runs, so concurrent code that compiles cannot corrupt shared state through unsynchronized access. It removes a class of intermittent bugs that runtime detectors can only sample. Runtime race detectors, single-threaded execution models, and synchronization discipline enforced by convention do not provide this guarantee.
Swift 6 language mode enforces Sendable and actor isolation at compile time to prevent data races.
Green threads are lightweight threads scheduled by the language runtime and multiplexed over a smaller number of operating-system threads. They make it practical to have very many concurrent activities, such as one per connection, without the memory and switching costs of OS threads. Tasks that suspend only at explicit await points, or an experimental scheduler, are partial support; OS threads and single-threaded coroutines are not green threads.
Io.Evented implements M:N green threads with stackful coroutines but is experimental and incomplete in Zig 0.16; Io.Threaded uses OS threads.
Python · Not supported
threading uses OS threads and asyncio tasks are explicit coroutines; greenlet-style libraries are third-party.
Swift tasks are runtime-scheduled on a cooperative thread pool but suspend only at explicit await points, not at arbitrary blocking calls.
Safety
Lifetime checking enforces static rules that prevent a reference from outliving the storage it can access. It catches dangling-reference errors before execution and helps make borrowed access safe without relying solely on runtime memory management. Best-effort diagnostic warnings are not the same as an enforced validity rule. Full support covers ordinary borrowed references; checking only specialized scoped or reference-like value families is partial support.
ref safety enforces escape/lifetime rules for scoped and by-reference values. It is not general borrowing analysis for all objects or unsafe pointers.
Compile-time checks cover nonescapable values and safe interoperation. This is not a general lifetime discipline for all values and unsafe pointers.
C interoperation
C interoperability covers three capabilities. C ABI interoperability lets code call or expose functions using compatible calling conventions, data layouts, and linker symbols, giving access to native libraries, operating-system APIs, and components written in other languages. Direct header import preprocesses C headers and exposes supported declarations without manually rewriting their signatures, so bindings do not drift away from the library they describe. Automatic header export generates C declarations for selected C-compatible APIs defined in the language, so native libraries are easier to consume. Full support provides all three in the bundled toolchain; lacking direct header import or automatic header export, or offering only a narrow or experimental FFI, is partial support.
C-compatible scalar and raw-pointer calls work through the native ABI; by-value C aggregates and variadic calls are not supported. @CType maps selected quoted declarations rather than preprocessed headers, and bundle --header emits an l header, not a C header; whole-header import and C header export are planned.
The C compiler implements its target's native calling conventions and data layout, and the preprocessor consumes C headers directly. Header export is the language's own declaration syntax; -aux-info additionally emits prototypes.
C linkage and compatible types support C ABI calls, and compatible C headers can be included directly. The compared toolchain does not generate a C API header; handwritten extern C headers are not automatic export.
extern C declarations support native calls behind unsafe boundary code, but foreign signatures must be written or generated by separate bindgen, and C header export uses separate cbindgen rather than the bundled toolchain.
Extern declarations, C-compatible types, and build-system C translation support native calls and direct header import. The -femit-h option has a documented nonfunctional implementation, so headers are not exported.
cgo calls C through generated bridge code, preprocesses included headers to expose supported declarations, and generates C declarations for exported Go functions in library build modes. It needs an external C compiler and enforces pointer/ABI restrictions.
The Foreign Function and Memory API provides native C ABI calls with explicit layouts, and Native Image generates C headers for exported entry points. CContext consumes header metadata but Java signatures must still be written; automatic binding generation requires separate jextract.
P/Invoke supports C ABI calls with suitable signatures and marshalling, but declarations must be written or separately generated, and the compared .NET compiler does not generate C headers for native exports.
Emitted JavaScript can use experimental node:ffi in Node 26.1+ builds with --experimental-ffi, which is a Node-only FFI subset that needs supplied signatures. tsc does not import C headers or emit them.
ctypes calls C ABI functions through manually declared types and signatures; it does not preprocess C headers, and CPython does not generate C headers from Python functions. External binding generators are excluded.
The Clang importer consumes supported C declarations from headers exposed through modules and supports native calls through the C ABI. Swift 6.3's @c attribute generates C declarations for selected compatible functions and enums.
The optional bundled FFI extension calls C functions using declared C-compatible types. FFI::load parses only a supported subset of C declarations without preprocessing or #include, and no C headers are generated from PHP code.
Integrated tooling
Project build tools coordinate program or module compilation and its dependencies beyond invoking a compiler on one file. A shared build description gives developers and CI a repeatable way to assemble an application, and can avoid rebuilding unchanged work. An individual-file compiler driver alone does not supply that project-level workflow.
Package management fetches reusable code and manages declared dependencies so a program can build on packages outside its own source tree. It makes dependency setup repeatable and reduces manual copying and path configuration. Fetching and locating packages without a managed dependency workflow is partial support; a full workflow can use explicit, content-addressed versions rather than requiring an automatic version solver.
Package fetching and location work, and use links libraries found on a -L search path, but there is no managed or versioned dependency-resolution workflow.
Bundled ensurepip bootstraps pip for dependency installation/resolution. Some OS distributors package ensurepip separately.
A test runner discovers or executes tests and reports their outcomes through a consistent command. It makes regression checks part of the normal development and CI workflow instead of a collection of ad hoc scripts. A bundled test command counts even when a test framework is supplied as a package; source-tree maintenance harnesses alone do not.
API documentation tools derive a browsable or structured description of public declarations from source code. They help users discover available types and operations while reducing the amount of reference material that must be maintained by hand. Structured output such as XML counts, even when rendering a complete documentation website is a separate step.
The documenter emits signature-based HTML/JSON API documentation. Documentation comment text is not yet included.
go doc generates textual API documentation; a rendered documentation website is not required for this row.
The C# compiler emits structured XML documentation. Rendering a documentation website requires another tool.