News for hackers. Stuff that matters.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by adamdusty on Thursday July 15, 2021 @05:13PM (#27850195)
I want to like Zig, but I also want operator overloading because multiplying matrices together in chains of functions is among the ugliest code I've ever had to look at. There have been more than a couple decent suggestions for making it clean and obvious, but there are a few things that Andrew isn't willing to budge on.

> defer is pure genious

Being able to defer for resource cleanup is nice. I like that Zig makes it a priority to make things adaptable (not sure that's the right word). For example, everything in the std lib that allocates, takes an allocator object, so if you want to implement a custom allocator, it's very simple to use. If you want to use a custom event loop for async, it's easy to substitute. All in all, I think its shaping up to be a nice language, just not for me.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by gameswithgo on Thursday July 15, 2021 @07:13PM (#27851194)
Every day I see people avoiding entire interesting languages for bad reasons. Little things they are used to and don’t yet know how to work around the change, but you would learn and it would be fine.

parens braces semicolons operator overloading generics etc

the lack or presence of these things are never deal breakers, it can be worth trying to adjust because maybe you will find 900 things you love about the new way that make up for the one or two things you think you can’t stand.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by jolux on Thursday July 15, 2021 @08:17PM (#27851727)
I’ve yet to find the perfect language but that doesn’t mean I’m satisfied with the imperfections that exist in the ones I use. It’s perfectly reasonable to not use a language for lack of operator overloading. I personally avoid languages developed with contempt for functional programming and type systems. We all have our reasons.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by seg_lol on Thursday July 15, 2021 @09:16PM (#27852073)
Python wants you back in 3.10
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by IshKebab on Friday July 16, 2021 @06:11AM (#27854679)
Not until it gets its static typing act together, and runs faster than a 1-legged dog.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by seg_lol on Saturday July 17, 2021 @08:28PM (#27869875)
Python 3.11 has its eye on you.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by adamdusty on Thursday July 15, 2021 @09:45PM (#27852229)
I've used zig for projects before. It's a nice language with some improvements on C, just not ones I care about. I did adjust, finished the projects I was doing, and I decided to move on to something else. I had a problems with the C interop as well. It's likely that I was doing something wrong, but the documentation is poor and I want something that just works.

Again, the language is great. It's a young project. I'm glad you like it. Andrew seems to have a very firm idea of where Zig is headed. It's just not for me, in part because operator overloading is not a "little thing" for me.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by dnautics on Friday July 16, 2021 @12:19AM (#27853019)
What projects did you finish? Do you have any links? Curious to know what people do and don't have trouble with, would be nice to see some naive code
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by ByteJockey on Thursday July 15, 2021 @08:24PM (#27851772)
> the lack or presence of these things are never deal breakers

Sure, but it can definitely break a tie. Frankly, operator overloading can create some really nice apis. And code that's pleasing to read takes less mental overhead to pick up.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by wirrbel on Friday July 16, 2021 @12:45AM (#27853107)
Yes and no. I'd take a non-operator overloaded API any time of the abuse operator overloading is used for in the Haskell ecosystem.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by judofyr on Thursday July 15, 2021 @05:28PM (#27850362)
I was actually thinking about writing a “macro” (i.e. function) which takes in a math expressions as a string and an anonymous struct for these use cases:

    eval("Ax + v", {.A = …, .x = …, v = …})
Wouldn’t that be cool?
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by mkishi on Thursday July 15, 2021 @07:03PM (#27851128)
That's indeed nice for math expressions.

I'm a bit sad there seems to be no interest in adding interpolation syntax to Zig. While anonymous tuples work well for shorter expressions, they can get confusing for larger textual templates or DSLs, imo.

I've been experimenting with Zig for WASM, for example, and the readability of HTML templates suffers. I'd love to be able to pass arguments inline, and while we can do that with anonymous tuples, the added noise makes it annoying to use in practice. I believe Javascript's template literals, out of all things, would be a fitting inspiration for a little syntactic sugar on top of anonymous tuples:

  var tuple_params = html.create("{} ... {} ... {}", .{a, b+c, observable});
  
  var tuple_inline = html.create(.{"", a, " ... ", b+c, " ... ", observable, ""});
  
  // equivalent to tuple_inline
  var tuple_sugar = html.create(`{a} ... {b+c} ... {observable}`);
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by ifreund on Thursday July 15, 2021 @07:28PM (#27851295)
zig's standard library `std.fmt` functions support named arguments as well:

  var tuple_params = html.create("{[foo]} ... {[bar]} ... {[observable]}", .{
    .foo = a,
    .bar = b+c,
    .observable = observable,
  });
It's verbose and explicit to be sure, but quite readable IMO.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by kristoff_it on Thursday July 15, 2021 @06:01PM (#27850624)
That's fairly easy to implement with comptime.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by pron on Thursday July 15, 2021 @05:48PM (#27850531)
> because multiplying matrices together in chains of functions is among the ugliest code I've ever had to look at.

OK, but how much code is it? I doubt even a game engine has matrix operations in more than 0.1% of its lines of code (obviously, in Matlab/Julia it can by 99%). The beauty of so little code is a worthy sacrifice for the explicitness of a low-level language.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by kettlecorn on Thursday July 15, 2021 @06:47PM (#27851005)
It also comes up with vector addition, subtraction, and multiplication, all of which are used all over in gameplay code.

I like Zig overall, but lack of operator-overloading just seems like a pain to me.

Zig in many ways puts faith in the coder to not screw things up, but in this case it diverges from that philosophy. Would it be too much to trust the user not to overload operators in an unacceptable way? Social convention over technical constraint.

That said I'd be more persuaded by the argument that it's not worth the extra code and complexity for a language as concise as Zig.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by pron on Thursday July 15, 2021 @07:25PM (#27851269)
> lack of operator-overloading just seems like a pain to me.

It is a pain, but all things considered, having it would have been a greater pain. Note that the main issue with operator overloading in Zig isn't so much the operator part but the overloading part, because overloading introduces ambiguity that cannot be resolved at the code-unit level. Zig doesn't allow name overloading even for ordinary identifiers (actually, Zig is more strict in its opposition to overloading than Clojure or Erlang, which do allow it when there can be no ambiguity). I think it's more likely that Zig will allow user-defined infix operators (say +' or something) before it allows overloading of any kind.

> Zig in many ways puts faith in the coder to not screw things up

I understand Zig very differently. Zig puts a lot of effort to make it hard for the coder to screw up. Even where it doesn't enforce correctness at compile time or at runtime (and it does both much more than C++, to the point that the goal is to eliminate all or nearly all undefined behaviour in safe mode), its strong emphasis on correctness, including functional correctness, is expressed precisely through a simple and lean language that's easy to understand, so that it's harder for the programmer to screw up (plus fast compilation and easily isolated code units). So to the extent that Zig puts faith in the coder (less than C++, more than Rust), it can do so because the language is so lean and simple.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by dnautics on Friday July 16, 2021 @12:34AM (#27853076)
> I think it's more likely that Zig will allow user-defined infix operators (say +' or something) before it allows overloading of any kind.

I proposed this and it got rejected by the language committee.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by pron on Friday July 16, 2021 @04:54AM (#27854265)
I meant that they'd sooner allow some infix operators than overloading, not that they intend to add operators in the language's first version.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by losvedir on Thursday July 15, 2021 @11:11PM (#27852698)
> because overloading introduces ambiguity that cannot be resolved at the code-unit level

Can you explain this a bit? Operator overloading just seems like such a strange bugbear of Zig folks to me. Maybe I've just never been bitten by it, but at least in Elixir, my main language, I've never really understood the danger.

Are operators truly so different from functions? You'd expect that any function you import could do, well, anything. Aren't operators essentially just the same thing, just with a "prelude" in the language that basically imports them by default?

In Elixir, you might have (discouraged):

    defmodule MyMod do
      import MyOperators
and then you know to check what's defined by MyOperators, for that module. But more common, if you really wanted to overload, would be to be more specific:

    defmodule MyMod do
      import MyOps, only: [+: 2]
and then you know plus (with two arguments) has a different meaning in that module.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by throwaway17_17 on Thursday July 15, 2021 @11:31PM (#27852803)
I’m not super familiar with Zig specifically, aside from watching some of Andrew’s talks and coding streams. But this thought process is not a Zig only concept. As far as I know, the explicit ‘operators’ that are provided with Zig are guaranteed to be a low-level construct (ideally implemented at the ISA level) which are made visible to users of the language in standard ways, i.e. infix arithmetic operators. When overloading of these operators is allowable that guarantee goes out the window. There is a fundamental difference at the generated code level between a function call resulting from a use of ‘’ to multiple two random matrices and a use of ‘’ to multiple two 32 but integers. Further, as I understand it, Zig guarantees that anything that allocates memory does so explicitly and anything that calls a function does so explicitly. The very concept of operator overloading is incompatible with such a guarantee. So, I think given the implied goal of explicit is better, it makes sense that a flat rejection of operator overloading is the broad view.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by xfer on Friday July 16, 2021 @02:56AM (#27853655)
So zig doesn't support 64bit arithmetics on 32/16/8bit architectures? What about division? Quite a few hardware don't support division( https://community.arm.com/developer/ip-products/processors/b...).

> The very concept of operator overloading is incompatible with such a guarantee.

Why would operator overloading allocate memory? The memory is already allocated(by the user) when you call with the operands.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by dinglejungle on Friday July 16, 2021 @05:16AM (#27854412)
> Why would operator overloading allocate memory?

What if I wanted to overload the + operator to concatenate strings?

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by xfer on Friday July 16, 2021 @03:37PM (#27860341)
Yes that was poorly thought out. I realized it after i commented.

There is definitely some merit in being puritanical about not having any allocations in operators.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by pron on Friday July 16, 2021 @04:44AM (#27854213)
> Operator overloading just seems like such a strange bugbear of Zig folks to me.

Not operator overloading; just overloading. You can't overload ordinary-named subroutines in Zig either.

> and then you know plus (with two arguments) has a different meaning in that module.

That's not overloading, that's shadowing. Overloading is when the same name refers to multiple things depending on the type of its arguments. Erlang allows overloading based on arity, but that's something that can be understood from the call-site code.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by pcwalton on Friday July 16, 2021 @12:10AM (#27852983)
> overloading introduces ambiguity that cannot be resolved at the code-unit level

This seems clearly false to me. Operator overloading is non-virtual type-based dispatch, just like the dot operator for method calls in C++/Java/etc., and is resolved statically at compile time. You can think of overloadable "+" as just syntactic sugar for an ".add()" method (in fact, this is more or less exactly how it's implemented in Rust). Even if your language doesn't have methods, you can implement overloading with typeclasses, like Haskell does (as does Rust, formally speaking).

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by pron on Friday July 16, 2021 @04:46AM (#27854229)
Operators are a red herring here; what Zig disallows is overloading of names, period. Yes, overloading is resolved statically at compile time, but that's not the point (Zig does allow dynamic dispatch). The point is that a simple understanding of what the code does requires information available outside the unit (subroutine or even file). A subroutine can contain two references to `foo` resolving to two different targets, but you can't know that from the code in the subroutine. The meaning of a name must be defined in one place in Zig, and a name can only refer to one thing.

We could certainly argue on whether disallowing overloading is helpful, but it's first important to understand what the principle is, and it's not about the operators.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by pcwalton on Friday July 16, 2021 @05:03AM (#27854340)
> The point is that a simple understanding of what the code does requires information available outside the unit (subroutine or even file).

No, it doesn't. Knowing what function "+" resolves to requires only the type of the left-hand side (or, I think, the RHS as well in Haskell). Absent overlapping instances (which Rust doesn't support, and Haskell only supports with an extension), there can only be one such function implementation per type. And, since top-level items are fully type-annotated in Rust (and idiomatically annotated in Haskell), this is a completely local determination. Therefore, for every occurrence of "+" in the source, it's completely unambiguous which implementation "+" calls, and this can be determined solely by looking at the calling function.

The end result: Rust, like Zig, disallows overloading of names, but Rust also effectively supports overloaded operators via traits (typeclasses).

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by pron on Friday July 16, 2021 @05:43AM (#27854553)
> there can only be one such function implementation per type

Right, and that's more than one definition per name. In Zig, a name refers to only one definition. (Plus, recall that in Zig the type of a target isn't always known when reading the code, probably more frequently than in C++ or Rust, where this only happens in macros.)

> disallows overloading of names

That the definition referred to by an identifier depends on the type is what overloading means. Overloading is the very essence of traits.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by pron on Friday July 16, 2021 @07:58AM (#27855367)
P.S.

> in C++ or Rust, where this only happens in macros

Sorry, this is wrong. In both of these languages it happens much more frequently. All the time, in fact, whenever you either infer return types or just chain calls on return values, or when the type is a generic/template type parameter. So when you see `foo(e)` or `e.foo()`, where `e` is some expression, you can only tell which definition of `foo` is referenced -- or even whether two instances of `foo` refer to the same definition or not -- by figuring out the type of `e`, which is often implicit in the code.

> Overloading is the very essence of traits.

Which is why in Zig, the mechanisms that perform a similar task to that of traits -- i.e. abstracting over types -- work differently, without overloading.

As a general rule, Zig does not want the semantics of a piece of code to depend on things the compiler might know but the reader of the unit might not. Whether or not such a degree of explicitness is a worthy principle for a low-level language is a matter of taste, I suppose.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by flohofwoe on Thursday July 15, 2021 @06:55PM (#27851066)
IMHO better than operator overloading (which opens the gates to hell) would be builtin vector and matrix types like in shading languages. Currently Zig has a vector type builtin (search for @Vector and/or std.meta.Vector), which supports the "simple" operations (addition, subtraction etc...), so it's halfway there, but no swizzle, and no matrix types.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by Hoppetosse on Thursday July 15, 2021 @07:22PM (#27851244)
The @Vector provided is actually a reference to SIMD vectors and there has been discussion to rename it to something that reflects this more clearly.

https://github.com/ziglang/zig/issues/7305

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by flohofwoe on Friday July 16, 2021 @06:10AM (#27854672)
I would love to see Clang's ext_vector_type extension features in Zig, it lets you write code like GLSL, including easy swizzling. Add the same thing for matrix types, and the the feature would be complete, no need for operator overloading:

https://www.godbolt.org/z/zWGfcY5a4

Whether this code translates to SIMD or scalar operations under the hood should depend on the target platform and compiler options.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by Zababa on Thursday July 15, 2021 @10:20PM (#27852429)
Would it be possible to do a slight Zig fork that works a bit like Rust, where + calls the add(), - the sub(), and * the mul() designed specifically for cases that needs it? Or maybe do it by declaring something at the top of your file? That could be a good compromise.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by dnautics on Friday July 16, 2021 @12:30AM (#27853067)
I did this, but the code underneath changed on me with the self-hosted compiler... it's not hard if you really wanted to. (~200 LOC)
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by Arnavion on Thursday July 15, 2021 @07:07PM (#27851150)
Having used defer in golang, I'm firmly in the camp that scope-based cleanup like defer is bad and value-based cleanup like destructors is better.

Say you have this (pseudocode):

    function foo() {
        let bar = make_bar();
        defer cleanup_bar(bar);
        let baz = make_baz();
        defer cleanup_baz(baz);
        let quux = make_quux();
        defer cleanup_quux(quux);

        do_something_with(bar, baz, quux);
    }
Now imagine you have multiple functions like this that all create bar, baz and quux's. Say these are unit tests and the bar-baz-quux are mocks or whatever.

So you decide to DRY by moving the creation to a common function.

    function create_mocks() -> (Bar, Baz, Quux) {
        let bar = make_bar();
        defer cleanup_bar(bar);
        let baz = make_baz();
        defer cleanup_baz(baz);
        let quux = make_quux();
        defer cleanup_quux(quux);

        (bar, baz, quux)
    }
... except this doesn't work any more, because the cleanup happens within create_mocks and create_mocks ends up returning cleaned-up values.

You could fix this by switching to a callback approach:

    function run_with_mocks(f: function(Bar, Baz, Quux)) {
        let bar = make_bar();
        defer cleanup_bar(bar);
        let baz = make_baz();
        defer cleanup_baz(baz);
        let quux = make_quux();
        defer cleanup_quux(quux);

        f(bar, baz, quux)
    }
... but now you have to make the caller use a callback that necessarily returns void and not any other type. (This is mostly a golang problem, since languages with generics can be generic on the return type of the callback.) It also means you can't easily do things like set variables in an outer scope easily, depending on how the language deals with closure captures.

Worse, if not all tests use the same mocks, you actually end up with multiple of these functions:

    function with_bar(f: function(Bar)) {
        let bar = make_bar();
        defer cleanup_bar(bar);

        f(bar)
    }

    ...

    function foo() {
        with_bar(bar => {
            with_baz(baz => {
                with_quux(quux => {
                    do_something_with(bar, baz, quux);
                });
            });
        });
    }
All these problems are avoided by having destructors.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by axaxs on Thursday July 15, 2021 @11:00PM (#27852630)
I'm not arguing against destructors, but this example is ultra contrived. Why would you defer cleaning up objects you are returning? That's no longer your responsibility.

Instead you'd return an object, and make it the callers responsibility to close it. Destruct all three in the close call.

It may not fit your mental model, and that's fine. But let's not pretend it's a serious hurdle.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by fauigerzigerk on Friday July 16, 2021 @05:36AM (#27854524)
>It may not fit your mental model...

No, it's not a psychological problem. This is real complexity, and it's one of the most serious and widespread issues in software development.

The key issue is that we often have rules that say: Whenever A happens then B must happen as well. It is absolutely critical that rules like this are expressed in exactly one place and that enforcement is opt-out not opt-in.

Everything else is error prone and creates significant mental burden.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by dnautics on Thursday July 15, 2021 @07:21PM (#27851240)
This is not really a problem in zig. If you really need a destructor, write a .deinit() function in your struct, and call that. For your case with mocks (note mock as a noun, not a verb, which is a bit of an antipattern anyways, see: http://blog.plataformatec.com.br/2015/10/mocks-and-explicit-...). That aside:

Go doesn't have generics so you can't really do this, but in the case of zig, your function would be parametrized on the type, so you would issue it a struct to be shared by the tests that has the common initialization upfront, make_ functions that return the pre-initialized stuff, followed by running the function, followed by no-op deinits, followed by whatever forensics you'd like to run later.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by Arnavion on Thursday July 15, 2021 @07:30PM (#27851316)
>This is not really a problem in zig. If you really need a destructor, write a .deinit() function in your struct, and call that.

This is missing the point. The point of a destructor is that it runs automatically. .deinit() is the same as the three cleanup_*()s in my example and thus has the same problems.

>but in the case of zig, your function would be parametrized on the type, so you would issue it a struct to be shared by the tests that has the common initialization upfront

The third example in my post shows why this is inadequate.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by dnautics on Thursday July 15, 2021 @09:26PM (#27852129)
Then im not really sure what your point is, because I've written zig code that reuses code in a similar way to what you are talking about, with three different forms of parallelism calling into some common runner code each of which has different needs for resource cleanup times (single threaded, cooperative, and independent parallelism). It wasn't a problem.

Have you tried doing what you propose in zig? Ultimately, the only difference between a .deinit() and a destructor is that one is explicit and the other is not. There are cases where you can easily make a mistake for both an explicit destructor and an implicit destructor.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by AndyKelley on Thursday July 15, 2021 @07:33PM (#27851334)
> except this doesn't work any more

it does in zig:

    fn create_mocks() !struct {bar: Bar, baz: Baz, quux: Quux} {
        var bar = try make_bar();
        errdefer cleanup_bar(bar);
        var baz = try make_baz();
        errdefer cleanup_baz(baz);
        var quux = try make_quux();
        errdefer cleanup_quux(quux);

        return .{
            .bar = bar,
            .baz = quux,
            .quux = quux,
        };
    }
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by Arnavion on Thursday July 15, 2021 @07:36PM (#27851364)
You've misunderstood. The thing that's being tested is not make_*, it's do_something_with()
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by dnautics on Friday July 16, 2021 @12:25AM (#27853042)
You didn't read the function signature.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by Arnavion on Friday July 16, 2021 @04:15AM (#27854029)
I did.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by lhorie on Thursday July 15, 2021 @07:31PM (#27851324)
But that's because you're doing continuation passing style. You could do this instead:

    func make_foo() *Foo {
      return &Foo {
        bar: make_bar(),
        baz: make_baz(),
        quux: make_quux(),
      }
    }
    func cleanup_foo(foo *Foo) {
      cleanup_bar(foo.bar)
      cleanup_baz(foo.baz)
      cleanup_quux(foo.quux)
    }

    // elsewhere
    foo := make_foo()
    defer cleanup_foo(foo)
    // [rest of logic goes here]
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by Arnavion on Thursday July 15, 2021 @07:38PM (#27851376)
Please see the third example I gave, which talks about the set of mocks not being the same for every test.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by lhorie on Thursday July 15, 2021 @07:50PM (#27851499)
There are several options

Poor man's:

    func make_foo_with_mock_bar() *Foo {
      return &Foo {
        bar: make_mock_bar(),
        baz: make_baz(),
        quux: make_quux(),
      }
    }
Explicit injection:

    func make_foo(bar: Bar, baz: Baz, quux: Quux) *Foo {
      return &Foo {
        bar: bar,
        baz: baz,
        quux: quux,
      }
    }
Or use some DI framework

Even if you have reservations about where verbosity lies, I don't see how this affects the cleanup side.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by Arnavion on Thursday July 15, 2021 @07:52PM (#27851517)
You've just restated your original comment, without addressing the fact that this does not work with the third example in my original comment.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by lhorie on Thursday July 15, 2021 @07:59PM (#27851583)
How so? You can mock any of bar/baz/quux however you want using the patterns/techniques above. Am I misunderstanding something?
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by Arnavion on Thursday July 15, 2021 @08:03PM (#27851621)
Again, your Foo always contains a bar, baz and a quux. And again, the third example in my original post talks about how not every test needs all three.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by lhorie on Thursday July 15, 2021 @10:02PM (#27852327)
Ah, I understand now. My understanding is that with zig and go, one does not DRY deferring, it's just a part of the idiom trade-offs between verbosity and explicitness.

So if bar, baz, quux aren't intrinsically related as a unit and might be mixed and matched arbitrarily, you just forego the whole ceremony and call each thing or the respective mock individually (the argument being that if you're making multiple different permutations, you're not repeating yourself per se; e.g. mocks don't need to be cleaned up in the first place)

There's also something to be said about tests needing several arbitrary mocks. Normally, I'd consider that a smell. I've actually seen the pattern of making a kitchen sink foo for scenarios like that, but with nils/noops for unused things (can't say I like that approach though).

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by dnautics on Friday July 16, 2021 @12:21AM (#27853032)
It's a huge code smell. It's funny because just today I was listening to a podcast about testing and they talk about exactly these sorts of contrivances as being a good sign you should not be mocking something in a test.

(https://smartlogic.io/podcast/elixir-wizards/s6e6-matthias-l...) if you're curious

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by AlexCoventry on Thursday July 15, 2021 @07:53PM (#27851521)
I thought you were looking for ways to DRY up creation of a set of mocks?
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by Arnavion on Thursday July 15, 2021 @07:57PM (#27851564)
I am.

With the three mocks I gave, there are 2^3 - 1 = 7 possibilities of which mocks a particular test needs. Of these, three are degenerate cases of a single mock each which would be served by the individual make_* already, so that leaves four cases of make_foo* that you'd need to implement.

With more than three mocks, this number grows rapidly.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by AlexCoventry on Thursday July 15, 2021 @07:46PM (#27851463)
The common approach in go is something like

    func newMocks(
    ) (
      foo Foo, bar Bar, baz Baz, 
      cleanup func(),
    )
and making the caller responsible for calling the cleanup function, possibly in a defer. To me, this seems like a good trade off between making a little more work for the caller and keeping things explicit and under the caller's control.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by Arnavion on Thursday July 15, 2021 @07:51PM (#27851509)
This doesn't work with the third example, ie where the set of mocks is not the same for every test. This suggestion devolves to the original code that we were trying to refactor.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by Mawr on Friday July 16, 2021 @05:49AM (#27854584)
Then the original code is perfectly fine and does not need refactoring. You create the mocks you need with cleanup defers and you're done.

You could just make a function that creates all the mocks needed for all tests, but you don't like that. So the only option left is to specify which mocks you need for each test... which is exactly what the original code is doing.

You're running into a wall because you're trying to abstract code that's already maximally abstracted.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by oconnor663 on Friday July 16, 2021 @04:58AM (#27854300)
I wonder if "conditionally send a local variable over a channel" is another good example of something that's hard to clean up with defer.

One upside of destructors is that adding destructor cleanup to a type isn't necessarily a breaking change. (In Rust terms, as long as the type isn't "Copy".) But in languages with defer or with/using statements, it is.

Another upside of destructors is that the cleaned-up state can be mostly hidden from other code. But when cleanup is a regular function, it's easier to access an object after cleanup, and you have to reason more about what happens in that case. (A Rust example here might be "What happens if you use a Box after it's freed?" The answer is that you just can't do that, even though Box doesn't have a freed/null state.)

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by j1elo on Friday July 16, 2021 @09:30AM (#27856022)
Wait, the moment a creator function returns its created values, it is also returning their ownership. So at least with that concrete DRY refactor you are showing, you're effectively not only moving code but also responsibilities around.

But I get your point: with destructors you are free to change the ownership responsibilities, because the link between a variable and its releasing is implicit and carried over by the runtime, instead of explicitly being cared for by the code.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by cmrdporcupine on Thursday July 15, 2021 @10:27PM (#27852478)
100%. And another problem with defer is that it only works in function scope, not block scope. This bit me in a piece of Go I had to refactor into multiple functions recently when all I wanted to do was a few lines change around a 'for' loop. The code review ended up being more substantial and it made the code less readable than just being able to sprinkle in some additional defers.

With the C++ destructor RAII pattern I can use it at any lexical scope I need it at, which is super handy.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by Jtsummers on Thursday July 15, 2021 @10:33PM (#27852499)
Zig's defers, at least, are block scoped. So they are executed at the end of whatever block they're created in, in contrast to Go's version of defer which is function scoped only.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by naikrovek on Thursday July 15, 2021 @11:47PM (#27852870)
RAII is very obviously a bad idea, and I'm shocked that more people can't see this. it's "fine" until it isn't, and when it bites you, it crushes your arm. you are unlikely to ever forget this after it happens. it's like driving a car fast while blindfolded; you will crash, and it will hurt, it's just a matter of when.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by pcwalton on Friday July 16, 2021 @12:07AM (#27852963)
Care to explain why? In Rust RAII works very well, and the common failure mode of RAII--use-after-free--is statically prevented by the borrow check.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by cmrdporcupine on Friday July 16, 2021 @10:45AM (#27856753)
Use-after-free is definitely the major footgun portion of RAII in C++ (in my experience) but seems to me like the same issue would be present in any non-GC'd language that offers a 'deferral' mechanism, asynchronous or not (unless it has ownership/borrow management like Rust as you point out).
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by marton78 on Friday July 16, 2021 @02:06AM (#27853444)
Your claims would benefit from you substantiating them.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by naikrovek on Friday July 16, 2021 @03:49PM (#27860468)
until you experience the problems yourself, you won't believe me, anyway, no matter what evidence I provide.

it is way too easy to critique an approach that led to a problem that hasn't happened to you, and is described to you by someone else.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by kitd on Friday July 16, 2021 @02:31AM (#27853539)
You can create "block-scope" with an immediately-called function.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by andrenth on Thursday July 15, 2021 @07:33PM (#27851336)
I would put the mocks in a Mock struct with a Cleanup() method and have the caller call defer mocks.Cleanup()
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by Arnavion on Thursday July 15, 2021 @07:37PM (#27851374)
Please see the third example I gave, which talks about the set of mocks not being the same for every test.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by axaxs on Thursday July 15, 2021 @11:58PM (#27852920)
You keep saying this, but it doesn't make a lot of sense.

Your initial pretense was that you always make 3 objects, so move it into a function.

When people show how to accomplish that, you state 'well what about my test that doesn't need all three?'

Why are you testing things your code doesn't do?

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by Arnavion on Friday July 16, 2021 @04:30AM (#27854135)
>When people show how to accomplish that, you state 'well what about my test that doesn't need all three?'

You've phrased this as if I said this after I'd been given a solution for the initial problem. In reality, I mentioned this in the original post itself, but it seems many people didn't read it.

It's almost like I have prior real-world experience of this problem, where I already went down the path of making common functions for all the creation and cleanup respectively, only to find that it doesn't help when all the functions don't create the same subset of things.

>Why are you testing things your code doesn't do?

I have no idea how you came to this conclusion.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by axaxs on Friday July 16, 2021 @11:32PM (#27863381)
Hey, I didn't mean to come across as rude but when I reread it, it was. I wasn't expressing myself well, I apologize.

I've been a Go mentor for years, and see how people take concepts they are used to and try to shoehorn them in, and complain they are ugly.

In this case, when I first saw your examples, my thought was 'youre doing it wrong'. But not wrong as in incorrect, but now how the language was designed.

If you truly care we can go through scenarios and I can tell you how I'd approach it. But in my past experience, when people find a paradigm they like, they tend to not listen.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by ghoward on Thursday July 15, 2021 @07:43PM (#27851421)
These are great examples. I knew that I preferred destructors over defer, but I could not explain why. You put it into words.

I think people have a bias against destructors because of C++, but they really are pretty nice. Speaking as a guy that uses C instead of C++, destructors are the one thing I would add to C.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by Arnavion on Thursday July 15, 2021 @07:49PM (#27851489)
They certainly have their problems. They're functions that get invoked without any code to explicitly invoke them. You have to consider whether a destructor can be invoked on a partially constructed object. You have to consider whether a destructor can be invoked more than once. You have to consider providing the ability to not call the destructor for performance. etc etc

It's understandable why a language trying to be close to C in spirit does not have them.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by ghoward on Thursday July 15, 2021 @09:33PM (#27852181)
Yes, those are all problems with destructors, but they are well understood. However, in a language I am currently creating, calling a destructor on a partial object is impossible because the object is created in "one" step. In other words, constructors themselves don't have access to the object in a partially created state.

It does make things much easier.

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by hnakamur on Thursday July 15, 2021 @08:13PM (#27851696)
I agree. For example, most of Rust libraries uses the drop handler (something like desctructors) to close a file descriptor. However it has problems since in async model closing is also async and it may return an error. So in tokio-uring explicit async close will be provided. https://github.com/tokio-rs/tokio-uring/pull/1/files#diff-3d...

And you can call close like: file.close().await.unwrap(); https://github.com/tokio-rs/tokio-uring/pull/17/files#diff-b...

Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by pcwalton on Friday July 16, 2021 @12:08AM (#27852967)
Note that there are ideas to fix this issue in the async foundations WG. It's arguably more of a problem with async as implemented in Rust than with the concept of RAII.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by candeira on Saturday July 17, 2021 @07:09AM (#27865000)
The motivation for explicit allocation is explicitness, not adaptability. The adaptability is great, but it's a consequence of the language designer's design principle that all function calls will be explicit via (), and all allocation will be explicit via allocator-passing, so no user of a library can be surprised that a call performs an allocation.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by majormajor on Thursday July 15, 2021 @05:27PM (#27850349)
Defer sounds like a nice improvement over old old languages, but Java's try-with-resources is one of the nicer ways of handling resource cleanup I've seen in a "mainstream" language, it's fairly neat and conceptually similar to some stuff I've seen done with lisp macros or Ruby blocks. It seems like defer is still lacking compared to this.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by ptrwis on Thursday July 15, 2021 @08:23PM (#27851766)
With defer it's developer's responsibility to free resources in right order, when one depends on another. Maybe something like this in Zig would be better:

  try std.fs.cwd().openFile(path, .{}) |file| : file.close()
  {
    // ... code ...
    // file.close() is called at the end
  }
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by dinglejungle on Thursday July 15, 2021 @08:35PM (#27851836)
defer in Zig is per-scope, so

    {
        var file = try std.fs.cwd().openFile(path, .{});
        defer file.close();

        // ... code ...
        // file.close() is executed here
    }
would work just fine.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by ptrwis on Thursday July 15, 2021 @10:01PM (#27852323)
Yes, but look at the example in the blog post of this thread- "stat" depends on "file", "file" depends on "path". You can call defer in any order in your code, the language syntactically doesn't enforce you to call defer right after resource acquisition. "try-with-resources" in Java (or "using" in c#) will always call "finalizers" in order opposite to how resources where created. Following the syntax I inveted above, for someone used to Java it would be easier to read than to think how defers are called:

  try std.fmt.allocPrint(allocator, "problems/{}", .{ id }) |path| : allocator.free(path) {
    try std.fs.cwd().openFile(path, .{}) |file| : file.close() {
      try file.stat() |stat| {
        try allocator.alloc(u8, stat.size) |contents| : allocator.free(contents) {
          // do something with 'contents'
        }
      }
    }
  }
In Zig you can write this, and mess everything up:

  const path = try std.fmt.allocPrint(allocator, "problems/{}", .{ id });
  var file = try std.fs.cwd().openFile(path, .{});
  const stat = try file.stat();
  var contents = try allocator.alloc(u8, stat.size);
  defer allocator.free(contents);
  defer file.close();
  defer allocator.free(path);
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by dnautics on Friday July 16, 2021 @12:29AM (#27853058)
That's what errdefer is for.
Re:Zig, Skia, Clojure, Geometry and the Japanese TV Show: IC... by dom96 on Thursday July 15, 2021 @06:47PM (#27851009)
If you haven't already then you might be interested in checking out Nim.