Skip to content

Make HTTPFields hashing consistent with equality - #143

Merged
fabianfett merged 3 commits into
apple:mainfrom
Hashim1999164:hash-httpfields-match-equality
Sep 3, 2026
Merged

Make HTTPFields hashing consistent with equality#143
fabianfett merged 3 commits into
apple:mainfrom
Hashim1999164:hash-httpfields-match-equality

Conversation

@Hashim1999164

@Hashim1999164 Hashim1999164 commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Summary

HTTPFields equality does not depend on the order of differently named fields. Hashing used storage order, so equal values could hash differently.

This hashes each name group in relative order, then mixes those group hashes without depending on name order. The existing hash test now asserts equal hashes.

Fixes #139

Test plan

Run the package tests on macOS. Confirm hashMatchesEqualityForDifferentOrder passes without a known issue wrapper.

@nerdsupremacist nerdsupremacist left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for fixing this. Please check if you can simplify the part where we're combining the hash results from all the field names

Comment thread Sources/HTTPTypes/HTTPFields.swift Outdated
Comment on lines +308 to +310
var entryHasher = Hasher()
entryHasher.combine(name)
entryHasher.combine(nameHasher.finalize())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Each field contains the name already. So the name is already included when we hash the field itself. I think you can just avoid this intermediate hasher here and use the nameHashers result directly

// Equality ignores the order of differently named fields, so hashing
// must too. Combine each name's sequence (order of same-named fields
// still matters), then mix those group hashes commutatively.
var grouped = [String: Hasher]()

@nerdsupremacist nerdsupremacist Aug 28, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I wonder if there's a way to perform this change without a dictionary.

The dictionary will cause extra allocations, which might not be the fastest when doing basic hashing

That being said, I don't know off the top of my head, all the contexts in which we hash this struct and how critical performance is. So happy to continue with this implementation and we look for a performance improvement later. Correctness is the top priority

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should at least reserve enough capacity here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reserved grouped.reserveCapacity(_fields.count) so the hasher dictionary does not reallocate as names are inserted.

Comment thread Sources/HTTPTypes/HTTPFields.swift Outdated
Comment on lines +301 to +304
let key = field.name.canonicalName
var nameHasher = grouped[key] ?? Hasher()
nameHasher.combine(field)
grouped[key] = nameHasher

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: can we use default here?

Suggested change
let key = field.name.canonicalName
var nameHasher = grouped[key] ?? Hasher()
nameHasher.combine(field)
grouped[key] = nameHasher
let key = field.name.canonicalName
grouped[key, default: Hasher()].combine(field)

@Hashim1999164

Copy link
Copy Markdown
Contributor Author

Simplified the hasher as suggested. Each field already includes its name, so the extra per entry hasher is gone, and grouping uses the default Hasher subscript. Left the dictionary in place for correctness; happy to follow up on allocations if hashing this type shows up hot.

@Hashim1999164
Hashim1999164 force-pushed the hash-httpfields-match-equality branch from 7085c1a to f58a651 Compare September 1, 2026 15:49

@fabianfett fabianfett left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great! Thanks!

@fabianfett
fabianfett enabled auto-merge (squash) September 3, 2026 13:43
@fabianfett fabianfett added the 🔨 semver/patch No public API change. label Sep 3, 2026
@fabianfett
fabianfett merged commit dfebed0 into apple:main Sep 3, 2026
71 of 73 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🔨 semver/patch No public API change.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Hash implementation does not match Equals implementation

3 participants