Skip to content

Proposal: todo #24328

@matklad

Description

@matklad

TL;DR

Introduce a new expression, todo, which is coercible to any type, traps at runtime in any build
mode, and is used to communicate incomplete code which is being actively authored right now:

pub fn eval(op: Op, lhs: u64, rhs: u64) i64 {
  return switch (op) {
    .add => lhs +% rhs,
    .sub => lhs -% rhs,
    .mul => lhs *% rhs,
    .div => if (rhs == 0)
      todo // Figure out how to signal division by zero.
    else
      lhs / rhs
  };
}

Proposed todo is a bit like undefined, and a bit like unrechable, but clearly communicates
that the code itself is incomplete.

Motivation & Use Cases

Direct Use

The simplest case is for direct usage by the programmer when they write code! It's hard to
write everything right from start to finish, often it is useful to spike only the central use-case
and happy path, and just "stub out" all the more tricky cases. Today, you can use unreachable or
@panic("todo") for this use-case, and they work OK. However, unreachable can be confused with
intentional unreachable, and @panic("todo") is a pain to type. While optimizing typing is not
the goal in general, when you are spiking out solution it becomes relatively more important.

Another subtle point is that unreachable and @panic() are typed as noreturn, but occasionally
you want to type something like

const x: Thing = todo;
x.frobnicate();

and get compilation erros for the code with follows.

todo is simple to type, simple to delete, and unambigious.

"IDEs"

The primary motivation comes from "IDE" land. IDEs offer various automated refactors, and very
often
you want to explictly mark parts of the code to be filled-in by the user. For example,
returning to the original example, a typical IDE feature would be for the user to type
return op.switch, mash tab key, and get that exanded to

return switch (op) {
  .add => todo,
  .sub => todo,
  .mul => todo,
  .div => todo,
};

Again, an IDE can generate @panic("todo"),, but then it'll be harder to replace for the user (due
to ,, just dW won't work).

Depending on how deep you want to go into the IDE-land, you can imagine strctural-editing-like
experience, where if exands to if (todo) todo else todo, and then tab key cycles through
todos. In general, explicit todo makes building something like
hazel, but text easier.

Again, this is not a deal breaker, @panic("todo") would work ok, but todo would be nicer.

Resilitent Compilation

In some sense this is a continuation of the previous use-case, but interally, and IDE might want
to be resilient to compilation errors. To provide type-guided features in downstream code, an
IDE-concious compiler might want to treat erroneous code const x = xs.no_such_method() like
const x = todo;. Having an explicit todo makes that sort of desugaring more natural! Note, however, that implementing resilient compilation is explicitly out of scope for this proposal.

Proposal

Add a todo expression, which behaves like a cross between undefined and @panic():

  • Like undefined, todo is typed using RLS, it is not noreturn.
  • But, like @panic, undefined is guaranteed to generate a trap.
  • Unlike both, it is runtime-known.

todo can desugar as

if (runtime_true()) @panic("todo") else undefined

where runtime_true is just an anti-comptime barrier:

fn runtime_true() bool { return true; }

Here's a todo test:

pub fn todo_test() []const u8 {
    const x = todo;
    const y: i32 = todo;
    todo;
    @as(std.time.Timer, todo).reset();
    return todo;
}

This function should compile succesfully. Notably, it shouldn't give "unreachable code" errors.
At runtime, under any optimization mode, it should trap when evaluating const x = todo.

Metadata

Metadata

Assignees

No one assigned

    Labels

    proposalThis issue suggests modifications. If it also has the "accepted" label then it is planned.

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions