-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Description
Motivation
It's already an established pattern in the Zig ecosystem to deprecate declarations first via a doc comment, and after a while by turning the old decl into a @compileError once the deprecation "grace period" is over, to then finally delete the decl altogether after some more time.
Currently a user that is interested in discovering early which APIs are being deprecated must grep the codebase for the word, find the decl the comment refers to, and figure out if and where its being used (directly or not) in the codebase.
Conversely, a library author that wants to maintain backwards compatibility (i.e. avoid a major version bump for a v1 package) will not want to set deprecated decls to @compileError as that would be a compatibility breakage, never giving users an opportunity to leverage compile errors to migrate their API usage.
It is possible today to implement a better solution entirely in userland:
// build.zig
fn build(b: *std.Build) void {
const opts = b.addOptions();
opts.addOption("deprecated", b.option(
bool,
"deprecated",
"turn deprecated decls into compile errors",
) orelse false);
}// root.zig
const options = @import("options");
fn oldFoo() void {
if (options.deprecated) {
@compileError("deprecated, use newFoo()");
}
}
/// Deprecated: use `GenericWriter`
const Writer = if (options.deprecated) @compileError("deprecated, use `GenericWriter`") else GenericWriter;Running zig build -Ddeprecated will turn deprecated decls in compile errors giving both a nice compiler-driven upgrade experience to users, while letting library authors maintain backwards compatibility.
This pattern seems so useful that might be worth enshrining it as a standardized practice.
Proposal
Introduce a @deprecated() builtin and a new -fdeprecated compiler flag to be used like so (this is a contrived example to showcase the workflow):
// main.zig
const std = @import("std");
const num = @deprecated(10);
pub fn main() void {
std.debug.print("num: {}\n", .{num});
}$ zig run main.zig
num: 10
$ zig run main.zig -fdeprecated
main.zig:3:13: error: found deprecated code
const num = @deprecated(10);
^~~~~~~~~~~~~~~
referenced by:
main: main.zig:6:34
posixCallMainAndExit: zig/lib/std/start.zig:647:22
4 reference(s) hidden; use '-freference-trace=6' to see all references
A less contrived example would be the current deprecation of std.time.sleep in the Zig standard library:
/// Deprecated: moved to std.Thread.sleep
pub const sleep = std.Thread.sleep;pub const sleep = @deprecated(std.Thread.sleep);A second example: using @deprecated in a function body:
const Strategy = enum { greedy, expensive, fast };
fn compute(comptime strat: Strategy, comptime foo: bool, bar: usize) void {
switch(strat) {
.greedy => {
// This strategy turned out to be bad when foo is false,
// use the fast strategy instead.
if (!foo) @deprecated();
runGreedy(foo, bar);
},
.expensive => runExpensive(foo, bar),
.fast => runFast(foo, bar),
}
}Lastly, since @deprecated conveys intent more precisely, tooling like LSPs and linters could offer diagnostics about deprecation automatically to the user in a more streamlined fashion than with the full userland solution.
A branch with @deprecated implemented can be found here: https://github.com/kristoff-it/zig/tree/deprecated-proposal (diff)