C. For readers who know C or Rust
Keyboard shortcuts
Press ← or → to navigate between chapters
Press S or / to search in the book
Press ? to show this help
Press Esc to hide this help
Appendix C: For readers who know C or Rust
The language guide has a full chapter for each. This appendix is the short version: the habits to keep and the ones to drop.
Coming from C
Keep: type-first declarations, `if`/`while`/`for` with parentheses and braces, `printf` with its format strings, structs with designated initialisers, `//` and `/* */` comments, and the feel of knowing where your memory is.
Drop these habits, because the compiler will refuse them:
- **Implicit conversion.**`int` and `long` do not mix here, and neither do `i32` and `i64`. Every conversion is a named call: `widen`, `narrow`, `to_float`, `to_int`. There is no `int`; every integer names its width.
- **Wrapping arithmetic.**`a + b` that does not fit is a fault, not a wrap. Ask for wrapping by name when a hash function wants it.
- **Null.** There is no null pointer in safe code. A value that may be absent is an `Option<T>`, and the compiler makes you check it.
- **Pointers for everything.** A pointer that only lets a callee look is `ref<T, shared>`, one that lets it change is `ref<T, exclusive>`, and a pointer that hands over ownership is just passing the value. A `rawptr<T>` exists, inside `unsafe`, for the few places that need an address and nothing else.
- **`free`.** Nothing is freed by hand. A value that owns memory is a resource, and its owner’s scope end frees it. There is no leak and no double free because there is no way to write either.
- **Error codes.** A function that can fail returns `Result<T, E>`, and the caller cannot forget to check it; a dropped `Result` is a compile error.
- **`switch`.** A `match` on an integer or text does the job, with a `_` arm, and a `match` on an enum must cover every variant.
- **Uninitialised variables.** A variable must be assigned on every path before it is read; the check is static.
- **`x & 1 == 0`.** In CobaltC this means `(x & 1) == 0`, as you meant.
What you gain is the compiler keeping the facts you used to keep in your head. And `cobc --keep-c` shows you the C your program became, with every check as an explicit line.
Coming from Rust
Keep: ownership, moves, borrowing with two modes, enums with payloads, `match`, `Option` and `Result` with `?`, generics with bounds, closures with captures, `unsafe` as the marked boundary, threads whose data is moved or lent, and the overall conviction that the compiler should prove things.
The differences are mostly about how far the checking is pushed to compile time, and about spelling:
- **Borrow checking is static where it can be and dynamic where it cannot.** Rust rejects every program it cannot prove safe. CobaltC rejects every program whose text shows a conflict, and checks the rest while it runs, faulting at the first conflicting access. The accepted set is fixed by the language, not by the cleverness of the checker, and there are no lifetime annotations: a function returning a reference must have exactly one reference parameter.
- **No traits.** There are six built-in bounds and no way to declare another. Generic code that needs an operation on your type takes it as a function parameter.
- **No method-call syntax.**`Vec::push(&mut v, x)`, never `v.push(x)`. The mode of every borrow is written where the borrow is taken.
- **No `mut` on bindings.** Every local can be assigned. What is controlled is who can reach a value, not whether it may change.
- **No iterators.**`foreach` walks the collections the language knows, and library functions such as `position`, `retain` and `sort_by` take function values. There is no iterator protocol to implement.
- **Three text types, spelled differently.**`str` is a copyable text constant, not a borrowed slice; `String` is owned text; `StringView` is Rust’s `&str`, a borrowed view of a `String`.
- **Declarations are type-first.**`i32 x = 5;` and `auto y = x;`. Blocks, `if` and `match` are expressions, as in Rust, but conditions take parentheses and `match` arms are `pattern : expression,`.
- **Overflow always checks.** In Rust it depends on the build profile. Here it is a fault in every build, and the wrapping and checked families are the only way to get another behaviour.
- **A closure’s capture list is written and checked.**`a, b { ... }` must name exactly the outer variables the body uses; how each is captured is decided by the body, and `move` captures by value.
- **A fault in a thread is contained.** It ends that thread, poisons its mutexes, and arrives at `try_join` as a value; a plain `join` re-raises it. Much like a panic, with the policy fixed by the language rather than the library.
- **Interior mutability is a mutex or an atomic, and nothing else.** There is no `Cell` or `RefCell`; `Rc<T>` gives shared read access and a mutex inside it gives mutation.
Rust programmers tend to find CobaltC easy to read and briefly annoying to write, until the qualified calls and the written borrows become automatic. The guide’s Rust chapter goes through each of these at length, with the reasons.
[](https://strawberry9.github.io/CobaltC/appendix-b-diagnostics.html "Previous chapter")
[](https://strawberry9.github.io/CobaltC/appendix-b-diagnostics.html "Previous chapter")