Why doesn't Zig have an unary + operator?

Most likely the answer will be “it is to avoid complexity, and unary + isn’t needed, since it doesn’t do anything”.

However, it does do something. It keeps our mathematical equations explicit, and aligned in a way that helps us easily read our code, verify our code, and to find bugs easily, in case we make some mistake with our equations.

If we had unary + operator, I would not have this weird right kink, in my j equation, which shifts the equation weirdly to the right, because I could use the + operator on the other two equations to align them perfectly.

Some will most likely argue that this is not an issue.

It is an issue: For readability. For reducing the mental load, while working on mathematical equations. Especially for mathematical equations that are expected to be symmetric when written out.

Question is whether Zig devs want to fix it, or ignore it.

    pub fn cross(A: Self, B: Self) Vec3 {
        const x, const y, const z = .{ A.x, A.y, A.z };
        const a, const b, const c = .{ B.x, B.y, B.z };

        const i = (y * c - b * z);
        const j = -(x * c - a * z);
        const k = (x * b - a * y);

        return .{ .x = i, .y = j, .z = k };
    }

1 Like

Alignment seems about the least important use case of the unary plus. Usually I’ve seen it used for type coercion - especially in C.

Do you have other examples where it would be beneficial in Zig rather than using @intCast or similar?

Adding a unary plus operator would make things like const y = ++x; valid syntax, which could make people believe that Zig supports ++ operators. (Though I suppose the same could be said about --x which is currently valid and just returns x)

3 Likes

Would you consider just adding a couple spaces where the plus sign would be and switching off zig format?

2 Likes

This seems sensible to me, in clang-format I’ve often escaped its formatting. Maybe theres a way we can do this with zig fmt?

Not sure what the symmetry is you are using. I find the following more natural:

const i = (y * c - b * z);
const j = (z * a - c * x);
const k = (x * b - a * y);
8 Likes

Yes, you can turn off Zig format for regions of code using a control comment. It’s in the docs.

1 Like

I guess you can always make the implicit zero explicit:

        const i = 0 + (y * c - b * z);
        const j = 0 - (x * c - a * z);
        const k = 0 + (x * b - a * y);
7 Likes

Tongue in cheek - making the symmetry mechanical rather than textual and thus forgoing any formatting friction:

fn cross(x: [3]f64, y: [3]f64) [3]f64 {
    var res: [3]f64 = undefined;
    inline for (0..3) |i| {
        res[i] = x[comptime (i + 1) % 3] * y[comptime (i + 2) % 3] - y[comptime (i + 1) % 3] * x[comptime (i + 2) % 3];
    }
    return res;
}

You don’t need comptime everywhere. Arithmetic is always comptime if all inputs are known at compile time, which is the case for the inline for index.

2 Likes

You might be interested in this proposal which suggests that zig fmt should have a mode that automatically aligns tabular data:

In addition to solving your problem without introducing a redundant unary + operator, it would also help with the related problem of right-aligning integers or aligning numbers on the decimal point:

const positions: [][2]f32 = &.{
   .{   31.4,     83.0   },
   .{  481.3,   -717.333 },
   .{   40.0,      0.900 },
   .{ -300.0,    -31.0   },
   .{    1.333,    0.75  },
   .{  -73.1,     10.87  },
};
4 Likes

I for one miss unary plus. It “offends” my sense of symmetry to be able to say -11 but not +11. But I don’t have much hope it will be added.

1 Like

Be aware that in the case of IEEE 754 floats, 0 + x or 0 - y are not the same as x and -y because of signed zeroes. You’ve added additional operations that the compiler is not allowed to optimize out (unless compiling with @setFloatMode(.optimized)) because +0 + -0 is +0, not -0, and +0 - +0 is +0, not -0.

4 Likes

To be specific, that’d be:

   // zig fmt: off
    const i =  (y * c - b * z);
    const j = -(x * c - a * z);
    const k =  (x * b - a * y);
    // zig fmt: on
3 Likes

My questions are, Why was it removed? and What did they benefit by removing it?

At worst, a unary + operator is a NOP, thus can be ignored after parsing. So, why not just parse it, and allow us to write our mathematical equations, in the way we like?

We can understand when some controversial features are removed from a language, but removing something that’s conventionally used in all languages, seems counter-productive.

As usual, we provide a simple example to highlight an important issue, and instead of addressing the issue, everyone tries to solve the example in a different way to show that the issue doesn’t exist.

We want unary + operator in Zig. It is not difficult to implement it. Every language includes it for a reason. It’s conventional, it’s symmetric, and necessary for explicitly align mathematical equations, where different + - * / operations can exist on different lines.

There’s no need rewrite/fix our examples because it defeats the purpose.

We want the feature, and if Zig devs don’t think they can support it, we want their explicit statement, and their reason behind why they’ll not support something so crucial for mathematics.

Sorry, I know it can be annoying when people focus on the example rather than the point. Genuinely curious though, since it’s never something I’ve wanted to use, if you can give some more examples where it would really help?

I unfortunately do not know how to provide an example that will make others see why we’d want something like this.

Unary + operator doesn’t do anything. At worst, it is a NOP operation. At best it can be ignored after being parsed, and doesn’t even need to be semantically analyzed.

However many of us who write lot of mathematical code want it, because it is symmetric to the unary - operator. Whenever we use a unary - operator, we can choose to use a unary + operator, to explicitly imply that the value is intended to not be changed, while wherever we used - operator, should be changed, and negated.

Sure on the surface level we mostly use it for aligning + and - operations, on consecutive lines, so they are easier to read, and that is a significant reason behind why we want it.

Being able to read something easily is important for us, because when we write somewhat well organized/aligned code, we can find bugs/issues in our code easily.

I can not explain this. You have to experience this yourself.

However whatever our reasons maybe, the issue happened because Zig devs decided to remove a feature that is available in all languages, without any valid reason.

  • We know pre/post increment/decrement operators (++ and --) should not exist, because they cause a lot of bugs.
  • We know pointer arithmethic causes lots of bugs.
  • We know implicit type casts cause bugs.
  • We know operator/function overloading makes the language complex, etc.

Most of us can understand why some features were not included in the language, because we understand that it makes the language simpler, and safer. Even if a feature was causing issues it should be allowed (in some cases), such as labeled goto, computed goto, pointer arithmetic, etc. because in certain cases, they’re necessary.

However it is difficult to understand why the unary + operator was removed.

It was not harming anyone.

So we want it back.

1 Like

Just FYI, there has never been a unary + operator in any version of Zig as far as I’m aware. Using the earliest released version of Zig:

test "foo" {
    const a = +5;
}
>.\zig.exe version
0.1.1
>.\zig.exe test test.zig
C:\Users\Ryan\Downloads\zig-win64-0.1.1\test.zig:2:15: error: invalid token: '+'
    const a = +5;
              ^
1 Like

You are insulting.
The OP’s point is absolutely valid.
Having to turn the formatter off requires to know the syntax for it and adds visual clutter to the source.

1 Like