Rust

43 Posts

FFI: Letting C Call Rust

5 minute

Letting C call Rust does not mean exposing ordinary Rust functions directly to C.

C understands C ABI: symbol names, calling conventions, integers, pointers, struct layout, memory ownership, release functions, and error returns. Rust internals such as String, Vec, Result, traits, and panic should not cross the C boundary directly.

A stable model is:

C call
-> extern "C" wrapper
-> validate pointer and length
-> convert to safe Rust types
-> call internal Rust logic
-> convert result to C-compatible return values

This article covers C calling Rust. Rust calling C comes next.

Read More

unsafe: Keep C Risks at the Boundary

4 minute

C developers may first read unsafe as “the place where Rust lets me write C.”

That is too broad.

unsafe does not disable all Rust checks, and it does not turn a block back into C. It permits a small set of operations the compiler cannot prove safe, such as dereferencing raw pointers, calling FFI functions, and accessing mutable global state.

The key question is not whether unsafe can be written. It is how small the unsafe boundary can be.

Read More

Generics: Stop Using void Pointers for Generic Code

5 minute

C has several common ways to write generic code:

  • void * plus size and callbacks
  • macros that generate code for different types
  • duplicated functions for different concrete types

All of them work, but each has a cost. void * loses type information, macros are harder to debug, and duplicated functions drift.

Rust generics solve the same problem: one piece of code can work for multiple types while keeping type checking.

Read More

trait: From Function Pointer Tables to Behavior Abstraction

5 minute

C has no language-level interface, but C projects have always had interfaces.

Driver tables, callback tables, vtables, opaque pointers, and void *ctx plus function pointers all express the same idea: an object can perform a group of operations.

Rust trait also expresses behavior. The difference is that Rust puts “this type must provide these methods” into the type system instead of relying only on struct fields and calling conventions.

This article covers the first use of traits: migrating C function pointer tables into Rust behavior abstraction.

Read More

Enums and Unions

5 minute

In C, enum and union often appear together. The enum says which kind of value is active, and the union reuses the same storage for different payloads:

enum ValueKind {
    VALUE_INT,
    VALUE_FLOAT,
    VALUE_TEXT,
};

struct Value {
    enum ValueKind kind;
    union {
        int i;
        float f;
        const char *text;
    } data;
};

This is a tagged union. It is common and useful, but correctness depends on discipline:

if (value->kind == VALUE_INT) {
    printf("%d\n", value->data.i);
}

If kind is VALUE_TEXT and the code reads data.i, the C type system does not stop it. In Rust, this shape is usually represented with enum, where the tag and the data live in one type.

Read More

Lifetimes: References Cannot Outlive Data

5 minute

When C developers first see lifetimes, it is easy to think they are annotations for “how long an object lives.”

That is not quite right.

Rust lifetimes are not runtime timers, and they are not manual descriptions of real object lifetime. They are static constraints the compiler uses to check reference relationships: a reference cannot outlive the data it points to.

C has the same problem. It is just checked by humans.

Read More

Borrowing: References Are Not Plain Pointers

5 minute

The previous article covered ownership: who owns, who frees.

Most functions do not want to take over a resource. They only want to inspect data temporarily, or mutate it temporarily. In C, this usually means passing a pointer:

void inspect(const uint8_t *buf, size_t len);
void update(uint8_t *buf, size_t len);

In Rust, this is borrowing:

fn inspect(buf: &[u8]) {}
fn update(buf: &mut [u8]) {}

Borrowing is not just “Rust pointer syntax.” It is access permission: temporarily read or write without taking ownership.

This article covers the first engineering meaning of &T and &mut T, without expanding lifetime annotations yet.

Read More

Ownership: Who Owns, Who Frees

5 minute

C code already has ownership.

Whenever memory needs free, a file needs fclose, or a handle needs release, there is a question: who is responsible for releasing it?

C’s problem is not the absence of ownership. The problem is that ownership is usually not in the type. It lives in function names, comments, conventions, and team experience. Rust ownership turns those conventions into rules the compiler can check.

This article covers one core question: who owns, who frees, and why a moved value cannot be used again.

Read More

Resource Cleanup: From cleanup to Drop

5 minute

In C, resource cleanup is maintained by people.

Open a file and you need fclose. Allocate memory and you need free. Take a lock and you need to unlock it. Initialize an object and every error path must destroy it. Once a function has several resources, goto cleanup becomes the common structure.

Rust does not remove cleanup. It binds cleanup responsibility to whoever owns the resource. When a value leaves scope, its cleanup logic runs automatically. This mechanism is Drop.

Read More

From Return Codes to Result

5 minute

C developers know this pattern well:

int read_config(const char *path, struct Config *out);

The function returns 0 on success and a non-zero value on failure; the real result is written through an output parameter. System calls often use -1 plus errno. With more resources, the code usually grows a goto cleanup path.

This works, but it has long-term costs:

  • callers may forget to check the return value
  • success values and error codes live in different places
  • output parameters need rules for failure cases
  • integer error codes often lose context
  • cleanup paths get tangled with error propagation

Rust Result is not an exception mechanism. It is closer to putting “success or failure” directly into the function return type.

Read More