Theme 07 · Effect / Rust / OCaml-Style Design
Rust-like safety comments
Explanation
Rust-like safety comments
Plain Human Explanation
Rust uses unsafe blocks for operations the compiler cannot fully protect. A good Rust team treats each one as a small contract: this is dangerous, and here is why this exact use is safe.
TypeScript does not have unsafe blocks, but product code still has dangerous moments: deleting files, moving money, granting access, trusting external input, skipping retries, or bypassing a normal validation path. A safety comment belongs next to those moments when the type system cannot express the full rule.
The point is not to decorate risky code. The point is to make the hidden product promise visible at the exact line where a future edit could break it.
Technical Explanation
A safety comment should explain the invariant that makes a dangerous operation acceptable. An invariant is a rule that must remain true, such as “this path was built by our storage module” or “this refund key is unique for this charge.”
Good safety comments are short, local, and backed by code. They usually appear after a parser, constructor, permission check, path normalizer, state transition, or transaction guard has already narrowed the data.
Weak safety comments say “this is safe” without showing why. Strong safety comments point to the guard that made it safe and name the consequence if someone removes that guard.
Why It Matters
- User impact: fewer accidental deletes, duplicate charges, leaked records, or confusing support cases.
- Product behavior: risky operations stay tied to the rule that makes them legitimate.
- Risk: future edits can bypass a guard because the danger looked like normal application code.
- Decision point: add this when a wrong edit would damage data, money, access, privacy, or recovery.
The Core Move
Put the guard in code, then put a compact “why this dangerous line is safe” comment beside the dangerous line.
Small Example
Rust-like safety comments: Small Example
Bad TypeScript Example
type Storage = {
delete(path: string): Promise<void>;
};
export async function deleteAvatar(pathFromRequest: string, storage: Storage) {
await storage.delete(pathFromRequest);
}
type Storage = {
delete(path: string): Promise<void>;
};
export async function deleteAvatar(
pathFromRequest: string,
storage: Storage,
) {
await storage.delete(pathFromRequest);
}
Good TypeScript Example
type AvatarPath = {
value: string;
};
type Storage = {
delete(path: string): Promise<void>;
};
function avatarPathForUser(userId: string): AvatarPath {
return { value: `avatars/${userId}.png` };
}
export async function deleteAvatar(userId: string, storage: Storage) {
const path = avatarPathForUser(userId);
// Safety: this path is constructed by avatarPathForUser, so callers cannot delete an arbitrary storage key.
await storage.delete(path.value);
}
type AvatarPath = {
value: string;
};
type Storage = {
delete(path: string): Promise<void>;
};
function avatarPathForUser(
userId: string,
): AvatarPath {
return {
value: `avatars/${userId}.png`,
};
}
export async function deleteAvatar(
userId: string,
storage: Storage,
) {
const path = avatarPathForUser(userId);
// Safety: this path is constructed by avatarPathForUser, so callers cannot delete an arbitrary storage key.
await storage.delete(path.value);
}
What Changed
- The bad version lets request input decide which storage object to delete.
- The good version builds the storage key from a trusted local rule.
- The safety comment explains the guard that makes the delete acceptable.
Realistic Example
Rust-like safety comments: Realistic Example
This example uses account export cleanup. The risky operation is permanent file deletion, so the code should show why the file is truly owned by the export being expired.
Bad TypeScript Example
type CleanupRequest = {
exportId: string;
filePath: string;
};
type ExportStore = {
markDeleted(exportId: string): Promise<void>;
};
type ObjectStorage = {
delete(path: string): Promise<void>;
};
export async function expireExport(request: CleanupRequest, store: ExportStore, storage: ObjectStorage) {
await storage.delete(request.filePath);
await store.markDeleted(request.exportId);
}
type CleanupRequest = {
exportId: string;
filePath: string;
};
type ExportStore = {
markDeleted(
exportId: string,
): Promise<void>;
};
type ObjectStorage = {
delete(path: string): Promise<void>;
};
export async function expireExport(
request: CleanupRequest,
store: ExportStore,
storage: ObjectStorage,
) {
await storage.delete(request.filePath);
await store.markDeleted(request.exportId);
}
Good TypeScript Example
type ExportRecord = {
id: string;
ownerId: string;
storageKey: string;
status: "ready" | "expired";
};
type ExportStore = {
findReadyExport(exportId: string): Promise<ExportRecord | null>;
markDeleted(exportId: string): Promise<void>;
};
type ObjectStorage = {
delete(path: string): Promise<void>;
};
function expectedExportPrefix(exportId: string) {
return `exports/${exportId}/`;
}
export async function expireExport(exportId: string, store: ExportStore, storage: ObjectStorage) {
const exportRecord = await store.findReadyExport(exportId);
if (!exportRecord) return { ok: false, error: "export-not-ready" };
if (!exportRecord.storageKey.startsWith(expectedExportPrefix(exportRecord.id))) {
return { ok: false, error: "storage-key-mismatch" };
}
// Safety: the ready export record came from our store and the key still matches this export's owned prefix.
await storage.delete(exportRecord.storageKey);
await store.markDeleted(exportRecord.id);
return { ok: true };
}
type ExportRecord = {
id: string;
ownerId: string;
storageKey: string;
status: "ready" | "expired";
};
type ExportStore = {
findReadyExport(
exportId: string,
): Promise<ExportRecord | null>;
markDeleted(
exportId: string,
): Promise<void>;
};
type ObjectStorage = {
delete(path: string): Promise<void>;
};
function expectedExportPrefix(
exportId: string,
) {
return `exports/${exportId}/`;
}
export async function expireExport(
exportId: string,
store: ExportStore,
storage: ObjectStorage,
) {
const exportRecord =
await store.findReadyExport(exportId);
if (!exportRecord)
return {
ok: false,
error: "export-not-ready",
};
if (
!exportRecord.storageKey.startsWith(
expectedExportPrefix(exportRecord.id),
)
) {
return {
ok: false,
error: "storage-key-mismatch",
};
}
// Safety: the ready export record came from our store and the key still matches this export's owned prefix.
await storage.delete(
exportRecord.storageKey,
);
await store.markDeleted(exportRecord.id);
return {
ok: true,
};
}
What Changed
- The bad version trusts a caller-provided file path for a permanent delete.
- The good version reloads the export record and checks the storage ownership rule first.
- The safety comment names the two guards a reviewer should preserve: trusted record lookup and prefix ownership.
System Example
Rust-like safety comments: System Example
At system scale, safety comments are useful around operations that cross product boundaries: storage, billing, permissions, email, or data retention.
Larger System-Level Bad TypeScript Example
type RetentionPorts = {
users: {
findInactiveUsers(): Promise<
Array<{
id: string;
email: string;
fileKeys: string[];
}>
>;
};
files: {
delete(key: string): Promise<void>;
};
audit: {
write(event: string, payload: unknown): Promise<void>;
};
};
export async function runRetentionJob(ports: RetentionPorts) {
const users = await ports.users.findInactiveUsers();
for (const user of users) {
for (const key of user.fileKeys) {
await ports.files.delete(key);
}
await ports.audit.write("retention.deleted", user);
}
}
type RetentionPorts = {
users: {
findInactiveUsers(): Promise<
Array<{
id: string;
email: string;
fileKeys: string[];
}>
>;
};
files: {
delete(key: string): Promise<void>;
};
audit: {
write(
event: string,
payload: unknown,
): Promise<void>;
};
};
export async function runRetentionJob(
ports: RetentionPorts,
) {
const users =
await ports.users.findInactiveUsers();
for (const user of users) {
for (const key of user.fileKeys) {
await ports.files.delete(key);
}
await ports.audit.write(
"retention.deleted",
user,
);
}
}
Larger System-Level Good TypeScript Example
type RetentionCandidate = {
userId: string;
cutoffDay: string;
fileKeys: string[];
};
type RetentionPorts = {
users: {
findRetentionCandidates(cutoffDay: string): Promise<RetentionCandidate[]>;
};
files: {
delete(key: string): Promise<void>;
};
audit: {
write(
event: string,
payload: {
userId: string;
fileCount: number;
cutoffDay: string;
},
): Promise<void>;
};
};
function ownedRetentionKey(userId: string, key: string) {
return key.startsWith(`users/${userId}/retained/`);
}
export async function runRetentionJob(cutoffDay: string, ports: RetentionPorts) {
const candidates = await ports.users.findRetentionCandidates(cutoffDay);
for (const candidate of candidates) {
const ownedKeys = candidate.fileKeys.filter((key) => ownedRetentionKey(candidate.userId, key));
for (const key of ownedKeys) {
// Safety: retention candidates come from the retention query, and this key is still under the user's retained-file prefix.
await ports.files.delete(key);
}
await ports.audit.write("retention.files_deleted", {
userId: candidate.userId,
fileCount: ownedKeys.length,
cutoffDay,
});
}
}
type RetentionCandidate = {
userId: string;
cutoffDay: string;
fileKeys: string[];
};
type RetentionPorts = {
users: {
findRetentionCandidates(
cutoffDay: string,
): Promise<RetentionCandidate[]>;
};
files: {
delete(key: string): Promise<void>;
};
audit: {
write(
event: string,
payload: {
userId: string;
fileCount: number;
cutoffDay: string;
},
): Promise<void>;
};
};
function ownedRetentionKey(
userId: string,
key: string,
) {
return key.startsWith(
`users/${userId}/retained/`,
);
}
export async function runRetentionJob(
cutoffDay: string,
ports: RetentionPorts,
) {
const candidates =
await ports.users.findRetentionCandidates(
cutoffDay,
);
for (const candidate of candidates) {
const ownedKeys =
candidate.fileKeys.filter((key) =>
ownedRetentionKey(
candidate.userId,
key,
),
);
for (const key of ownedKeys) {
// Safety: retention candidates come from the retention query, and this key is still under the user's retained-file prefix.
await ports.files.delete(key);
}
await ports.audit.write(
"retention.files_deleted",
{
userId: candidate.userId,
fileCount: ownedKeys.length,
cutoffDay,
},
);
}
}
What Changed
- The bad version deletes every stored key on a broad user object.
- The good version narrows the job input to retention candidates and filters keys by ownership.
- The safety comment sits at the delete call, where a future edit could create the user-facing harm.
When To Use It
Rust-like safety comments: When To Use It
Use This When
- The code performs a dangerous operation the type system cannot fully prove safe.
- A future edit could remove the guard and cause data loss, duplicate money movement, privacy exposure, or bad access.
- The guard is already present in code and the comment helps a reviewer connect the guard to the risky line.
Avoid This When
- The code can be made safe with a clearer type, constructor, parser, or permission check.
- The comment would only restate what the next line already says.
- The operation is routine and has no meaningful product, data, privacy, or money risk.
Tradeoffs
Safety comments add noise if they are used everywhere. They are most valuable when they mark a small number of lines that deserve extra care during review.
Related Concepts
- OCaml-ish domain modules
- Effect preferences
- Errors-as-values thinking
Practice Prompt
Rust-like safety comments: Practice Prompt
Beginner Exercise
Find one delete, refund, permission grant, or external-call line. Write down what must be true for that line to be safe.
Intermediate Exercise
Move the guard for that operation closer to the dangerous line. Add one short safety comment that names the guard, not just the danger.
Stretch Exercise
Replace one safety comment with a stronger type or constructor. Keep the comment only if there is still a rule TypeScript cannot express.
Reflection Question
Would a future reviewer know exactly which guard must not be removed?
Suggest an edit
Leave a private editorial note. This creates a GitHub issue for this curriculum page.