Hacker News new | ask | show | jobs
by tiehuis 3206 days ago
I've been writing a fair bit since a few months ago, and have written a few things for the stdlib. Here are some examples I like about it.

Hassle-free error management

Consider you write a function

  fn div(a: u8, b: u8) -> u8 {
      a / b
  }
If later you find an error case that needs to be handled updating the code usually will not require much extra addition.

  error DivideByZero;
  fn div(a: u8, b: u8) -> %u8 {
      if (b == 0) {
          error.divideByZero
      } else {
          a / b
      }
  }
The caller can choose to ignore errors using `%%div(5, 1)` or can propagate errors to the caller, similar to Rust's `try!`, `?` using `%return div(5, 2)`.

I find this so easy that I'm much more inclined to think about edge cases and handle errors up front. I find when writing Rust the extra setup and management of errors adds a fair bit of tedium (although to be fair, with error-chain and proper setup at the beginning of a project this isn't too bad).

Compile-time programming

Zig has pretty strong compile-time programming support. For example, its printf formatting capability is all written in userland code [1]. It doesn't at this moment support code-generation like D's mixins but I personally have not found this too problematic.

Generic functions can be written in a duck-typing fashion. With compile-time assertions the inputs can be limited to what they need pretty clearly and the errors during usage are pretty self-explanatory.

  error Overflow;
  pub fn absInt(x: var) -> %@typeOf(x) {
      const T = @typeOf(x);
      comptime assert(@typeId(T) == builtin.TypeId.Int); // must pass an integer to absInt
      comptime assert(T.is_signed); // must pass a signed integer to absInt
      if (x == @minValue(@typeOf(x))) {
          return error.Overflow;
      } else {
          @setDebugSafety(this, false);
          return if (x < 0) -x else x;
      }
  }
Zig doesn't have any form of macros. Everything is done in the language itself.

[1]: http://ziglang.org/documentation/#case-study-printf

2 comments

>The caller can choose to ignore errors using `%%div(5, 1)` or can propagate errors to the caller, similar to Rust's `try!`, `?` using `%return div(5, 2)`.

>I find this so easy that I'm much more inclined to think about edge cases and handle errors up front. I find when writing Rust the extra setup and management of errors adds a fair bit of tedium (although to be fair, with error-chain and proper setup at the beginning of a project this isn't too bad).

How is this different than say:

   5_u8.checked_div(1).unwrap()
That unwrap() is performing the same thing as your %% example, if I understand it correctly.

Are the Error types in Zig on the stack or the heap? By default Rust puts everything, including errors, on the stack, which means that the size of the return type always needs to be known. To make this easier you can return Boxed errors:

   fn this_errors() -> Result<u32, Box<Error>> { ... }
And then error types can be very simple. Also, I recommend people getting into Rust really checkout error_chain!, which is a macro that helps in combining all the errors that your library might need to deal with: https://docs.rs/error-chain/0.11.0/error_chain/
Yes you have it right, `unwrap()` is equivalent to `%%`. It will panic if the result is an error.

Error values under the hood are just unsigned integers and are returned on the stack. In fact, the granularity at which allocators are exposed in the stdlib makes any possible dynamic allocation very explicit in the language.

This link [1] provides an overview of errors and some of the surrounding control flow.

[1]: http://ziglang.org/documentation/#errors

Ah. Thanks for the link. I didn't see anything that section talk about passing values with the Errors, based on your comment, is that even possible?

If you had an error you wished to pass a reason string for, would you need to use a global static or something?

Not with the builtin error type. Its pretty much analogous to a c error code.

If you wanted to send data you would need some other means like you suggest. I'd be interested in finding an ergonomic solution to this but it probably wouldn't be at the language level.

You say that Zig has no macros, just like FORTRAN, but that isn't a language property at all. Technically, C has no macros, but most people use the C preprocessor. I've seen people preprocess with sed scripts, perl scripts, tr, python scripts, and m4. FORTRAN too was often preprocessed.

So we'll take your Zig and shove it through a preprocessor or several. I've seen code get preprocessed 3 times during builds, but I'm sure that isn't a record.

If a language designer fails to provide a suitable well-matched and effective preprocessor, we'll add something nasty. Oh well. Stuff has to get done.

> Technically, C has no macros, but most people use the C preprocessor

technically the C preprocessor is a non-optional part of the C language and specification (and in fact embedded in the actual C compiler binary in many implementations). So both in theory and in practice C very much has a macros.

/pedantic