Unreadable anytype

So… this is from std.mem.
How on earth can I know what anytype is.

pub fn sortUnstable(
    comptime T: type,
    items: []T,
    context: anytype,
    comptime lessThanFn: fn (@TypeOf(context), lhs: T, rhs: T) bool,
) void {
    std.sort.pdq(T, items, context, lessThanFn);
}
3 Likes

Generally, we don’t need custom contexts, so just pass a void value {} to context.

Maybe it is great to also provide a more friendly API without context.

3 Likes

It is just a value that is passed as first argument in the lessThanFn.
You can use the void value {} if you don’t care.

Currently you can discover it by reading the source.

1 Like

It is whatever you want it to be. The function doesn’t use it. It simply passes it along to the function that you provide. You can use it for anything you want. If you don’t need it, just pass void.

2 Likes

Very strange. And how should the lessthan function look like?
I have a (sub) slice of structs i want to sort.

fn less_than(a: Node, b: Node) bool
{
    return a.data.code < b.data.code;
}

I miss a magic first parameter

Should be

fn less_than(_: void, a: Node, b: Node) bool
1 Like
const slice = self.nodes.items[node.child_ptr..node.child_ptr + node.count];
std.mem.sortUnstable(Node, slice, void, less_than);
fn less_than(_: void, a: Node, b: Node) bool
{
    return a.data.code < b.data.code;
}

error: expected type
‘fn (comptime type, gaddag.Node, gaddag.Node) bool’,
found
‘fn (void, gaddag.Node, gaddag.Node) bool’

note: non-generic function cannot cast into a generic function

You need to pass the value {}, not the type void.

std.mem.sortUnstable(Node, slice, {}, less_than);
1 Like

Working!!
Too bad there is no documentation.
Is there any chance this anytype is replaced with something else?
In my feeling I have to guess what is going on there.

What would you replace it with? It can be anything in this case.
I think there are cases where anytype parameters expect specific values and there it would be nice to be able to quickly identify what is accepted, but I don’t know how you would add such a syntax that still would be ziggy.

For example go uses some kind of Type-Variable constraint language for types,
but adding such a thing to Zig seems to me like giving up on finding a solution that matches the Zig spirit.

Currently you need to read the code, personally I think that is better than having an inaccurate replication of the code in the form of prose in cases where it would be hard to describe, however in this case it would be good if the documentation said something like “context is an arbitrary value provided as first argument to the comparison function”, the reason why it currently isn’t is probably because this is obvious and can be seen from the code, once you are familiar with this type of code and because the standard library is still written to serve the construction of the compiler and didn’t have its rewrite for more user friendly-ness.

Part of me would like to have a feature to be able to use “Type Checking” functions as parameter types instead of anytype, that would be invoked with a type and accept or reject it, however proposals along those lines have been rejected, as far as I can tell mostly because they would lead the language towards a tendency to create towers of abstractions like c++ tends to do, creating increasing complexity that has marginal value when compared to using simple assertions instead. Those constructs also tend to be just a declarative copy of what you already can do with assertions, thus they would be against the idea of “Only one obvious way to do things.”

When writing generic functions that use anytype it is simple to write comptime checkMyTypeCheck(@TypeOf(my_param)); as the first line of that function and such a function can provide a detailed error message for what was expected.
I am not totally on one side or the other, but so far I haven’t seen a proposal that actually fits Zig, but I also haven’t researched that deep.

Some issues related to this are:

I think reading these types of issues and what they discuss and link to, can give you more of a grasp of what has been proposed already, why certain solutions get rejected and possibly what areas / solutions could be researched.

But from what I have seen it seems like a very complex topic and difficult to find something that fits Zig and is an improvement over status quo, without just copying a feature from some other language.

Ideally somebody would find some slight addition/change to the language that sets of a series of small changes that leads to a different way of writing these generic functions without having to introduce huge new features, but still allows things like good documentation / integration with zls.

1 Like

I am aware of some existing solutions, like the traits in rust (aaargh, trauma) or interfaces in other languages, which are not 100% ok as well. Will read the links.
At least anytype should be airtightly documented if it stays like this.
I will try and follow the source and maybe get familiar…

For me it is a bit strange to see anytype. It looks frightening. Visually resembling completely untyped javascript or python.

We already see a lot of anytypes, like in the built in functions. Over-use would not be good is my opinion at least…

1 Like

It sort of is like that but only for code that runs at comptime and you always can find out the actual type with @TypeOf which makes it less scary, because you quickly can assert boundaries on what is possible.
I think when you are comfortable with writing comptime code it becomes less scary.

1 Like

comptime params I really love!

1 Like

If it helps:

fn foo(arg: anytype) void;
foo(my_arg);

Is essentially shorthand for

fn foo(T:type, arg: T) void;
foo(@TypeOf(my_arg), my_arg);
3 Likes

Indeed that should be mentioned. I get it now. And can kind of read it when following the code :slight_smile:

Interesting posts btw. There are more people who object against the way it is now. But I can imagine the difficulties changing it without keeping zig ziggy.

1 Like

This is the standard way to do callbacks in Zig. Since Zig doesn’t have closures, there needs to be some way for the code that passes in the callback to communicate information to that callback in case it needs it–for instance, your sort comparison function might do different things based on configuration settings. So in addition to the callback function, there should always be a context argument that is passed along to the callback function. Since that context could be anything–it’s just a means of the caller of sort to communicate with its lessthan function–it’s declared anytype. (BTW, there’s an open proposal to change that name, for esoteric semantic reasons.)

APIs that take callback functions but not context arguments are basically broken. There was recently a question on SO from someone using the Zap web framework (written in zig) wanting to know how to pass context info to his callback functions, but the API doesn’t provide for it. All anyone could suggest was setting global variables … good luck with that in a multi-threaded application.

Tangential rant: C libraries should always do (at least) these two things:

  1. Allow passing in a context pointer – otherwise say good-bye to your thread-safety.
  2. Receive “string” arguments as a pair (ptr, len) – basically a slice. Accept len as 0 if you want to support zero-terminated strings, but for $DEITY’s sake, allow the use of non-null-terminated strings! (I am looking at you, libpq).

One can only dream.

3 Likes