# Calling C, and Working with Pointers

This chapter is the one place in the book where we take the safety rails off. Julia can call C and Fortran libraries with no wrapper code, no glue layer, and no overhead — and it can hand out raw pointers to its own memory. That's a genuine superpower for a language this high-level, and it's also the corner where you can crash your process.

I'll be clear about the split: **almost every Julia programmer uses `ccall` occasionally and raw pointers almost never.** Read this chapter so you can understand the code you'll encounter, and so you know the escape hatch is there. Then go back to writing ordinary Julia.

## Why this exists at all

Decades of numerical computing are written in C and Fortran. BLAS, LAPACK, FFTW, HDF5, SQLite, libcurl — the foundations of scientific computing are compiled libraries that nobody is going to rewrite. Julia's approach isn't to reimplement them or generate binding code; it's to call them directly, as a language feature.

Here's the entire mechanism:

```{code-block} julia
julia> ccall(:clock, Int32, ())
1234567
```

That called the C standard library's `clock()` function. No header parsing, no build step, no `.so` shim.

## The anatomy of `ccall`

```{code-block} julia
@ccall library.function_name(arg1::ArgType1, arg2::ArgType2)::ReturnType
```

The modern `@ccall` macro reads much better than the older four-argument `ccall` form you'll see in existing code. An example with actual arguments:

```{code-block} julia
julia> @ccall sqrt(16.0::Cdouble)::Cdouble
4.0
```

Every argument carries a **C type**, and Julia handles the conversion. The C-compatible types are named to match:

| Julia | C |
|---|---|
| `Cint`, `Cuint` | `int`, `unsigned int` |
| `Clong`, `Culong` | `long`, `unsigned long` |
| `Cfloat`, `Cdouble` | `float`, `double` |
| `Cchar` | `char` |
| `Cstring` | `char*` (null-terminated) |
| `Cvoid` | `void` |
| `Ptr{T}` | `T*` |

Use these names rather than `Int32` directly. `Cint` is `Int32` on every platform you're likely to meet, but on the one where it isn't, your code still works.

### Calling into a named library

```{code-block} julia
# Get the size of a file using libc's stat, conceptually:
@ccall strlen("hello world"::Cstring)::Csize_t     # 11
```

For a library other than libc, name it:

```{code-block} julia
@ccall "libm".cos(1.0::Cdouble)::Cdouble
```

In real packages, the library is provided by a **JLL package** — a Julia package that ships pre-built binaries for every platform, produced by the BinaryBuilder system. If you want to wrap a C library for the ecosystem, you don't ask users to install it; you build a JLL and depend on it. That's why installing a Julia package that wraps a huge C library "just works" on Windows, macOS, and Linux without a compiler.

## Passing arrays to C

Julia arrays are contiguous blocks of memory with a C-compatible layout, so passing one to a C function costs nothing:

```{code-block} julia
x = [1.0, 2.0, 3.0, 4.0]

# BLAS's dnrm2 computes the Euclidean norm
n = @ccall "libopenblas".dnrm2_64_(
    length(x)::Ref{Int64},
    x::Ptr{Float64},
    1::Ref{Int64}
)::Float64
```

The array is passed as a pointer to its first element. No copy, no marshalling.

:::{warning} The garbage collector doesn't know about C
This is the one genuinely dangerous thing in this chapter.

If you pass a pointer to C and the C function stores it for later, Julia's garbage collector may free that memory while C still holds the pointer. You get a crash, or worse, silent corruption.

For the duration of a single `@ccall`, Julia keeps the arguments alive automatically. If a pointer outlives the call, you must protect it yourself with `GC.@preserve`:

```{code-block} julia
GC.@preserve x begin
    p = pointer(x)
    # ... C holds p during this block ...
end
```

If you never write a C callback that stashes a pointer, you'll never need this. If you do, forgetting it is a Tuesday afternoon you won't get back.
:::

## Pointers

Julia has two pointer types, and the naming tells you everything:

- `Ptr{T}` — a raw, unmanaged address. Unsafe. For talking to C.
- `Ref{T}` — a managed reference the garbage collector knows about. Safe. For output parameters.

`Ref` is what you use when a C function wants to write into a variable you pass:

```{code-block} julia
out = Ref{Cint}(0)
# C function writes into out
# ... @ccall some_c_function(out::Ref{Cint})::Cvoid
out[]                 # read the value back
```

The `[]` dereferences it. That's the same syntax you'd use on a zero-dimensional array, which is essentially what a `Ref` is.

The genuinely unsafe operations are, mercifully, named to tell you so:

```{code-block} julia
unsafe_load(p, i)          # read from a raw address
unsafe_store!(p, v, i)     # write to a raw address
unsafe_wrap(Array, p, n)   # treat a C-allocated block as a Julia array
unsafe_string(p)           # build a String from a char*
```

Julia's naming convention here is a small piece of good design worth noticing: every function that can segfault your process has `unsafe_` in its name. You cannot use one by accident, and a code reviewer can find all of them with grep.

## `Memory`: the newer, lower-level building block

:::{note} New in Julia 1.11
Julia 1.11 introduced `Memory{T}`, a fixed-length, low-level block of memory that now sits underneath `Array`. Before this, `Array` was a special compiler-blessed type; now most of `Array`'s implementation is ordinary Julia code built on `Memory`.

You will rarely use `Memory` directly. It matters for two reasons: it's another step in Julia's project of implementing the language in itself, and if you're reading array internals in the standard library, you'll now see `Memory` at the bottom of the stack rather than compiler magic.
:::

## A complete, realistic example

Let's wrap a real C function properly — the shape of code you'd actually ship.

```{code-block} julia
"""
    system_memory_page_size()

Return the operating system's memory page size in bytes.
"""
function system_memory_page_size()
    if Sys.isunix()
        return Int(@ccall getpagesize()::Cint)
    else
        error("not implemented on this platform")
    end
end
```

```{code-block} julia
julia> system_memory_page_size()
4096
```

Three things that make this production-quality rather than a demo:

1. **It's wrapped in a Julia function** with a Julia-friendly signature and a docstring. Callers never see `@ccall`.
2. **It converts at the boundary** — `Int(...)` turns the `Cint` into an ordinary Julia `Int` so nothing downstream has to think about C types.
3. **It handles the platform** rather than crashing mysteriously on Windows.

That's the pattern for every C wrapper: a thin, safe Julia surface over the unsafe call, with conversion at the edge.

## Compiling Julia into a shareable binary

The other direction is worth knowing about, because it's the answer to "can I actually deploy this?"

Historically, shipping Julia meant shipping the whole runtime — hundreds of megabytes. Two developments changed that:

- **`PackageCompiler.jl`** builds a custom system image or an app bundle with your package baked in, removing the compile latency at startup.
- **`--trim`** (experimental, Julia 1.12) strips code that isn't statically reachable from your entry point. In the Julia team's testing, a small program went from **206 MB to 1.6 MB**. It's driven through `JuliaC.jl`, which gives you a CLI.

The catch on `--trim` is real: your code must have no dynamic dispatch reachable from the entry point, or trimming isn't provably safe and compilation errors out. In practice that means writing type-stable code, which — conveniently — is exactly what the next chapter is about.

I want to flag why this matters to me personally. The thing that defeated me during the pandemic wasn't writing the model; it was getting it onto hardware without a rewrite. A Julia binary you can ship is the last piece of that puzzle. It's experimental today and I wouldn't bet a production deployment on it yet, but the direction is unmistakable.

## Try it yourself

1. Use `@ccall` to call `getpid()` and print your Julia process's ID. Check it against your operating system's process list.
2. Call `strlen` on a Julia string and confirm the result matches `length`. Then try it with a string containing an emoji, and explain the difference to yourself.
3. Create a `Ref{Cint}`, set a value, read it back with `[]`.
4. Look up a JLL package on juliahub.com (try `Zlib_jll`), add it, and see what artifacts it ships.

### Solutions

```{code-block} julia
# 1.
@ccall getpid()::Cint

# 2.
s = "hello"
@ccall strlen(s::Cstring)::Csize_t      # 5, same as length(s)

s2 = "hi🔥"
length(s2)                              # 3 characters
@ccall strlen(s2::Cstring)::Csize_t     # 6 bytes — C counts bytes, Julia counts characters

# 3.
r = Ref{Cint}(42)
r[]                                     # 42
r[] = 7
r[]                                     # 7
```

Exercise 2 is a nice little lesson in why `Cstring` conversions need care.

You've now seen the bottom of the language. Next we come back up a level to something that affects code you write every day: mutation, copies, and who owns what.
