Hello, World & Output
Hello, World
There is no
main and no include. A Swift file compiled as the main file runs its top-level statements in order, and print is part of the standard library rather than something you declare.#include <stdio.h>
int main(void) {
printf("Hello, World!\n");
return 0;
}print("Hello, World!")print appends the newline and knows how to render whatever it is given, so it is closer to puts than to printf; print(x, terminator: "") is the no-newline form. There is no return 0 because there is no function to return from — falling off the end of the file is a zero exit status, and exit(3) from the platform module is how you return something else.Interpolation Instead of a Format String
A Swift string literal can carry expressions inline. The compiler reads the type out of each expression, so there is no
%d-versus-%ld to get wrong and no way to pass four values to a format string expecting five.#include <stdio.h>
int main(void) {
const char *name = "Ada";
int count = 3;
double average = 2.5;
printf("%s has %d items averaging %.2f\n", name, count, average);
return 0;
}import Foundation
let name = "Ada"
let count = 3
let average = 2.5
print("\(name) has \(count) items averaging \(String(format: "%.2f", average))")
// Without a format string at all:
print(name, "has", count, "items averaging", (average * 100).rounded() / 100)The precision is the one thing interpolation does not carry:
String(format:) from Foundation takes C’s conversion specifiers when you want them, and formatted() or explicit rounding covers the rest. What has gone is the mismatch — printf("%s", 42) compiles under most settings and prints whatever lives at address 42, while there is no way to spell that here.Writing to Standard Error
The two streams are still separate and still buffered separately. Reaching
stderr means importing the platform module, and this is the first row where the Darwin-or-Glibc split shows up.#include <stdio.h>
int main(void) {
printf("this is the result\n");
fprintf(stderr, "this is a diagnostic\n");
return 0;
}#if canImport(Darwin)
import Darwin
#else
import Glibc
#endif
print("this is the result")
fputs("this is a diagnostic\n", stderr)That is the actual C
fputs and the actual C stderr, called with no wrapper — which is the whole point of this page in miniature. Foundation offers FileHandle.standardError if you would rather not import the platform module, and it is what an application would use; the direct call is here because it shows how thin the boundary is.Command-Line Arguments
There is no argument list on an entry point that does not exist.
CommandLine.arguments is an array of strings the runtime built, so it knows its own count and needs no terminator.#include <stdio.h>
int main(int argc, char *argv[]) {
printf("argument count: %d\n", argc - 1);
for (int index = 1; index < argc; index++) {
printf(" %d: %s\n", index, argv[index]);
}
return 0;
}let arguments = CommandLine.arguments
print("argument count: \(arguments.count - 1)")
for (index, value) in arguments.dropFirst().enumerated() {
print(" \(index + 1): \(value)")
}Element zero is the program name, matching
argv[0], which is why both columns skip it. CommandLine.unsafeArgv is still there if you need the original char ** — for instance to hand straight to a C library — and its name tells you exactly what you are taking on.Types, Widths, and Overflow That Traps
let and var, With the Type Inferred
Swift is statically typed and rarely makes you say so.
let is a constant binding and var a variable one — and unlike C, the constant is the default that idiomatic code reaches for first.#include <stdio.h>
int main(void) {
const int count = 42; /* const is the exception, not the habit */
double ratio = 2.5;
const char *label = "answer";
printf("%d %.1f %s\n", count, ratio, label);
/* count = 1; does not compile. */
return 0;
}let count = 42 // inferred Int
var ratio = 2.5 // inferred Double
let label = "answer" // inferred String
print(count, ratio, label)
print(type(of: count), type(of: ratio), type(of: label))
ratio = 3.5 // var, so this is fine
// count = 1 // error: cannot assign to a let constantInference runs only from the initializer, so this is nothing like a dynamic language — the type is fixed at compile time and
count = "text" is an error. Where there is no initializer you say the type: var total: Int. The compiler also warns when a var is never reassigned, which is how let ends up almost everywhere.Overflow Traps Instead of Being Undefined
Signed overflow in C is undefined behavior, which is why the same expression can print different things at different optimization levels. Swift specifies it: ordinary
+ traps and stops the program, and wrapping has its own operator.#include <stdio.h>
#include <limits.h>
int main(void) {
long biggest = LONG_MAX;
/* biggest + 1 is undefined behavior. The safe way to see a wrap
is to do the arithmetic in unsigned, where it is defined, and
then read the same bits back as signed. */
unsigned long wrapped = (unsigned long)biggest + 1UL;
printf("wrapped: %ld\n", (long)wrapped);
/* Detecting it beforehand is arithmetic you write yourself: */
if (biggest > LONG_MAX - 1) {
printf("would overflow\n");
}
return 0;
}let biggest = Int.max
// let bad = biggest + 1 // TRAPS at run time: "arithmetic overflow"
print("wrapped: \(biggest &+ 1)") // &+ is opt-in wrapping
let (partial, overflowed) = biggest.addingReportingOverflow(1)
print("partial: \(partial), overflowed: \(overflowed)")
let (product, alsoOverflowed) = biggest.multipliedReportingOverflow(by: 2)
print("product: \(product), overflowed: \(alsoOverflowed)")The trap is a deliberate choice: a program that has overflowed is already wrong, and stopping is better than continuing with a wrong number.
&+, &- and &* are the wrapping operators for when the wrap is the point — a hash function, a checksum — and the …ReportingOverflow family is the version that hands you a flag instead. None of these is undefined; every one of them has a specified answer.No Implicit Numeric Conversion At All
C converts between numeric types silently, following rules most people cannot recite, which is where the sign-comparison and narrowing bugs come from. Swift converts nothing implicitly — every mixed-type expression needs a written conversion.
#include <stdio.h>
int main(void) {
int whole = 7;
double fraction = 2.5;
/* Silently promotes whole to double. */
printf("%.1f\n", whole + fraction);
/* And silently narrows, losing the fraction: */
int narrowed = fraction;
printf("%d\n", narrowed);
/* The classic: a signed operand converts, so this is FALSE. */
unsigned int zero = 0u;
printf("-1 < 0u ? %d\n", -1 < zero);
return 0;
}let whole = 7
let fraction = 2.5
// print(whole + fraction) // error: cannot add Int and Double
print(Double(whole) + fraction)
let narrowed = Int(fraction) // must be written; truncates toward zero
print(narrowed)
let zero: UInt = 0
print("-1 < 0 ? \(-1 < Int(zero))")
// The exactly: initializers fail rather than silently narrowing:
print(Int8(exactly: 100) ?? -1)
print(Int8(exactly: 300).map(String.init) ?? "nil — 300 does not fit")The verbosity is real and it is the whole trade: every conversion is visible at the place it happens, so the C column’s last line — where
-1 becomes a very large unsigned value and the comparison comes out false — cannot be written. The exactly: initializers are the safer constructors: each returns an optional and gives nil rather than truncating, which is the check C makes you write by hand — Int8(exactly: 300) is nil, where a C assignment would quietly store 44.Int Is the Word Size; the Rest Say Their Width
Swift’s default integer is
Int, which is the platform word — 64 bits everywhere that matters, like C’s long on Unix. The sized types spell the width in the name, exactly as <stdint.h> does.#include <stdio.h>
#include <stdint.h>
#include <limits.h>
int main(void) {
printf("int %zu\n", sizeof(int));
printf("long %zu\n", sizeof(long));
printf("int32 %zu\n", sizeof(int32_t));
printf("int64 %zu\n", sizeof(int64_t));
printf("int max %d\n", INT_MAX);
return 0;
}print("Int32 \(MemoryLayout<Int32>.size)")
print("Int \(MemoryLayout<Int>.size)")
print("Int64 \(MemoryLayout<Int64>.size)")
print("UInt8 \(MemoryLayout<UInt8>.size)")
print("Int32 max \(Int32.max)")
print("Int max \(Int.max)")MemoryLayout<T>.size is sizeof, and it has two siblings C conflates into one number: stride is the distance between consecutive elements in an array — which is size rounded up to the alignment, and is what sizeof actually gives you — and alignment is the requirement itself. When allocating a buffer for a C API, stride is the one you want.Optionals Instead of NULL
A Missing Value Has a Different Type
This is the central idea. In C any pointer may be
NULL and nothing in the type says whether this one can be, so the check is a discipline. In Swift a String can never be absent and a String? might be — and the compiler will not let you use the second as the first.#include <stdio.h>
#include <string.h>
/* NULL is allowed by the type, so the check is on you — and
forgetting it is undefined behavior rather than an error. */
static size_t length_of(const char *text) {
if (text == NULL) {
return 0;
}
return strlen(text);
}
int main(void) {
printf("%zu\n", length_of("hello"));
printf("%zu\n", length_of(NULL));
return 0;
}func lengthOf(_ text: String?) -> Int {
// text.count does not compile: the value might not be there.
guard let text else { return 0 }
return text.count
}
print(lengthOf("hello"))
print(lengthOf(nil))
let definitely: String = "never nil"
print(definitely.count) // no check needed, and none allowed
// let bad: String = nil // error: String is not an optional typeAn optional is not a pointer with a flag — it is an enumeration with two cases,
some(Wrapped) and none, so Int? works exactly as well as String? and there is no sentinel value to reserve. The consequence is that the null check is not something you can forget: reaching the value requires unwrapping, and the compiler knows whether you did.Four Ways to Get the Value Out
Unwrapping is where the check happens, and Swift gives it several shapes so the right one is always convenient: bind it and continue, bind it or leave, supply a default, or assert that it is there.
#include <stdio.h>
#include <stdlib.h>
static const char *find_setting(const char *key) {
if (key[0] == 'h') return "found";
return NULL;
}
int main(void) {
/* Bind and continue */
const char *first = find_setting("host");
if (first != NULL) {
printf("if: %s\n", first);
}
/* Bind or leave */
const char *second = find_setting("port");
if (second == NULL) {
printf("guard: absent\n");
}
/* Supply a default */
const char *third = find_setting("port");
printf("default: %s\n", third != NULL ? third : "(none)");
return 0;
}func findSetting(_ key: String) -> String? {
key.hasPrefix("h") ? "found" : nil
}
if let first = findSetting("host") {
print("if: \(first)")
}
func describe() -> String {
guard let value = findSetting("port") else {
return "guard: absent" // guard MUST leave the scope
}
return "guard: \(value)"
}
print(describe())
print("default: \(findSetting("port") ?? "(none)")")
print("chained: \(findSetting("host")?.uppercased() ?? "(none)")")
let known = findSetting("host")! // ! asserts; traps if it is nil
print("forced: \(known)")guard let is the one worth adopting first: it binds for the rest of the enclosing scope rather than for a nested block, so the happy path stays un-indented and the early return is where the failure is handled — which is the shape the C column’s if (x == NULL) return ladder is reaching for. The trailing ! is the deliberate escape: it traps at run time if the value is nil, so it is a promise you are making, and every style guide treats it as something to justify.An Optional Instead of a Sentinel Return
When a C function must report "no answer" for an integer it picks a value that cannot occur —
-1, SIZE_MAX, INT_MIN — and every caller has to know which one. An optional adds the missing state to the type instead, so no legitimate value has to be sacrificed.#include <stdio.h>
#include <stdlib.h>
/* Returns -1 for "not a number", which is also a valid number. */
static long parse_or_sentinel(const char *text) {
char *end = NULL;
long value = strtol(text, &end, 10);
if (end == text || *end != '\0') {
return -1;
}
return value;
}
int main(void) {
printf("%ld\n", parse_or_sentinel("42"));
printf("%ld\n", parse_or_sentinel("bad"));
printf("%ld\n", parse_or_sentinel("-1")); /* indistinguishable */
return 0;
}func parse(_ text: String) -> Int? {
Int(text) // the standard library already returns an optional
}
print(parse("42") as Any)
print(parse("bad") as Any)
print(parse("-1") as Any)
// And the difference is visible at the use site:
for candidate in ["42", "bad", "-1"] {
if let value = parse(candidate) {
print("\(candidate) parsed as \(value)")
} else {
print("\(candidate) is not a number")
}
}The C column’s last two lines print the same thing for a failure and for a legitimate
-1, which is the bug the sentinel convention guarantees eventually. Int("…") returning Int? is the standard library making the same choice everywhere: Array.first, Dictionary subscripting, and firstIndex(of:) all hand back optionals rather than reserving a value.ARC Instead of malloc and free
ARC: Reference Counting the Compiler Writes
There is no
free and no garbage collector. The compiler inserts retain and release calls around every reference to a class instance, and the object is destroyed the moment the count reaches zero — at a point you can predict.#include <stdio.h>
#include <stdlib.h>
struct Resource { const char *name; };
static struct Resource *resource_create(const char *name) {
struct Resource *self = malloc(sizeof *self);
if (self == NULL) return NULL;
self->name = name;
printf("created %s\n", name);
return self;
}
static void resource_destroy(struct Resource *self) {
printf("destroyed %s\n", self->name);
free(self);
}
int main(void) {
struct Resource *first = resource_create("first");
struct Resource *second = first; /* two pointers, one block */
printf("still alive: %s\n", second->name);
resource_destroy(first); /* exactly one destroy */
return 0;
}final class Resource {
let name: String
init(name: String) {
self.name = name
print("created \(name)")
}
deinit {
print("destroyed \(name)")
}
}
do {
var first: Resource? = Resource(name: "first")
let second = first // count is now 2
first = nil // count is 1 — nothing happens
print("still alive: \(second!.name)")
} // count hits 0 here
print("out of scope")The
deinit runs at a known moment, which is why it is a workable place to close a file handle in a way a garbage-collected finalizer is not. What reference counting cannot free on its own is a cycle — two objects holding each other stay alive forever — so Swift gives you weak and unowned references to break one, and that is the leak you will actually hunt. Structs, being value types, are not reference counted at all.defer Instead of goto cleanup
The
goto cleanup ladder is the correct C answer to "several things were acquired and any of them might fail". defer inverts it: the release is written next to the acquisition, and runs when the scope exits by any route.#include <stdio.h>
#include <stdlib.h>
int main(void) {
char *first = NULL, *second = NULL;
int result = 1;
first = malloc(16);
if (first == NULL) goto cleanup;
second = malloc(16);
if (second == NULL) goto cleanup;
printf("both acquired\n");
result = 0;
cleanup:
free(second);
free(first);
printf("cleanup ran, result %d\n", result);
return 0;
}func work() -> String {
let first = UnsafeMutablePointer<UInt8>.allocate(capacity: 16)
defer {
first.deallocate()
print("released first")
}
let second = UnsafeMutablePointer<UInt8>.allocate(capacity: 16)
defer {
second.deallocate()
print("released second")
}
print("both acquired")
return "done"
}
print("result: \(work())")Deferred blocks run in reverse order of registration, so the second buffer is released before the first — which is the order the
goto cleanup ladder achieves by listing the frees backwards. The real advantage is proximity: the release sits on the line after the acquisition, so a new return added later cannot skip it, and neither can a thrown error.Value Semantics Without Paying for the Copy
Assigning a Swift array copies it — that is what a value type means. It does not actually copy: the two share one buffer until one of them is written to, which is the optimization you would build by hand with a reference count in C.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
size_t count = 5;
int *original = malloc(count * sizeof *original);
if (original == NULL) return 1;
for (size_t index = 0; index < count; index++) {
original[index] = (int)index;
}
/* Assigning the pointer shares; copying means memcpy, now. */
int *shared = original;
int *copied = malloc(count * sizeof *copied);
if (copied == NULL) { free(original); return 1; }
memcpy(copied, original, count * sizeof *copied);
copied[0] = 99;
shared[1] = 88;
printf("original[0] %d — the copy did not touch it\n", original[0]);
printf("original[1] %d — the shared pointer did\n", original[1]);
printf("copied[0] %d\n", copied[0]);
free(copied);
free(original);
return 0;
}var original = [0, 1, 2, 3, 4]
var copied = original // no copying yet — the buffer is shared
copied[0] = 99 // NOW it is copied
print("original[0] \(original[0]) — the copy did not touch it")
print("original[1] \(original[1]) — nothing can alias a Swift array")
print("copied[0] \(copied[0])")
// Proof that the sharing is real, via the identity of the buffer:
let untouched = original
print("shared before a write: \(untouched.count == original.count)")Every standard-library collection works this way —
Array, String, Dictionary, Set — so passing a large array to a function that only reads it costs a retain, not a copy. The consequence for a C programmer to internalize: there is no aliasing between two array variables, ever, so the whole family of "somebody else mutated my buffer" bugs is gone, and the cost model still matches your instincts.Arrays That Know Their Count
The Array Knows How Long It Is
The
sizeof(array) / sizeof(array[0]) trick works only where the array has not decayed to a pointer, which is why every C function taking an array also takes a length. A Swift array carries its count, so the parameter list gets shorter and cannot disagree with itself.#include <stdio.h>
static int sum_of(const int *values, size_t count) {
int total = 0;
for (size_t index = 0; index < count; index++) {
total += values[index];
}
return total;
}
int main(void) {
int numbers[] = { 3, 1, 4, 1, 5 };
size_t count = sizeof numbers / sizeof numbers[0];
printf("count %zu, sum %d\n", count, sum_of(numbers, count));
/* Nothing stops the caller lying about the length: */
printf("lied %d\n", sum_of(numbers, 3));
return 0;
}func sumOf(_ values: [Int]) -> Int {
var total = 0
for value in values {
total += value
}
return total
}
let numbers = [3, 1, 4, 1, 5]
print("count \(numbers.count), sum \(sumOf(numbers))")
// Or the whole thing as one call:
print("reduce \(numbers.reduce(0, +))")An array does not decay: pass it anywhere and it is still an array, still knows its count, still bounds-checks. The C column’s last line — the caller passing a length the callee has no way to question — has no spelling here. If you genuinely want the pointer-and-count shape,
withUnsafeBufferPointer in the unsafe section hands it to you for the duration of a closure.Every Subscript Is Checked
Indexing past the end of a C array reads whatever is there and is the root of most memory-corruption bugs. Swift checks every subscript and traps — it stops the program rather than throwing, because an out-of-range index is a logic error rather than a condition.
#include <stdio.h>
int main(void) {
int numbers[3] = { 10, 20, 30 };
int wanted = 5;
/* numbers[5] is undefined behavior, so the check is yours. */
if (wanted >= 0 && wanted < 3) {
printf("%d\n", numbers[wanted]);
} else {
printf("index %d is out of range\n", wanted);
}
return 0;
}let numbers = [10, 20, 30]
let wanted = 5
// numbers[wanted] would TRAP: "Index out of range". The checked
// alternatives return optionals instead.
print(numbers.indices.contains(wanted)
? "\(numbers[wanted])"
: "index \(wanted) is out of range")
print("first \(numbers.first as Any), last \(numbers.last as Any)")
print("safe \(numbers.dropFirst(5).first as Any)")The trap costs a compare and a branch per subscript, and the optimizer removes most of them — a
for value in numbers loop is proved safe and emits no check at all. Note what the standard library does instead of trapping where absence is legitimate: first and last return optionals, so an empty array gives nil rather than reading whatever was at index zero.A Slice Is a View, Not a Copy
A range of a C array is a pointer into the middle plus a count, and keeping the two in step is your problem. An
ArraySlice is that pair as one value — it shares the original storage, carries its own bounds, and keeps the original’s indices.#include <stdio.h>
static int sum_of(const int *values, size_t count) {
int total = 0;
for (size_t index = 0; index < count; index++) {
total += values[index];
}
return total;
}
int main(void) {
int numbers[6] = { 1, 2, 3, 4, 5, 6 };
printf("whole %d\n", sum_of(numbers, 6));
printf("middle %d\n", sum_of(numbers + 2, 3)); /* elements 2,3,4 */
return 0;
}let numbers = [1, 2, 3, 4, 5, 6]
print("whole \(numbers.reduce(0, +))")
let middle = numbers[2..<5] // a view, no copying
print("middle \(middle.reduce(0, +))")
// A slice keeps the ORIGINAL indices — the classic surprise:
print("indices \(middle.startIndex)..<\(middle.endIndex)")
print("first element \(middle.first!)")
print("as its own array \(Array(middle)[0])")The index behavior is the thing to remember:
middle[0] traps, because the slice’s valid indices start at 2. That is deliberate — it means an index found in a slice is meaningful in the parent array — and it is why Array(slice) exists for when you want a fresh, zero-based copy. The same idea appears as Substring for strings, and for the same reason.map, filter and reduce Instead of Three Loops
Filtering, transforming and accumulating are three hand-written loops in C, each with its own index and accumulator. Here they are named operations that take a closure, and the loop is inside them.
#include <stdio.h>
int main(void) {
int numbers[8] = { 3, 1, 4, 1, 5, 9, 2, 6 };
size_t count = sizeof numbers / sizeof numbers[0];
int total = 0, matches = 0;
for (size_t index = 0; index < count; index++) {
if (numbers[index] % 2 == 0) {
total += numbers[index] * numbers[index];
matches++;
}
}
printf("%d even values, squares total %d\n", matches, total);
return 0;
}let numbers = [3, 1, 4, 1, 5, 9, 2, 6]
let evenSquares = numbers.filter { $0 % 2 == 0 }.map { $0 * $0 }
print("\(evenSquares.count) even values, squares total \(evenSquares.reduce(0, +))")
print("largest three: \(numbers.sorted(by: >).prefix(3).map(String.init).joined(separator: " "))")
print("any above 8: \(numbers.contains { $0 > 8 })")$0 is the first closure argument when you have not named it, which keeps short transformations short. These are eager — each one walks the array and builds a new one — so a three-stage chain over a million elements does three passes; numbers.lazy.filter { … }.map { … } switches to a single fused pass, at the cost of a little indirection per element.Strings Are Characters, Not Bytes
A Character Is What a Reader Would Call One
A C string is bytes and a terminator. A Swift
String is a sequence of grapheme clusters — what a person would point at and call a character — which means count answers a question about text rather than about storage.#include <stdio.h>
#include <string.h>
int main(void) {
const char *plain = "cafe";
const char *accented = "caf\u00e9"; /* UTF-8: 5 bytes */
const char *emoji = "\U0001F468\U0000200D\U0001F4BB"; /* one glyph */
printf("plain %zu bytes\n", strlen(plain));
printf("accented %zu bytes\n", strlen(accented));
printf("emoji %zu bytes\n", strlen(emoji));
return 0;
}let plain = "cafe"
let accented = "caf\u{e9}"
let emoji = "\u{1F468}\u{200D}\u{1F4BB}" // one glyph, three scalars
print("plain \(plain.count) characters, \(plain.utf8.count) bytes")
print("accented \(accented.count) characters, \(accented.utf8.count) bytes")
print("emoji \(emoji.count) characters, \(emoji.utf8.count) bytes")
print("emoji scalars \(emoji.unicodeScalars.count)")The emoji is one character, three Unicode scalars, and eleven UTF-8 bytes — and the C column can only report the last of those. That correctness has a price:
count is O(n) because grapheme boundaries must be found, and a String cannot be subscripted by an integer at all. String.Index is what you use instead, which is the single biggest adjustment coming from a language where a string is an array.No Terminator, So a Zero Is Just Data
Because the length is stored rather than found, a Swift string may contain a zero character with no consequence. In C that same byte truncates the value at every function taking a
char * — the source of a long list of security bugs.#include <stdio.h>
#include <string.h>
int main(void) {
char text[] = "safe\0evil";
printf("strlen says %zu\n", strlen(text));
printf("printed: %s\n", text);
printf("array is %zu bytes\n", sizeof text);
return 0;
}import Foundation
let text = "safe\u{0}evil"
print("count says \(text.count)")
print("printed: \(text.replacingOccurrences(of: "\u{0}", with: "<NUL>"))")
print("contains a zero: \(text.contains("\u{0}"))")
print("utf8 bytes: \(text.utf8.count)")The practical upshot at an interop boundary: a Swift string can hold something a C function will silently truncate.
withCString and the automatic conversion when calling a C function both produce a NUL-terminated copy, so an embedded zero means the C side sees a shorter string than you sent — worth remembering before passing user input through to a C library.Find, Slice, Split, Compare
Every operation returns a value rather than filling a buffer you sized, and comparison is
== — so the C habit of writing if (a == b) for strings, a bug there, is correct here.#include <stdio.h>
#include <string.h>
int main(void) {
const char *text = "the quick brown fox";
printf("length %zu\n", strlen(text));
printf("contains %d\n", strstr(text, "quick") != NULL);
char piece[6];
strncpy(piece, text + 4, 5);
piece[5] = '\0';
printf("substr %s\n", piece);
printf("equal %d\n", strcmp("apple", "apple") == 0);
printf("before %d\n", strcmp("apple", "banana") < 0);
return 0;
}let text = "the quick brown fox"
print("length \(text.count)")
print("contains \(text.contains("quick"))")
print("substr \(text.dropFirst(4).prefix(5))")
print("equal \("apple" == "apple")")
print("before \("apple" < "banana")")
print("upper \(text.uppercased())")
print("split \(text.split(separator: " ").joined(separator: "|"))")dropFirst and prefix return Substrings, which share the original’s storage — cheap, and the reason a long-lived substring keeps the whole original string alive until you build a fresh String from it. uppercased is locale-correct rather than the ASCII arithmetic the C column would need, which is most of why C string handling looks simpler than it is.Getting a char * Out of a String
When Swift calls a C function taking a
const char *, you can hand it a String directly and the compiler produces a NUL-terminated UTF-8 copy for the duration of the call. When you need the pointer yourself, withCString is the explicit form — and the lifetime rule is the C one again.#include <stdio.h>
#include <string.h>
int main(void) {
const char *text = "caf\u00e9";
printf("strlen %zu\n", strlen(text));
for (size_t index = 0; index < strlen(text); index++) {
printf("%02x ", (unsigned char)text[index]);
}
printf("\n");
return 0;
}import Foundation
#if canImport(Darwin)
import Darwin
#else
import Glibc
#endif
let text = "caf\u{e9}"
// A String converts automatically at a C call site:
print("strlen \(strlen(text))")
print(Array(text.utf8).map { String(format: "%02x", $0) }.joined(separator: " "))
// And explicitly, when you need the pointer for longer than one call:
text.withCString { pointer in
print("first byte \(pointer[0])")
}The automatic conversion is real but temporary: the buffer is valid only for the duration of that one call, so storing the pointer that
strdup was invented to avoid needing is still exactly as wrong here as in C. withCString makes the window explicit — the pointer is valid inside the closure and nowhere else — which is Swift’s general pattern for handing out an address it does not want you to keep.Value Types and Reference Types
struct Copies; class Does Not
A Swift
struct is the C struct you know: assigning it, passing it, and returning it all copy every field. A class with the same fields behaves the opposite way. This row runs the same code against both so the difference is the only variable.#include <stdio.h>
struct Counter { int value; };
static void bump_by_value(struct Counter copy) {
copy.value += 1; /* the caller never sees this */
}
static void bump_by_pointer(struct Counter *shared) {
shared->value += 1; /* the caller does */
}
int main(void) {
struct Counter counter = { 0 };
bump_by_value(counter);
printf("after by-value: %d\n", counter.value);
bump_by_pointer(&counter);
printf("after by-pointer: %d\n", counter.value);
return 0;
}struct ValueCounter { var value = 0 }
final class ClassCounter { var value = 0 }
func bump(_ copy: ValueCounter) { var copy = copy; copy.value += 1 }
func bump(_ shared: ClassCounter) { shared.value += 1 }
let asValue = ValueCounter()
bump(asValue)
print("after struct pass: \(asValue.value)")
let asClass = ClassCounter()
bump(asClass)
print("after class pass: \(asClass.value)")Note that the struct version needs
var copy = copy before it can write at all: function parameters are constants in Swift, which is a stronger position than C’s "the copy is yours to scribble on". Note too that asClass is declared let and its field still changed — let on a class reference means the reference cannot be repointed, not that the object cannot be modified, which is exactly C’s Counter *const.Methods, and the mutating Keyword
The C pattern is a struct and functions taking a pointer to it. Swift folds that argument into the syntax and calls it
self — with one extra rule for value types: a method that changes the struct must say mutating, which is the difference between the const and non-const pointer in the C column.#include <stdio.h>
struct Rectangle { double width; double height; };
static double rectangle_area(const struct Rectangle *self) {
return self->width * self->height;
}
static void rectangle_scale(struct Rectangle *self, double factor) {
self->width *= factor;
self->height *= factor;
}
int main(void) {
struct Rectangle shape = { 3.0, 4.0 };
printf("area %.1f\n", rectangle_area(&shape));
rectangle_scale(&shape, 2.0);
printf("scaled area %.1f\n", rectangle_area(&shape));
return 0;
}struct Rectangle {
var width: Double
var height: Double
func area() -> Double { // const self
width * height
}
mutating func scale(by factor: Double) { // non-const self
width *= factor
height *= factor
}
}
var shape = Rectangle(width: 3.0, height: 4.0)
print("area \(shape.area())")
shape.scale(by: 2.0)
print("scaled area \(shape.area())")The
mutating marker is not decoration: a let struct will not let you call one, so the compiler enforces the const discipline the C column can only ask for. The memberwise initializer Rectangle(width:height:) comes free with the struct — there is no boilerplate constructor to write, and the argument labels make the call site read.Protocols Instead of a Table of Function Pointers
Polymorphism in C is a struct of function pointers each implementation fills in, plus the discipline of pairing the right table with the right data. A protocol is that table, built and checked by the compiler — and unlike a class hierarchy, a struct can conform to one.
#include <stdio.h>
struct ShapeOperations { double (*area)(const void *self); };
struct Square { double side; };
struct Circle { double radius; };
static double square_area(const void *self) {
double side = ((const struct Square *)self)->side;
return side * side;
}
static double circle_area(const void *self) {
double radius = ((const struct Circle *)self)->radius;
return 3.14159265358979 * radius * radius;
}
int main(void) {
struct ShapeOperations squareOps = { square_area };
struct ShapeOperations circleOps = { circle_area };
struct Square square = { 3.0 };
struct Circle circle = { 1.0 };
printf("%.2f\n", squareOps.area(&square));
printf("%.2f\n", circleOps.area(&circle));
return 0;
}import Foundation
protocol Shape {
func area() -> Double
}
struct Square: Shape {
let side: Double
func area() -> Double { side * side }
}
struct Circle: Shape {
let radius: Double
func area() -> Double { Double.pi * radius * radius }
}
let shapes: [Shape] = [Square(side: 3.0), Circle(radius: 1.0)]
for shape in shapes {
print(String(format: "%.2f", shape.area()))
}A type that says
: Shape and omits area fails to compile, which is the check the C column cannot make — nothing stops you leaving a function pointer null. Both shapes fitting in one array is the other half, and it costs something: a [Shape] stores each value in an existential box with a witness table, which is one indirection per element. Making the function generic over <T: Shape> instead specializes it and removes the box.An Enum That Carries Data Is a Tagged Union
C’s tagged union is a struct holding a tag and a union, with the correspondence between them left entirely to you. A Swift enum carries the payload in the case itself, so there is no way to read the wrong member.
#include <stdio.h>
enum ValueKind { KIND_INTEGER, KIND_TEXT };
struct Value {
enum ValueKind kind; /* the tag you must remember to set */
union {
int as_integer;
const char *as_text;
} data;
};
static void describe(const struct Value *value) {
switch (value->kind) {
case KIND_INTEGER: printf("integer %d\n", value->data.as_integer); break;
case KIND_TEXT: printf("text %s\n", value->data.as_text); break;
}
}
int main(void) {
struct Value first = { KIND_INTEGER, { .as_integer = 42 } };
struct Value second;
second.kind = KIND_TEXT;
second.data.as_text = "hello";
describe(&first);
describe(&second);
/* Nothing stops reading the wrong member: */
printf("misread %d\n", second.data.as_integer != 0);
return 0;
}enum Value {
case integer(Int)
case text(String)
case pair(Int, Int)
}
func describe(_ value: Value) {
switch value {
case .integer(let number): print("integer \(number)")
case .text(let string): print("text \(string)")
case .pair(let left, let right): print("pair \(left),\(right)")
}
}
describe(.integer(42))
describe(.text("hello"))
describe(.pair(1, 2))
// There is no way to read the Int out of a .text case: the payload
// is only reachable through a pattern that matched that case.The tag and the payload cannot get out of step, because they are the same thing — and the
switch must handle every case or the file does not compile, so adding a fourth case surfaces every place that needs updating. The runtime layout is still a tag plus a union, often packed into the spare bits of the payload, so the representation is as tight as the C one and none of the hazards come with it.Adding Methods to a Type You Did Not Write
In C, a helper for a type someone else declared is a free function with a prefix on its name. An extension attaches it to the type itself — including to
Int, String and the other built-ins — with no subclassing and no wrapper.#include <stdio.h>
#include <stdbool.h>
/* A helper for int lives at file scope with a name that says
what it is for, because there is nowhere else to put it. */
static bool int_is_even(int value) {
return value % 2 == 0;
}
static int int_clamped(int value, int lowest, int highest) {
if (value < lowest) return lowest;
if (value > highest) return highest;
return value;
}
int main(void) {
printf("%d %d\n", int_is_even(4), int_is_even(5));
printf("%d\n", int_clamped(99, 0, 10));
return 0;
}extension Int {
var isEven: Bool { self % 2 == 0 }
func clamped(to range: ClosedRange<Int>) -> Int {
// Swift.min, not a bare min: inside an Int extension the
// unqualified name resolves to the static property Int.min.
Swift.min(Swift.max(self, range.lowerBound), range.upperBound)
}
}
print(4.isEven, 5.isEven)
print(99.clamped(to: 0...10))
extension String {
var reversedWords: String {
split(separator: " ").reversed().joined(separator: " ")
}
}
print("the quick brown fox".reversedWords)An extension cannot add stored properties — it would change the layout — so it is methods and computed properties only, which keeps the size of every existing value unchanged. This is how the standard library is organized internally, and it is also how protocol conformance is usually added:
extension MyType: Equatable { … } keeps the conformance and its methods together rather than swelling the original declaration.Labels, inout, and Closures
Arguments Have Labels at the Call Site
A C call is positional, so
copy(a, b) does not say which is the source. Swift parameters have an external label that appears at every call, and it is part of the function’s name — which is why the standard library reads the way it does.#include <stdio.h>
#include <string.h>
static void copy_range(char *destination, const char *source,
size_t start, size_t count) {
memcpy(destination, source + start, count);
destination[count] = '\0';
}
int main(void) {
char buffer[16];
/* Which argument is which? The call does not say. */
copy_range(buffer, "the quick fox", 4, 5);
printf("%s\n", buffer);
return 0;
}func copy(from source: String, start: Int, count: Int) -> String {
String(source.dropFirst(start).prefix(count))
}
print(copy(from: "the quick fox", start: 4, count: 5))
// The first label can be suppressed with _, for when it adds nothing:
func double(_ value: Int) -> Int { value * 2 }
print(double(21))The labels are part of the identity, so
copy(from:start:count:) and copy(from:to:) are different functions rather than overloads — which is also how Swift avoids most of the overload ambiguity other languages have. The underscore is the deliberate opt-out, used where the parameter’s role is obvious from the function name, as in print(_:).inout Instead of Passing an Address
The C way to let a function write to your variable is to pass its address and dereference. Swift has that as a parameter mode:
inout on the declaration, and an & at the call so the reader can see which arguments a call can change.#include <stdio.h>
static void swap_values(int *left, int *right) {
int held = *left;
*left = *right;
*right = held;
}
int main(void) {
int first = 1, second = 2;
swap_values(&first, &second);
printf("%d %d\n", first, second);
return 0;
}func swapValues(_ left: inout Int, _ right: inout Int) {
(left, right) = (right, left)
}
var first = 1, second = 2
swapValues(&first, &second) // the & is required at the call
print(first, second)
// The standard library already has this one:
var third = 10, fourth = 20
swap(&third, &fourth)
print(third, fourth)The
& is required, which is the opposite of C++ and Pascal and means a call site never hides a mutation. Under the hood this is copy-in, copy-out rather than a genuine reference, so passing the same variable as two inout arguments is a compile error rather than the aliasing surprise it would be in C. There is no way to store an inout parameter for later — the window closes when the function returns.A Closure Is a Function Pointer With Its Context
Every C callback interface carries a
void * beside the function pointer, because the pointer remembers nothing. A Swift closure captures the variables in scope where it was written, so the extra parameter disappears from every layer.#include <stdio.h>
static void for_each(const int *values, size_t count,
void (*action)(int value, void *context),
void *context) {
for (size_t index = 0; index < count; index++) {
action(values[index], context);
}
}
static void add_to_total(int value, void *context) {
*(int *)context += value;
}
int main(void) {
int numbers[] = { 1, 2, 3, 4 };
int total = 0;
for_each(numbers, 4, add_to_total, &total);
printf("total %d\n", total);
return 0;
}let numbers = [1, 2, 3, 4]
var total = 0
numbers.forEach { total += $0 } // total is captured
print("total \(total)")
// A closure built at run time — no C function pointer can be:
func multiplier(by factor: Int) -> (Int) -> Int {
{ value in value * factor }
}
let triple = multiplier(by: 3)
print("triple(7) = \(triple(7))")The captured variable is not copied — the closure holds the same storage, which is why
total ends at 10 and why factor outlives the call to multiplier. That last part is the piece C cannot do: a function pointer can only point at code the compiler emitted, so the closest C gets is a struct holding a pointer and its data, which is what the void * in the left column is. A closure that captures nothing is convertible to a real C function pointer, which the interop section uses.Defaults and Variadics That Count Themselves
C’s answer to an optional parameter is a second function, and its variadic functions cannot know how many arguments arrived or what types they were. Swift gives parameters defaults, and a variadic parameter is an ordinary typed array.
#include <stdio.h>
#include <stdarg.h>
static int sum_of(int count, ...) {
va_list arguments;
va_start(arguments, count);
int total = 0;
for (int index = 0; index < count; index++) {
total += va_arg(arguments, int);
}
va_end(arguments);
return total;
}
static void log_message(const char *text, const char *level) {
printf("[%s] %s\n", level, text);
}
static void log_simple(const char *text) { log_message(text, "info"); }
int main(void) {
printf("%d %d\n", sum_of(3, 1, 2, 3), sum_of(0));
log_simple("started");
log_message("careful", "warn");
return 0;
}func sumOf(_ values: Int...) -> Int {
values.reduce(0, +) // values is a real [Int]
}
func logMessage(_ text: String, level: String = "info") {
print("[\(level)] \(text)")
}
print(sumOf(1, 2, 3), sumOf())
logMessage("started")
logMessage("careful", level: "warn")The leading count is gone because the array knows its length, and every element is type-checked at the call site —
sumOf(1, "two") does not compile, where sum_of(2, 1, "two") does and then reads a pointer as an int. Default values are evaluated at the call, in the caller’s context, so func log(at time: Date = Date()) gets the current time on each call rather than the one from when the program started.throws Instead of a Return Code
throws, and the try You Must Write
A returned status can be ignored and usually nothing warns. A Swift function that can fail is marked
throws, and every call to it must be written with try — so the failure is visible in the source at every call site, not just at the declaration.#include <stdio.h>
#include <stdlib.h>
static int parse_count(const char *text, long *out) {
char *end = NULL;
long value = strtol(text, &end, 10);
if (end == text || *end != '\0') {
return 0;
}
*out = value;
return 1;
}
int main(void) {
long parsed = 0;
if (parse_count("42", &parsed)) {
printf("parsed %ld\n", parsed);
}
if (!parse_count("not a number", &parsed)) {
printf("parse failed\n");
}
/* And ignoring the result compiles silently: */
parse_count("also bad", &parsed);
printf("ignored, program continues\n");
return 0;
}enum ParseError: Error {
case notANumber(String)
}
func parseCount(_ text: String) throws -> Int {
guard let value = Int(text) else {
throw ParseError.notANumber(text)
}
return value
}
do {
print("parsed \(try parseCount("42"))")
print("parsed \(try parseCount("not a number"))")
} catch ParseError.notANumber(let text) {
print("parse failed: \(text) is not a number")
}
// try? turns a throw into nil; try! asserts it cannot happen.
let quiet = try? parseCount("bad")
print("optional: \(quiet.map(String.init) ?? "nil")")Every call to a throwing function needs
try, and a function that lets an error propagate must itself be throws — so the failure path is documented all the way up the call stack, which is what a return code never manages. Swift errors are not exceptions in the C++ sense: there is no stack unwinding machinery and no cost when nothing throws, because throws compiles to an extra return register.Result, For When a Failure Is a Value
Sometimes the failure needs to be stored, passed around, or handled later rather than at the call.
Result is the two-case enum for that — success or failure, both carrying a payload — which is the type C approximates with a status code and an out-parameter.#include <stdio.h>
#include <stdlib.h>
/* The status-plus-out-parameter shape: the two halves can be
separated, and nothing keeps them in step. */
struct ParseResult {
int succeeded;
long value;
const char *message;
};
static struct ParseResult parse_count(const char *text) {
char *end = NULL;
long value = strtol(text, &end, 10);
if (end == text || *end != '\0') {
struct ParseResult failure = { 0, 0, "not a number" };
return failure;
}
struct ParseResult success = { 1, value, NULL };
return success;
}
int main(void) {
struct ParseResult first = parse_count("42");
struct ParseResult second = parse_count("bad");
printf("%s\n", first.succeeded ? "ok" : first.message);
printf("%ld\n", first.value);
printf("%s\n", second.succeeded ? "ok" : second.message);
/* And nothing stops reading second.value, which is meaningless. */
return 0;
}enum ParseError: Error {
case notANumber
}
func parseCount(_ text: String) -> Result<Int, ParseError> {
guard let value = Int(text) else { return .failure(.notANumber) }
return .success(value)
}
for candidate in ["42", "bad"] {
switch parseCount(candidate) {
case .success(let value): print("\(candidate) -> \(value)")
case .failure(let error): print("\(candidate) -> \(error)")
}
}
// Results compose, which a status code does not:
let doubled = parseCount("21").map { $0 * 2 }
print(doubled)The value and the failure cannot both be present and neither can be absent, because they are two cases of one enum — so the C column’s last hazard, reading
value after a failure, has no spelling. map and flatMap transform the success case and pass the failure through untouched, which is how a chain of fallible steps becomes one expression rather than a ladder of checks.assert, precondition, and fatalError
C has one
assert, compiled out by NDEBUG. Swift has three levels, and the distinction is which builds keep the check — which matters because the answer for a programmer error and for a validated input is not the same.#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
static int divide(int numerator, int denominator) {
/* Compiled out entirely with -DNDEBUG. */
assert(denominator != 0);
return numerator / denominator;
}
int main(void) {
printf("%d\n", divide(7, 2));
/* For a check that must survive the release build, you write
the branch and the abort by hand: */
if (2 == 0) {
fprintf(stderr, "denominator was zero\n");
abort();
}
printf("checked\n");
return 0;
}func divide(_ numerator: Int, by denominator: Int) -> Int {
assert(denominator != 0, "debug builds only")
precondition(denominator != 0, "every build")
return numerator / denominator
}
print(divide(7, by: 2))
// fatalError never returns, and the compiler knows it:
func mustBePositive(_ value: Int) -> Int {
guard value > 0 else { fatalError("value must be positive") }
return value
}
print(mustBePositive(5))
print("checked")assert is removed in a release build, exactly like C’s; precondition survives it, which is what you want for anything validating input rather than checking your own reasoning; and fatalError is always present and has return type Never, so the compiler knows the code after it is unreachable and a guard ending in one satisfies the "must leave the scope" rule. The three cover what C leaves to assert plus a hand-written abort.switch Must Cover Every Case
switch Covers Every Case and Never Falls Through
C’s
switch falls into the next label unless you write break, and a missing case is silently nothing. Swift’s runs exactly one branch, and refuses to compile unless the cases cover every possible value.#include <stdio.h>
enum Level { LEVEL_LOW, LEVEL_MEDIUM, LEVEL_HIGH };
static const char *describe(enum Level level) {
switch (level) {
case LEVEL_LOW: return "low";
case LEVEL_MEDIUM: return "medium";
/* LEVEL_HIGH is missing. Without -Wswitch, nothing says so,
and the function falls off the end returning garbage. */
}
return "unknown";
}
int main(void) {
printf("%s %s %s\n",
describe(LEVEL_LOW), describe(LEVEL_MEDIUM), describe(LEVEL_HIGH));
/* And the classic fallthrough bug: */
int total = 0;
switch (2) {
case 2: total += 1;
case 3: total += 10;
default: total += 100;
}
printf("fell through to %d\n", total);
return 0;
}enum Level {
case low, medium, high
}
func describe(_ level: Level) -> String {
switch level {
case .low: return "low"
case .medium: return "medium"
case .high: return "high"
// Omitting .high is a compile error: "switch must be exhaustive".
}
}
print(describe(.low), describe(.medium), describe(.high))
print("no fallthrough is possible")
// Ranges, tuples and where clauses are all patterns:
for value in [-5, 0, 7, 99] {
switch value {
case ..<0: print("\(value): negative")
case 0: print("\(value): zero")
case 1...9: print("\(value): small")
case let n where n > 50: print("\(n): large")
default: print("\(value): medium")
}
}Exhaustiveness is the feature that pays off later: add a fourth case to the enum and every
switch over it stops compiling, listing exactly the places that need updating. C offers -Wswitch, which catches the same thing for enums only, is a warning, and is off unless you asked. The keyword fallthrough exists for the rare case you genuinely want C’s behavior, and having to write it is the point.Returning Several Values Without a Struct
A C function returning two things needs a struct or an out-parameter. A tuple is an anonymous, ordered group of values with optional names — good enough for a return value that does not deserve a type of its own.
#include <stdio.h>
struct DivisionResult {
int quotient;
int remainder;
};
static struct DivisionResult divide(int numerator, int denominator) {
struct DivisionResult result;
result.quotient = numerator / denominator;
result.remainder = numerator % denominator;
return result;
}
int main(void) {
struct DivisionResult result = divide(17, 5);
printf("%d remainder %d\n", result.quotient, result.remainder);
/* Or the out-parameter shape: */
int quotient = 0, remainder = 0;
quotient = 17 / 5;
remainder = 17 % 5;
printf("%d remainder %d\n", quotient, remainder);
return 0;
}func divide(_ numerator: Int, by denominator: Int)
-> (quotient: Int, remainder: Int) {
(numerator / denominator, numerator % denominator)
}
let result = divide(17, by: 5)
print("\(result.quotient) remainder \(result.remainder)")
// Destructuring at the point of use:
let (quotient, remainder) = divide(17, by: 5)
print("\(quotient) remainder \(remainder)")
// And tuples are comparable and matchable:
let point = (x: 3, y: 0)
switch point {
case (0, 0): print("origin")
case (_, 0): print("on the x axis")
default: print("somewhere else")
}A tuple is a value type laid out like a struct, so returning one costs nothing extra — and unlike the out-parameter form there is no half-initialized state to reach. What a tuple cannot do is carry methods, conform to a protocol, or be extended, which is the line to draw: when the pair starts being passed around and reasoned about, promote it to a struct.
Generics Instead of void *
Generics Instead of void *
C has two ways to write a container for any type:
void * with casts, which loses all checking, or a macro that pastes the type in, which loses readable errors. A generic keeps the type in the signature and checks it.#include <stdio.h>
#include <stdlib.h>
/* A void * stack: nothing prevents pushing an int and popping a
char *, and the compiler cannot help. */
struct Stack { void **items; size_t count; };
static void push(struct Stack *self, void *item) {
self->items = realloc(self->items, (self->count + 1) * sizeof(void *));
self->items[self->count++] = item;
}
static void *pop(struct Stack *self) {
return self->count ? self->items[--self->count] : NULL;
}
int main(void) {
struct Stack stack = { NULL, 0 };
int first = 42;
push(&stack, &first);
printf("%d\n", *(int *)pop(&stack)); /* the cast is a promise */
free(stack.items);
return 0;
}struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ item: Element) { items.append(item) }
mutating func pop() -> Element? { items.popLast() }
}
var numbers = Stack<Int>()
numbers.push(42)
print(numbers.pop() ?? -1) // an Int, no cast, no boxing
var names = Stack<String>()
names.push("hello")
print(names.pop()?.uppercased() ?? "")
// numbers.push("wrong type") // error at compile timeSwift generics are specialized: the optimizer emits a separate copy for each concrete type it sees, so
Stack<Int> stores raw Ints with no boxing and no indirection — the same layout you would have written by hand. That is the difference from Java’s erased generics, and it is why a generic container in Swift has no per-element cost.Constraints: Saying What the Type Must Support
A C macro-template fails at the point of expansion with an error about a line the reader never wrote. A generic constraint states the requirement in the declaration, so the failure is reported at the call site in terms of the type the caller passed.
#include <stdio.h>
/* The macro compiles only if the type happens to support <, and
the error, when it fails, names this line rather than the caller's. */
#define MAXIMUM(a, b) ((a) > (b) ? (a) : (b))
int main(void) {
printf("%d\n", MAXIMUM(3, 7));
printf("%.1f\n", MAXIMUM(2.5, 1.5));
return 0;
}func maximum<T: Comparable>(_ left: T, _ right: T) -> T {
left >= right ? left : right
}
print(maximum(3, 7))
print(maximum(2.5, 1.5))
print(maximum("apple", "banana"))
// A constraint can require several things at once:
func describeAll<S: Sequence>(_ items: S) -> String
where S.Element: CustomStringConvertible {
items.map(\.description).joined(separator: ", ")
}
print(describeAll([1, 2, 3]))The constraint is also what makes the body legal: without
T: Comparable the compiler rejects left >= right, because it cannot know the type has it. That is the opposite of a C++ template, which type-checks only when instantiated — so a Swift generic either compiles for every conforming type or does not compile at all, and the error names the constraint rather than a line inside a library.The Unsafe Family, When You Need It
Real Pointers, Named So You Notice
Swift has the whole pointer family, and every one of them has
Unsafe in the name. Inside them the rules are C’s: no bounds checks, no lifetime tracking, and an allocation you must deallocate.#include <stdio.h>
#include <stdlib.h>
int main(void) {
size_t count = 4;
int *block = malloc(count * sizeof *block);
if (block == NULL) return 1;
for (size_t index = 0; index < count; index++) {
block[index] = (int)index * 10;
}
printf("%d %d %d %d\n", block[0], block[1], block[2], block[3]);
int *walk = block + 2;
printf("through a walked pointer: %d\n", *walk);
free(block);
return 0;
}let count = 4
let block = UnsafeMutablePointer<Int32>.allocate(capacity: count)
block.initialize(repeating: 0, count: count)
defer {
block.deinitialize(count: count)
block.deallocate() // the free you are back to writing
}
for index in 0..<count {
block[index] = Int32(index) * 10
}
print(block[0], block[1], block[2], block[3])
let walk = block + 2 // pointer arithmetic, unchecked
print("through a walked pointer: \(walk.pointee)")Every rule you know applies again inside this block: nothing is bounds-checked, the memory is not managed, and forgetting the
deallocate is a genuine leak — which is why the defer is written on the line after the allocation. The extra step with no C counterpart is initialize/deinitialize: Swift distinguishes memory that has been allocated from memory that holds a valid value, and for a type with a reference count that distinction is what keeps the counting correct.MemoryLayout: size, stride, and alignment
C’s
sizeof answers two questions at once — how big a value is, and how far apart consecutive ones are in an array — because in C those are the same number. Swift separates them, and the distinction matters the moment you allocate a buffer for a C API.#include <stdio.h>
#include <stddef.h>
struct Header {
int length;
unsigned char kind; /* one byte, then three of tail padding */
};
int main(void) {
printf("sizeof %zu\n", sizeof(struct Header));
printf("align %zu\n", _Alignof(struct Header));
printf("len at %zu\n", offsetof(struct Header, length));
printf("kind at %zu\n", offsetof(struct Header, kind));
printf("array of 3 is %zu bytes\n", 3 * sizeof(struct Header));
return 0;
}struct Header {
var length: Int32
var kind: UInt8 // one byte, then three of tail padding
}
print("size \(MemoryLayout<Header>.size)")
print("stride \(MemoryLayout<Header>.stride)")
print("align \(MemoryLayout<Header>.alignment)")
print("len at \(MemoryLayout<Header>.offset(of: \Header.length) ?? -1)")
print("kind at \(MemoryLayout<Header>.offset(of: \Header.kind) ?? -1)")
print("array of 3 is \(3 * MemoryLayout<Header>.stride) bytes")size is the bytes the value actually occupies — 5 here, four for the Int32 and one for the UInt8 — while stride is that rounded up to the alignment, 8, and is the number that matches C’s sizeof. Getting them mixed up when allocating is the classic mistake: capacity * size under-allocates for any type with tail padding, and stride is the one to multiply by. The layout of a plain Swift struct is not guaranteed to match a C struct unless you say @frozen or go through an imported C type, which the interop section covers.Borrowing a Pointer Into Safe Storage
Sometimes you need a
(pointer, count) pair for a C call, but the data lives in a Swift array. The withUnsafe… family lends you the address for exactly the duration of a closure, which is the language stating the lifetime rule rather than leaving it to you.#include <stdio.h>
#include <string.h>
static int sum_of(const int *values, size_t count) {
int total = 0;
for (size_t index = 0; index < count; index++) {
total += values[index];
}
return total;
}
int main(void) {
int numbers[5] = { 1, 2, 3, 4, 5 };
/* The array decays to a pointer at the call. */
printf("sum %d\n", sum_of(numbers, 5));
/* And the raw bytes are just there: */
unsigned char *bytes = (unsigned char *)numbers;
printf("first byte %d\n", bytes[0]);
printf("total bytes %zu\n", sizeof numbers);
return 0;
}let numbers: [Int32] = [1, 2, 3, 4, 5]
let total = numbers.withUnsafeBufferPointer { buffer -> Int32 in
var running: Int32 = 0
for index in 0..<buffer.count {
running += buffer[index]
}
return running
}
print("sum \(total)")
numbers.withUnsafeBytes { raw in
print("first byte \(raw[0])")
print("total bytes \(raw.count)")
}The pointer is valid inside the closure and nowhere else — storing it and using it afterwards is undefined behavior, exactly as returning the address of a C local is. That is the whole design: Swift is willing to hand you the address, and insists on bounding the window in which it means anything. The same pattern appears as
withUnsafeMutableBufferPointer for writing, and withUnsafeBytes for the untyped byte view.Where They Meet: Importing C
Calling C With No Wrapper At All
This is the destination the page points at, and it needs almost no explanation: import the platform module and the C standard library is simply there, with its own names, taking its own types.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>
int main(void) {
printf("strlen %zu\n", strlen("hello"));
printf("abs %d\n", abs(-5));
printf("sqrt %.4f\n", sqrt(2.0));
printf("strcmp %d\n", strcmp("apple", "apple") == 0);
return 0;
}import Foundation
#if canImport(Darwin)
import Darwin
#else
import Glibc
#endif
print("strlen \(strlen("hello"))")
print("abs \(abs(-5))")
print("sqrt \(String(format: "%.4f", sqrt(2.0)))")
print("strcmp \(strcmp("apple", "apple") == 0)")No binding generator, no wrapper layer, no build step — the module is the C headers, imported. The reason it works is that Swift ships a Clang importer: it parses the real header, maps each declaration to a Swift one, and applies naming rules along the way. Notice what the conversion did quietly: a
String literal became a NUL-terminated UTF-8 buffer for the call, and size_t came back as an Int you can print.Importing Your Own Header
The same mechanism works for your code: a module map points at a header, and every function, struct and macro in it becomes available. Nothing is generated and nothing has to be kept in step, because the header is the interface.
/* geometry.h */
#ifndef GEOMETRY_H
#define GEOMETRY_H
typedef struct {
double x;
double y;
} Point;
double point_distance(Point first, Point second);
#endif
/* geometry.c */
#include "geometry.h"
#include <math.h>
double point_distance(Point first, Point second) {
double dx = first.x - second.x;
double dy = first.y - second.y;
return sqrt(dx * dx + dy * dy);
}// module.modulemap, beside the header:
//
// module Geometry {
// header "geometry.h"
// export *
// }
//
// Then, with the module on the import path:
import Geometry
let first = Point(x: 0, y: 0)
let second = Point(x: 3, y: 4)
print(point_distance(first, second)) // prints 5.0
// The imported struct gets a memberwise initializer, its fields are
// Swift Doubles, and it is a value type — laid out exactly as the C
// one, because it IS the C one.Three things arrive for free and are worth knowing about. The C struct becomes a Swift struct with the identical layout, so passing it to and from C copies nothing. A C enum becomes a real Swift enum, so a
switch over it is exhaustive. And a function-like macro does not come across — only object-like ones that are simple constants — which is the one place the importer gives up and you write a small inline function in the header instead.Handing Swift Code to C as a Callback
A C library taking a function pointer —
qsort is the canonical one — can be given a Swift closure, with one hard restriction: the closure must capture nothing, because a C function pointer has nowhere to put captured state.#include <stdio.h>
#include <stdlib.h>
static int compare_integers(const void *left, const void *right) {
int first = *(const int *)left;
int second = *(const int *)right;
return (first > second) - (first < second);
}
int main(void) {
int numbers[5] = { 10, 9, 1, 100, 20 };
qsort(numbers, 5, sizeof numbers[0], compare_integers);
for (int index = 0; index < 5; index++) {
printf("%d ", numbers[index]);
}
printf("\n");
return 0;
}#if canImport(Darwin)
import Darwin
#else
import Glibc
#endif
var numbers: [Int32] = [10, 9, 1, 100, 20]
// @convention(c): no captures allowed, because a C function pointer
// has nowhere to store them.
let compare: @convention(c) (UnsafeRawPointer?, UnsafeRawPointer?) -> Int32 = {
left, right in
let first = left!.load(as: Int32.self)
let second = right!.load(as: Int32.self)
return first < second ? -1 : (first > second ? 1 : 0)
}
numbers.withUnsafeMutableBufferPointer { buffer in
qsort(buffer.baseAddress!, buffer.count,
MemoryLayout<Int32>.stride, compare)
}
print(numbers.map(String.init).joined(separator: " "))Both columns print the same sorted line, with the C library doing the sorting in each.
@convention(c) is the annotation that makes the closure a genuine C function pointer, and it is why a capture is rejected at compile time rather than corrupting something later. MemoryLayout<Int32>.stride rather than .size is the right multiplier here for the reason two rows back — qsort wants the array stride.A Struct Laid Out Like a C One
The layout of an ordinary Swift struct is not guaranteed — the compiler may reorder fields. A struct imported from a C header keeps the C layout by construction, which is the reliable way to share a record between the two.
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
struct WireHeader {
uint8_t kind;
uint32_t length;
uint16_t flags;
};
int main(void) {
struct WireHeader header = { 7, 1024, 3 };
printf("size %zu\n", sizeof header);
printf("kind at %zu\n", offsetof(struct WireHeader, kind));
printf("len at %zu\n", offsetof(struct WireHeader, length));
printf("flags at %zu\n", offsetof(struct WireHeader, flags));
unsigned char *bytes = (unsigned char *)&header;
printf("bytes ");
for (size_t index = 0; index < sizeof header; index++) {
printf("%02x ", bytes[index]);
}
printf("\n");
return 0;
}import Foundation
struct WireHeader {
var kind: UInt8
var length: UInt32
var flags: UInt16
}
var header = WireHeader(kind: 7, length: 1024, flags: 3)
print("size \(MemoryLayout<WireHeader>.size)")
print("stride \(MemoryLayout<WireHeader>.stride)")
print("kind at \(MemoryLayout<WireHeader>.offset(of: \WireHeader.kind) ?? -1)")
print("len at \(MemoryLayout<WireHeader>.offset(of: \WireHeader.length) ?? -1)")
print("flags at \(MemoryLayout<WireHeader>.offset(of: \WireHeader.flags) ?? -1)")
withUnsafeBytes(of: &header) { raw in
print("bytes " + raw.map { String(format: "%02x", $0) }.joined(separator: " "))
}The three offsets agree, and so does
stride against C’s sizeof — both 12. What differs is size, which reports 10: Swift counts the bytes the value actually occupies and excludes the two bytes of tail padding that sizeof includes, which is why the Swift byte dump is two bytes shorter. Multiply by stride, never size, when allocating for a C API. And note that the agreement is observed rather than promised: Swift does not guarantee the layout of an ordinary struct, so when it must be right, declare it in a C header and import it. Both columns print the machine’s own byte order, so a wire format still needs explicit swapping.