01 Rust-like safety comments 6 chapters

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.

  • 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.