Hacker News new | ask | show | jobs
by nindalf 1577 days ago
Serious question about Zig’s comptime - isn’t that just like Rust’s const fn, except every function is implicitly opted in to being const?

Where this could be a problem - I write a comptime function, only using other functions that I’ve verified can be executed at compile time. But now the implementation details of those functions (that they’re comptime) has leaked to their definition. Now a change to the impl of those functions could break my code, without the authors of those functions realising it.

Perhaps this is not a problem in practice?

2 comments

Kinda, but much more than that and the ergonomics are quite different as the entire language is basically available. For example in Zigs comptime, types are first class citizens which can be queried and operated on. This gives you generics without a lot of type system magic, and even const generics over arbitrary types. All without a macro system or Turing-Complete type checking (similar in flavour to the DeBruijn criterion: the comptime code/higher order proof system is turing complete but the type/core checking is straight-forward).

And yes the problem you describe could exists, but I've not encountered anything like it in practice. But the idea you had there captures the difference between Zigs and Rusts type-system/generics quite nicely:

> Rust tries to proof universally qualified correctness and behaviour of code, i.e. regardless of its context, while zig only proofs you the correctness of specific instantiations.

While this may sound like a bug, I'd argue that it's a feature, as it's YAGNI applied at a type level, and for example allows you to do things like:

  switch (builtin.target.cpu.arch) {
    .x86_64 => //do intel stuff
    .aarch64 => //do arm stuff
    else => @panic("unknown arch!");
  }

With the switch arms that don't apply simply being ignored and never type checked. This allows you to use a basic language construct where C requires a preprocessor and Rust requires macros or compiler magic.
The Rust solution for your arch variation specifically is hardly onerous, and does just the same, ignoring the code in not-matching branches:

  #[cfg(target_arch = "x86_64")]
  …

  #[cfg(target_arch = "aarch64")]
  …

  #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
  compile_error!("unknown arch!");
(If you got to much more complex cfg branches, cfg-if could be worthwhile, which lets you write `cfg_if! { if #[cfg(…)] { … } else if #[cfg(…)] { … } else { … } }`. But for only a few simple ones, I prefer to keep it out.)
Yes, it can happen. In practice is not a big deal because the kind of stuff that you would want to run at comptime is pretty much all logic that manipulates its inputs without other side-effects, but one could for example add logging to a function and break its ability to be run at comptime (Zig might add in the future ways of solving this problem).

I would say that more in general the problem between public interface vs implementation details is much bigger than any type system and requires humans to negotiate what should be considered part of the public interface through other means (eg: tests, comments). Comptime is one example, another is ABI stability (eg the layout of a struct, or just its size), another could be speed or memory consumption.

Zig doesn't have an official package manager yet, we'll see what happens once we get one and people in the community have to start communicating stability guarantees to their users.