Zig pipe operator

The problem is that Zig currently requires explicit casting in many situations that wouldn’t lose information. It’s a known issue though, and tbf, Rust is much worse in that regard.

Personally I would put the ‘casting noise’ very high on the list of issues that need to be fixed, because all that required explicit casting basically doesn’t let you see the forest for the trees (e.g. it becomes much harder to read what an integer math expression actually does).

3 Likes

I don’t want to ask you to do look it up, but do you happen to have an example or link to an issue handy?

Yes, I was able to remove a lot of casting when porting code from Rust to Zig, mostly because Rust doesn’t have implicit integer widening and Zig does.

The one case where I notice I have to cast more than expected in Zig (because I did not have to cast in Rust) is when I want a for (0..non_usize_val) |x| loop over something that is not a usize. I have seen the outstanding issues on that one, so perhaps it will be improved.

Yes, I’ve also experienced that issue. Also I’ve wanted to iterate over ranges that include negative values.

I listed a couple examples in the linked blog post. For instance here it’s clear that the result fits into a u4, but a cast is still necessary (e.g. this doesn’t compile without an @intCast):

fn trunc4(val: u8) u4 {
  return val & 0xF;
}

…same with a rightshift, the result fits into an u4 without data loss, but a cast is still needed, e.g. this also doesn’t compile:

fn bla(val: u8) u4 {
  return val >> 4;
}

…and here’s another one: the loop counter fits into an u4, but still needs to be casted:

for (0..16) |_i| {
  const i: u4 = @intCast(_i);
}

(PS: that link I talked about was in another reply… Zig and Emulators , search for Bit Twiddling and Integer Math can be awkward)

1 Like

Thanks for this info!

1 Like

It’s a known issue though,

I have hopes that it might get solved one day:

1 Like

Im sure you are aware, but for others not.

should address these cases.

1 Like

I also want this feature.
I tried a workaround using if (optional) |payload_capture| ... else unreachable
https://godbolt.org/z/M795r9646

1 Like

You seem to be looking for the .?operator

1 Like

I personally have the goal of trying myself on writing a Zig compiler for High Level Synthesis. But at earliest I will try that after 1.0. Way too big of a problem space to try before that. But that’s something people will run into there too.

If you don’t know what High Level Synthesis is, it’s compiling a high level language like C or C++ down to a bitstream you can put on an FPGA or build hardware with. Also a part of that often includes a library like SystemC (as described in IEEE 1666-2011).

just using opt_value.?

I intentionally made val optional so it can be used with if payload capture.

    const y1 = baz(bar(foo(val)));
 // const y2 = val
 //            |a| foo(a)
 //            |a| bar(a)
 //            |a| baz(a);
    const y3 = if (makeOptional(
               if (makeOptional(
               if (makeOptional(val))
               |a| foo(a) else unreachable))
               |a| bar(a) else unreachable))
               |a| baz(a) else unreachable;

this is just unreadable.

This is the Zig way:

const y = y: {
    const f = foo(val);
    const b = bar(f);
    break :y baz(y);
};
5 Likes

Alternatively, in a functional language where these operators make more sense (like Haskell with $ and . and many others) you would use a lambda to give the value a name (\x => x + x) since those operators usually act as various forms of function composition. It works very naturally with function currying as well.


Functional languages (and the shell) are usually designed from the bottom up, including the standard lib, with pipe-like operators in mind.

Lots of design principles and idioms as well related to the order of function arguments. Getting the order of the arguments wrong (like some functions in Hugo’s template system) makes it harder to write correct code. I don’t know if this style is a good fit for zig.

1 Like