Skip to content

add "select" syntax to the language to await the first function in a given set that completes #5263

Open
@andrewrk

Description

@andrewrk

I thought I had already proposed this but I could not find it. This depends on the cancel proposal being accepted, which is related to #3164 but I will open an explicit proposal for it.

There is one thing missing with zig's async / await features which is the ability to respond to the first completed function. Here's a use case:

fn httpRequestWithTimeout(url: []const, timeout_duration: u64) !Response {
    var request_tok = CancelToken{};
    var request = async httpRequest(url, &request_tok);
    defer request_tok.cancel(&request);

    var timeout_tok = CancelToken{};
    var timeout = async sleep(timeout_duration, &timeout_tok);
    defer timeout_tok.cancel(&timeout);

    select {
        request => |result| {
            request_tok.awaited = true;
            return result;
        },
        timeout => {
            timeout_tok.awaited = true;
            return error.TimedOut;
        },
    }
}

Here's a more complicated example, which shows how select also supports arrays of frames:

fn httpRequestWithMirrors(mirrors: []const []const u8) !Request {
    const frames = try allocator.alloc(@Frame(httpRequest), mirrors.len);
    defer allocator.free(frames);
    const cancel_tokens = try allocator.alloc(CancelToken, mirrors.len);
    defer allocator.free(cancel_tokens);

    for (frames) |*frame, i| {
        cancel_tokens[i] = CancelToken{};
        frame.* = async httpRequest(mirrors[i], &cancel_token[i]);
    }

    defer for (cancel_tokens) |*tok, i| tok.cancel(&frames[i]);

    var in_flight_count: usize = mirrors.len;
    while (true) {
        select {
            frames => |result, i| {
                cancel_tokens[i].awaited = true;
                if (result) |payload| {
                    return payload;
                } else |err| {
                    in_flight_count -= 1;
                    if (in_flight_count == 0)
                        return err;
                }
            },
        }
    }
}

Here's another use case:

/// Add a frame to the Batch. If all jobs are in-flight, then this function
/// waits until one completes.
/// This function is *not* thread-safe. It must be called from one thread at
/// a time, however, it need not be the same thread.
/// TODO: "select" language feature to use the next available slot, rather than
/// awaiting the next index.
pub fn add(self: *Self, frame: anyframe->Result) void {
const job = &self.jobs[self.next_job_index];
self.next_job_index = (self.next_job_index + 1) % max_jobs;
if (job.frame) |existing| {
job.result = if (async_ok) await existing else noasync await existing;
if (CollectedResult != void) {
job.result catch |err| {
self.collected_result = err;
};
}
}
job.frame = frame;
}

As an alternative syntax, we could replace select with await. Since only labeled blocks can return values, await { is unambiguously the "select" form of await.

Metadata

Metadata

Assignees

No one assigned

    Labels

    acceptedThis proposal is planned.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