Ответ
Hashable — это протокол, который позволяет типу быть использованным в качестве ключа в Dictionary или элемента в Set. Он является наследником протокола Equatable.
Для чего нужен: Для эффективного хранения и поиска объектов в хеш-таблицах (лежащих в основе Set и Dictionary).
Основное требование: Должна быть реализована функция hash(into:), которая комбинирует хеш-значения всех значимых свойств типа.
Пример реализации для кастомной структуры:
struct User: Hashable {
let id: UUID
let username: String
let email: String
// Реализация Hashable (и Equatable) может быть синтезирована компилятором,
// если все свойства соответствуют Hashable.
// Ручная реализация требуется для выбора конкретных свойств.
func hash(into hasher: inout Hasher) {
// В хеш включаем только id и username, email игнорируем.
hasher.combine(id)
hasher.combine(username)
}
// Соответствующая реализация Equatable (обязательна для Hashable).
static func == (lhs: User, rhs: User) -> Bool {
return lhs.id == rhs.id && lhs.username == rhs.username
}
}
// Использование в Set и Dictionary
let userSet: Set<User> = [User(id: uuid1, username: "alex", email: "a@test.com")]
let userScores: [User: Int] = [user1: 100]
Важнейшее правило (контракт Hashable):
Если два объекта равны (
a == b), их хеш-значения обязаны быть равными (a.hashValue == b.hashValue).
Обратное НЕ верно: Разные объекты (a != b) могут иметь одинаковые хеш-значения (это коллизия). Качественная hash(into:) функция минимизирует вероятность коллизий.
Как работает Hasher:
Swift использует детерминированный, но не криптографический хеш-алгоритм. Значение хеша может меняться между запусками программы (по умолчанию), что является мерой безопасности. Для тестов это поведение можно отключить через переменную окружения.
Автоматический синтез (Swift 4.1+):
Для структур и перечислений, все свойства которых соответствуют Hashable, компилятор автоматически генерирует реализации hash(into:) и ==.
Ответ 18+ 🔞
А, ну вот, опять эти протоколы, блядь. Hashable, говоришь? Да это ж та самая штука, без которой тебе в Dictionary ключом не стать и в Set не залезть, как последнему мудаку. По сути, это такой пропуск в мир быстрого поиска, понимаешь? Как будто тебе дали билет, а ты его в хеш-функцию сунул, и он тебе номерок выдал — короткий и понятный.
Зачем это, спрашивается, нахуй? А затем, что искать по номерку — быстро, ёпта. Представь себе склад: у тебя миллион коробок. Искать по надписи «Вилка для глаз или в жопу раз» — долго. А если у каждой коробки есть короткий цифровой код, который ведёт прямо к ней? Вот это и есть хеш-таблица, а Hashable — это умение твоего типа такой код себе сгенерировать.
Что от тебя хотят? Реализовать одну ёбаную функцию — hash(into:). В неё ты должен запихнуть все значимые свойства, по которым ты хочешь, чтобы объект отличался. Как будто мешаешь ингредиенты в один котёл.
Вот смотри, пример, чтобы не быть мудаком:
struct User: Hashable {
let id: UUID
let username: String
let email: String
// Реализация Hashable (и Equatable) может быть синтезирована компилятором,
// если все свойства соответствуют Hashable.
// Ручная реализация требуется для выбора конкретных свойств.
func hash(into hasher: inout Hasher) {
// В хеш включаем только id и username, email игнорируем.
hasher.combine(id)
hasher.combine(username)
}
// Соответствующая реализация Equatable (обязательна для Hashable).
static func == (lhs: User, rhs: User) -> Bool {
return lhs.id == rhs.id && lhs.username == rhs.username
}
}
// Использование в Set и Dictionary
let userSet: Set<User> = [User(id: uuid1, username: "alex", email: "a@test.com")]
let userScores: [User: Int] = [user1: 100]
Видишь? Я сказал хешеру: «Слушай сюда, дружок-пирожок, вот тебе id, вот тебе username. email — похуй, его не трогай». И теперь два юзера с одинаковым айдишником и ником будут считаться одинаковыми, даже если почты у них разные, блядь. Удивление пиздец, да?
А теперь главное правило, которое нарушать — себя не уважать:
Если два объекта равны по
==, то их хеш-значения обязаны совпадать. Точка, ёбаный насос.
Обратное, конечно, не всегда правда. Могут два разных объекта случайно получить одинаковый хеш — это называется коллизия, и это жизнь, ёпта. Но хорошая hash(into:) старается, чтобы такое случалось реже, чем ты находишь свои носки после стирки.
Про Hasher: Это такой хитрожопый мужик внутри Swift. Он берёт твои данные, мешает их в блендере и выдаёт число. Алгоритм детерминированный (в рамках одного запуска программы), но между разными запусками результат может отличаться — это для безопасности, чтобы всякие умники не догадались. Хотя для тестов можно эту фичу и отключить, если совсем припёрло.
И самое сладкое, блядь (Swift 4.1+): Если у тебя структура или enum, и все его свойства сами по себе Hashable, то можно вообще нихуя не писать! Компилятор сам всё сгенерирует, как по маслу. Просто объяви протокол, и всё — волшебство, ёбушки-воробушки. Но если нужно выбрать только часть свойств — тогда придётся ручками, как в примере выше.
Вот и вся философия. Не усложняй, просто корми хешер нужными полями и не нарушай контракт. А то получишь нестабильную коллекцию и будешь потом чесать репу, почему объект не находится.