Skip to content

Directives

Compiler directives (or pragmas) are statements that describe how the compiler should handle source code. Directives are safe to use. It is checked by the compiler and incorrect usage is warned.

For a directive to be valid, it must be used correctly in the right place. The directive must be start with the # prefix.


For example to directives:

jule
#typedef

Top Directives

Top directives are must be placed at top of source file. Usually contains specific compiler configurations for file or package.

Arguments

The arguments of the directives are separated by spaces. The directive must be followed by the required arguments, separated by spaces.


For example:

jule
#foo "my-argument"

Directive Expressions

Some directives evaluate the expression you wrote with a custom syntax, and if the expression returns true as a result, your file will be included in the build. Expression must always return a boolean. Expressions are only logical. So the tags you will use are boolean, and the binary operations you can do are only logical and, and logical or.

While some tags are automatically set by the compiler, others are passed using the --tags option in the compilation command. The tags you can use are the same as in file annotation. They are tags that are also described in the platform support documentation.

WARNING

Evaluation results in false on any syntax errors and empty expressions.

Syntax

The syntax is simple and easy to learn. The logical and operator is && and the logical or operator is ||. These are the same as Jule. The precedence of these operators is also the same. First, the logical or (||) operator is evaluated, then the logical and (&&) operator is evaluated. You also have parentheses. The parentheses, of course, are evaluated first. In addition to these, the logical not (!) operator is also available.

Tags

Here is the list of tags and their existence:

  • windows: operating system is Windows
  • macos: operating system is macOS
  • darwin: operating system is darwin
  • linux: operating system is Linux
  • unix: operating system is UNIX, or UNIX-like
  • i386: cpu architecture is intel 386
  • arm64: cpu architecture is ARM64
  • amd64: cpu architecture is AMD64
  • x32: 32-bit cpu architecture
  • x64: 64-bit cpu architecture
  • production: production compilation enabled
  • test: compiling for testing
  • clang: backend compiler is Clang
  • gcc: backend compiler is GCC
  • cpp20: using C++20 standard

Examples

Here is an example code via build directive:

jule
#build (darwin || windows) && x64
jule
#build unix && !darwin

Directive: pass

Directive pass is a top directive. Passes compiler flags to the generated compile command for compiling source code. Uses a string literal as an argument, but literals are not processed; it accepts it directly. So, you can't use escape sequences like original string literals. Pass directives add to command lines after source files.

In a pass directive, it may be necessary to provide multiple arguments. For example, passing -framework Cocoa as a single argument directly to the back-end compiler may not work as expected. For this reason, you can split the pass directive into separate arguments. Each one will be forwarded to the back-end compiler as an individual argument.

INFO

There are no issue if you are using same passes. The compiler may eliminate duplicate passes.


For example:

jule
#pass "-framework" "Foundation"
#pass "-framework" "Cocoa"

fn main() {
    // ...
}

The #pass directive treats its expression strictly as a single argument. This means that if you try to pass multiple arguments within a single directive, the compiler will not split them for you. For example, according to the case above, #pass "-framework Foundation -framework Cocoa" is not the same as using two separate directives. It will be passed to the back-end compiler as a single argument, which can lead to compilation issues.

Directive: build

The build directive is a top directive. Different way of specific programming such as platform specific programming. It can be used with or instead of file annotation. Unlike file annotation, it is a directive, not a naming convention.

Please look at the specific programming section for more information.

Directive: typedef

In external structs, if the structure is a typedef use this will configure code generation correctly. Otherwise, the struct will be treated as a classical structures.

Directive: cdef

In external functions, if the function is a #define, it configures code generation to be compatible.

Directive: test

Declares test function. For more information, read the Writing Tests section.

Directive export

The export directive is an important part of Integrated Jule. Determines how existing definitions are passed to the backend. This way you have a fixed identifier and can provide an API to the backend language for your Jule codes.

For more information, read the API development section.

Directive disable

Disables some default configuration options of the language for specific areas of the source code. Supported by only functions. Supports multiple arguments.

List of arguments:

  • boundary: Disables boundary checking for slice and array index/slice expressions. It also disables nil checking for slices.
  • nilptr: Disables nil pointer dereferencing checking for reference pointer dereferencing, smart pointer dereferencing, and anonymous function call expressions.
  • shift: Disables shift amount checking for << and >> operators. Useful to obtain pure shift expressions for performance critical software.

Directive noinit

Disables default and compiler-generated zero-initialization for global variables. When applied, the variable is allocated in uninitialized memory.

DANGER

Using #noinit is unsafe and intended for low-level or performance-critical programming. Reading from a variable marked with #noinit before manually initializing it results in undefined behavior.

Directive: inline

Suggests to the compiler that the body of the target function should be expanded directly at each call site, replacing the standard function call overhead (such as stack frame allocation and jump instructions) with the function's actual instruction stream.

INFO

In C++, inline also relaxes the One Definition Rule (ODR), allowing the function to be defined across multiple translation units (headers) without causing duplicate symbol linker errors. Modern compilers treat inline as a hint rather than a command and may ignore it if inlining is suboptimal.

Directive: noinline

Explicitly instructs the compiler never to inline the marked function, forcing it to remain a standalone call site regardless of optimization levels.

Directive future

Declares an async function as future function.

Directive ready

Declares a function as ready function, suitable for the future returns.