Skip to content

Add member_eq builtin for member-wise struct comparisons#2801

Merged
lerno merged 15 commits intoc3lang:masterfrom
NotsoanoNimus:dev/struct_cmp_macro
Mar 19, 2026
Merged

Add member_eq builtin for member-wise struct comparisons#2801
lerno merged 15 commits intoc3lang:masterfrom
NotsoanoNimus:dev/struct_cmp_macro

Conversation

@NotsoanoNimus
Copy link
Copy Markdown
Contributor

@NotsoanoNimus NotsoanoNimus commented Jan 20, 2026

The original PR comment below is outdated. member_eq is now the built-in of choice for simple == across all of a given type's members.

See the conversation below.


Allows comparisons of input structures that don't already have an == overload (and it will just compare them anyway if they do). This reduces the need to define @operator(==) overrides which just do return self.a == self.a && self.b == self.b && ...;. They can now become something like this:

struct MyType
{
    bool has_field;
    uint128 something;
    String[] list;
}
// This...
fn bool MyType.old_eq(&self, MyType other) @operator(==)
{
    return self.has_field == other.has_field && self.something == other.something && self.list[..] == other.list[..];
}
// ... now becomes:
fn bool MyType.eq(&self, MyType other) @operator(==) => struct_eq(*self, other);

This is especially useful when memcmp-style comparisons of structs are not.

You can also use the variadic arguments list to supply strings in dot-notation to exclude certain fields from being compared. See the unit tests for more information about how to do this.

The implementation is a bit redundant because there are no flow-control statements at compile-time. This could certainly use a few extra reviewers to ensure it works as broadly as possible, but tests seem to show a good coverage/capability.

@lerno
Copy link
Copy Markdown
Collaborator

lerno commented Mar 6, 2026

I've been looking at this one a lot, but ultimately I think it is too complex to go into the stdlib.

Let me try to explain why I am ambivalent:

  1. This will not properly handle unions (a necessary restriction)
  2. It will produce errors inside of the macro when it fails which is hard to debug
  3. It will recursively compare structs – something which might be undesirable
  4. It will compare pointers by underlying value, depending on the data, a pointer <=> comparison could have been desired
  5. It adds customization options that might be somewhat hard to understand

All of these things are subtly nudging users of the C3 stdlib that having a structure that doesn't work with struct_eq are somehow less idiomatic.

If it had been more "dumb", like "it does == on all members" then it's more understood to be convenience. Here instead it is semi-smart about things and then it looks like there's meaning in the choices.

I think that a dumb "member_equals" that just did == on each field, coupled with a @const function that can be used in the contract of this "member_equals" to check if a struct is eligible for being used with member_equals before the internals of the macro is run.

Finally, this being a macro is a bit dangerous. I think people will just see this and think "oh, I can use compare these things using the struct_eq function", not thinking about the fact that it's the full expansion of fields. Imagine doing this for a 100 field struct more than once. So ideally this perhaps just a generic function (or even a generic macro) That would prevent misuse.

@lerno
Copy link
Copy Markdown
Collaborator

lerno commented Mar 6, 2026

Feel free to discuss this with me on discord.

@lerno
Copy link
Copy Markdown
Collaborator

lerno commented Mar 6, 2026

Finally I'd like to add that the ambiguity of how to interpret struct fields is exactly why I didn't want this == to be built in for structs. So it's not that you could have made it more advanced or something.

@NotsoanoNimus
Copy link
Copy Markdown
Contributor Author

First of all, thanks for writing out your reasoning very clearly and taking the time to review this. When I originally submitted it, I had a few doubts about whether it would be suitable for such a generic purpose, and I suspected there would be a mix of broader problems and more nuances - but I was hoping these issues could push it toward an agreeable state that "just worked".

I think that a dumb "member_equals" that just did == on each field, [...]

Ah, so my thoughts about doing something like this instead were opposite of reality: I thought that would be too dumb, but considering allowing most structs to "qualify" to use it (by merit of == working on all members, and without some black-box/semi-smart operations), I think that is very logical. Probably is best to simply let all == invocations from each member do their own thing(s) without any hidden functionality.

this being a macro is a bit dangerous

As we know from my past contributions, "macro" is my middle name. 😃 Jokes aside, agreed. It stems from my own bad habits, and I had somehow rationalized not needing to wrap it in a generic function of some sort.

My follow-up now becomes - would you like me to implement a simpler member_eq instead? I do think it could still be helpful, à la if (member_eq(a, b)) do_thing();

@lerno
Copy link
Copy Markdown
Collaborator

lerno commented Mar 8, 2026

Yes, implement a simpler member_eq, and have a @const helper macro for the contract that can determine whether it's eligible for being used with member_eq, so the error can be reported on the caller.

@lerno
Copy link
Copy Markdown
Collaborator

lerno commented Mar 8, 2026

Like: @require may_member_eq($Type) : "Only structs with members that are trivially comparable with == are supported"

@lerno
Copy link
Copy Markdown
Collaborator

lerno commented Mar 13, 2026

Should we close this one then?

@NotsoanoNimus
Copy link
Copy Markdown
Contributor Author

The updates came out much simpler than the original PR. Any pending reviews notwithstanding, I'd say this is good to go.

@NotsoanoNimus NotsoanoNimus changed the title Add struct_eq builtin for member-wise struct comparisons Add ~~struct_eq~~ member_eq builtin for member-wise struct comparisons Mar 14, 2026
@NotsoanoNimus NotsoanoNimus changed the title Add ~~struct_eq~~ member_eq builtin for member-wise struct comparisons Add member_eq builtin for member-wise struct comparisons Mar 14, 2026
Comment thread lib/std/core/builtin.c3 Outdated
Comment thread lib/std/core/builtin.c3 Outdated
@lerno lerno merged commit d0acf8f into c3lang:master Mar 19, 2026
22 checks passed
@lerno
Copy link
Copy Markdown
Collaborator

lerno commented Mar 19, 2026

Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants