Getting Started

This guide walks you through installing SafeC, compiling your first program, and understanding the basic compilation workflow.

Installation

Run the install script. It downloads a prebuilt safec/safeguard/sc-lsp release from GitHub Releases and configures your shell environment — there is nothing to build. It does need clang 21 or newer on the machine (SafeC hands its LLVM IR to clang to compile and link, and clang imports C headers); Install says how to get one per platform.

bash
curl -fsSL https://raw.githubusercontent.com/safec-org/SafeC/main/install.sh | bash
powershell
irm https://raw.githubusercontent.com/safec-org/SafeC/main/install.ps1 | iex

Install Script Options

Option Description
--prefix=<path> Install directory (default: ~/safec)
--version=<tag> Release tag to install, e.g. v1.0.0-beta-7 (default: the newest, pre-releases included)
--std=<sections> The prebuilt std sections to install, e.g. core,collections,gui (default: all). std's sources are always installed; a section that isn't is compiled from source when a build needs it
--std=+<sections> Add prebuilt std sections to an existing install, from the release it came from
--skip-env Skip shell environment configuration

install.ps1 takes the same as -Prefix, -Version, -Std and -SkipEnv.

Published binaries: macOS arm64, Linux x86_64, Windows x86_64. See Releases for the full asset list. They need:

  • macOS 11 (Big Sur) or newer: the binaries and the prebuilt std are built for 11.0.
  • Linux with glibc 2.25 or newer (Debian 10, Ubuntu 18.04, RHEL 8 and later): the binaries are built against an older glibc and checked for it, and the prebuilt std calls nothing newer than 2.25 (getrandom). Musl-based distributions (Alpine) aren't supported by the release binaries.

TIP

Building from source (needed for a platform the release workflow doesn't publish, or to build from an unreleased commit) is still possible via each component's own CMakeLists.txt under compiler/, safeguard/, and the sibling sc-language-server repo — see Install → From source and Compiler Architecture.

Verify the install:

bash
safec --version       # safec 1.0.0-beta-7
safeguard --version   # safeguard 1.0.0-beta-7

Your First Program

Create a new project with safeguard:

bash
safeguard new hello
cd hello

This generates the following structure:

text
hello/
  .gitignore
  Package.toml
  src/
    main.sc

Replace src/main.sc with:

c
extern int printf(const char* fmt, ...);

int main() {
    printf("Hello from SafeC!\n");
    return 0;
}
Run in Playground

Build and run:

bash
safeguard build
safeguard run

Output:

text
Hello from SafeC!

A More Complete Example

This example demonstrates regions, references, arrays, and slices:

c
extern int printf(const char* fmt, ...);

int main() {
    // Stack variable with explicit region reference
    int x = 42;
    &stack int ref = &x;
    printf("x = %d\n", *ref);

    // Array with bounds checking
    int arr[5];
    arr[0] = 10;
    arr[1] = 20;
    arr[2] = 30;

    // Slice — a bounds-checked view into the array
    []int s = arr[0..3];
    printf("slice length = %ld\n", s.len);

    return 0;
}
Run in Playground

Compilation Pipeline

The SafeC compiler processes source files through a multi-stage pipeline:

text
Source (.sc)
    |
    v
Preprocessor     #define, #include, #ifdef, -D/-I flags
    |
    v
Lexer            Tokenization
    |
    v
Parser           Recursive-descent parsing -> AST
    |
    v
Sema             Type checking, region analysis, borrow checking
    |
    v
ConstEval        Compile-time function evaluation, static_assert
    |
    v
CodeGen          LLVM IR generation
    |
    v
LLVM IR (.ll)    Text IR or bitcode output
    |
    v
clang/lld        Link to native binary

Compiler Flags

text
safec <input.sc> [input2.sc ...] [options]

Every flag safec accepts is below (safec --help also lists LLVM's own options, which SafeC doesn't need).

Output

Flag Description
-o <file> Output file path (default -, standard output). With several inputs and no --emit-bin, a directory that gets one output per input; with several inputs and --emit-bin, the one linked binary
--emit-llvm Emit LLVM IR as text (.ll); without it the output is LLVM bitcode
--emit-bin Compile and link a native binary or library straight to -o, through the system clang (see Linking)
-j <N> Parallel compile jobs for several input files (default: the number of hardware threads)

Optimization and Linking

Flag Description
-O <level> Optimization level: 0 (default), 1, 2, 3, s, z
--release Release profile: -O2 unless -O is given
-l <name> Link against library name (--emit-bin only)
-L <dir> Add a library search directory for -l (--emit-bin only)
--shared With --emit-bin, produce a shared library instead of an executable
--static-lib With --emit-bin, produce a static library (.a, via ar)
--lto[=thin|full] With --emit-bin, link-time optimization (thin when no value is given)

Targets

Flag Description
--target=<alias|triple> Cross-compile: a bare-metal alias (arm-7em, rv32i, wasm32, ...) or any LLVM triple (default: the host)
--target-cpu=<name> Target CPU (default: the triple's generic CPU)
--target-features=<+a,-b> Target features, LLVM -mattr syntax
--target-abi=<name> Target ABI (ilp32e, lp64e, ...)
--freestanding Freestanding mode: no hosted runtime and no C header import (implied by the bare-metal aliases)
--provides=<fn,...> Functions another translation unit defines (e.g. a bare-metal runtime's malloc), so calling them gives no freestanding warning

Debug and Diagnostics

Flag Description
--dump-ast Print the AST and exit
--dump-pp Print preprocessed source and exit
--g lines Emit line-table debug info (DWARF)
--g full Emit full debug info with local variables
-v Verbose output

Compilation Control

Flag Description
--no-sema Skip semantic analysis
--no-consteval Skip compile-time evaluation pass
--compat-preprocessor Enable full C preprocessor compatibility (function-like macros, token pasting, stringification — see Preprocessor)
--no-import-c-headers Disable automatic C header import
--no-strict-aliasing Don't emit type-based alias info, for unsafe code that type-puns through pointer casts (see Strict Aliasing)

Preprocessor

Flag Description
-I <dir> Add include search path
-D NAME[=VALUE] Define a preprocessor macro

Incremental Compilation

Incremental compilation (per-file .bc caching) is on by default — no flag needed to enable it.

Flag Description
--no-incremental Disable incremental compilation (always recompile from scratch)
--cache-dir <dir> Set cache directory (default: .safec_cache)
--clear-cache Clear all cached .bc files and exit

Documentation

What safeguard doc runs to read a package's declarations:

Flag Description
--emit-doc-json Parse the inputs (no semantic analysis or codegen) and write their declarations and doc comments as JSON
--doc-root <dir> Document only files under dir (repeatable); paths in the output are relative to it
--doc-extern <name>=<dir> List declarations under dir as items of the external package name, for links into its reference
--doc-private Include static functions and globals

Information

These need no input file:

Flag Prints
--version The compiler version
--list-targets The bare-metal target aliases with their triples and ABIs
--print-target-clang-flags --target=<t> The clang flags that compile and link IR for that target
--print-clang The clang safec hands its IR to (SAFEC_CLANG overrides it)

C Header Import

SafeC can natively include C standard headers. The compiler parses C header declarations and makes them available without manual extern stubs:

c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    ?&char msg = malloc(64);               // raw void* -> ?&char: implicit
    match (msg) {
        case some(m): {
            strcpy(m, "Hello from C headers!");
            printf("%s\n", m);
        }
        case null: { }
    }
    free(msg);
    return 0;
}
Run in Playground

The automatic import works by invoking clang -ast-dump=json on the included header and extracting function and typedef declarations. Disable it with --no-import-c-headers if you prefer manual extern declarations.

Using the Package Manager

For projects with dependencies or multiple source files, use safeguard. The install script installs it alongside safec and sc-lsp, so it's already available.

bash
# Create a new project
safeguard new myproject
cd myproject

# Build and run
safeguard build
safeguard run

The project structure created by safeguard new:

text
myproject/
  .gitignore
  Package.toml       # Project manifest
  src/
    main.sc          # Entry point

The install script already sets SAFEC_HOME (to the install prefix, ~/safec by default) and adds it to PATH so safeguard can locate the compiler and standard library automatically — restart your shell, or source your shell rc file, after installing. If you skipped that step (--skip-env) or moved the install elsewhere, set it manually:

bash
export SAFEC_HOME=/path/to/safec
export PATH="$SAFEC_HOME/bin:$PATH"

Linking

SafeC compiles to LLVM IR. safec --emit-bin -o hello links it in one step through clang (Linking), and safeguard links a project for you; by hand, the final linking step uses your system's C toolchain:

bash
# Basic linking
clang hello.ll -o hello

# With pthreads (for spawn/join)
clang concurrency.ll -lpthread -o concurrency

# With math library
clang math_demo.ll -lm -o math_demo

Editor Support

An LSP server is available for .sc files, providing:

  • Diagnostics (error highlighting)
  • Hover information
  • Code completion
  • Go-to-definition
  • Document symbols

The server is sc-lsp, which the install script puts on your PATH; Tools has the VS Code, Neovim and Emacs setups (the VS Code extension is in the editors/vscode/ directory of the sc-language-server repository).

Next Steps