Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

A couple comments have mentioned that whatever issue my post raises, are minor compared to the simplicity of the language. But the current implementation of GenericWriter + AnyWriter (with some performance pitfalls) seems more, not less, complicated. Also, neither of these, nor anytype, lend themselves to _simple_ documentation, so that seems like another strike against the simplicity-argument.

As for anytype specifically, in simple cases where it's being used in a single function, you can quickly figure out what it needs.

But in non trivial cases, the parameter can be passed all over the place, including into different packages. For example `std.json.stringify`. Not only does its own usage of `out_stream: anytype` take more than a glance, it passes it into any custom `jsonStringify` function. So you don't just need to know what `std.json.stringify` needs, but also any what any custom serialization needs.



> But in non trivial cases, the parameter can be passed all over the place

Like pretty much any parameter in any language? That's abstraction.

Re jsonStringfy, that's a way for a type to control how its own instances are serialized as JSON. std.json.stringify doesn't depend on the details of a jsonStringfy implementation (nor a caller of std.json.stringify). A type being able to implement a method to customize how instances of the type are serialized is a common/typical feature of a lot of JSON serializers.


> Like pretty much any parameter in any language? That's abstraction.

It’s crappy abstraction. Why make every user of the interface figure out how to use it properly? And guess what functions are required and which are always available?

Rust traits seem like a strictly better tool here. You get exactly the same emitted code as anytype, but the expected interface is explicit and well documented. Any time a trait name appears, you can cmd+click the trait name and see what functions are parts of the trait. It’s clean and explicit.

And traits can also have associated types (eg for an error type). They can have default implementations of functions. You can store a trait object in a struct. And you can use &dyn Trait if you want dynamic dispatch instead of monomorphisation. If this article is anything to go by, Zig makes all of this stuff difficult to impossible.

Anytype only solves the problem for the compiler. Traits solve the problem for the compiler and the programmer at the same time.


> If this article is anything to go by, Zig makes all of this stuff difficult to impossible.

Well, having done it, it’s not.


Oh go on then - how do you make a zig interface that has equivalent behaviour to a rust trait? I want something that has a documented interface, default function implementations, can be stored in other structs, and allows both monomorphization and dynamic dispatch.

If the authors of the zig standard library can't do it, I assume its impossible. And hence this article talking about 3 different definitions of a "writer" appearing in the standard library, each trying to make up for shortcomings of the others. You have anytype, AnyWriter and GenericWriter. All with weird and complex tradeoffs and interactions. Not only do people need to learn 3 different interfaces, they also need to figure out how to convert from one kind of writer to another!

I talked to Rob Pike, back before Go hit version 1.0. I told him I really thought he should have not only enums, but parametric enums in Go. Of course, he disagreed. He said enums don't add enough value. Honestly I'm not sure if he'd ever used a language with parametric enums - and he didn't understand their value. Having used typescript, and now rust and swift, going back to C or Go and needing to build my own enums out of union structs and so on is awful. It feels like I'm trying to build a house using a rock and a piece of flint.

I see rust's trait system as just like that. What a pity that Zig doesn't have something similar. To me, traits seem like an obvious thing to copy into the language.


It'd be a bit gnarly, but somebody could absolutely write a utility capable of providing those features in the userspace of the language:

1. Documented Interface: Documentation would be a struct with the desired function declarations (combined with some type-checking, I spit-balled an idea for that recently [0]).

2. Default Function Implementations: The exact tool you'll use will vary since the language hasn't hit 1.0, but a `pub usingnamespace FakeInheritanceFooBarBaz(TheTrait, @This());` could have the desired effect.

3. Stored in other structs: Rust doesn't even really give you this. It has syntactic sugar to make the dynamic case easier, and it has the ability to store concrete implementations. For the latter, Zig just works. For the former, you want something that automagically wraps any trait-conforming object in a dynamic pointer-based structure. That's a weak point in the language since the metaprogramming facilities for creating functions and for creating objects with decls aren't very solid (that will get fixed by 1.0). You could make do for now by using comptime fields instead of decls in the wrapper logic that generates this thing (think `foo: fn (A) B` instead of `fn foo(A) B {}`), and that generation code would have to handle different argument counts by hand.

4. Monomorphization: You'd get that for free, since this is just ordinary comptime duck-typing.

5. Dynamic (runtime dispatch: This is really identical to the work for (3).

[0] https://news.ycombinator.com/item?id=42899354


I followed 80% of what you've said, but not all of it. I'd love to read a sketch of this in code.

How would you clean up Writer in std?


I'll try to put together a PoC a little later. I won't have time till at least this evening though, maybe later.


It's a very nontrivial amount of code to make the library that provides all of those features, even worse to do it well. Here's a PoC for one of them [0] (default implementations).

The broad theme is that other languages use keywords for traits or subclasses or whatever. In Zig, you can define libraries using the language's metapgrogramming facilities that do the same thing (or similar things).

Zig doesn't have a complete metaprogramming story yet either, which makes it harder (no custom types with custom decls, can't create a function dynamically, ...), but there are still a few options for creating something that looks like an interface.

In my mind, all the obvious solutions have something like a "template" and utilities that operate on that template. The template serves as documentation and also a hint to the LSP. Its exact semantics aren't important; they just have to match however that library decides the implementation should look.

[0] https://github.com/hmusgrave/zinter


Awesome! Nice work. I’m still astounded by what you can pull off with comptime.


Comptime is amazing. It's a new language, but I'm excited. I got my team excited too, for some fantastic benefits at work.


> Like pretty much any parameter in any language? That's abstraction.

This is only an issue in languages with duck-typing though. You're not really abstracting something if you need to go look how it's used to understand what to pass.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: