Skip to content

Add AnyHashableSendable #36

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ This library comes with a number of tools that make working with Swift concurren
testable.

* [`LockIsolated`](#lockisolated)
* [`AnyHashableSendable`](#anyhashablesendable)
* [Streams](#streams)
* [Tasks](#tasks)
* [Serial execution](#serial-execution)
Expand All @@ -44,6 +45,11 @@ testable.
The `LockIsolated` type helps wrap other values in an isolated context. It wraps the value in a
class with a lock, which allows you to read and write the value with a synchronous interface.

### `AnyHashableSendable`

The `AnyHashableSendable` type is a type-erased wrapper like `AnyHashable` that preserves the
sendability of the underlying value.

### Streams

The library comes with numerous helper APIs spread across the two Swift stream types:
Expand Down
42 changes: 42 additions & 0 deletions Sources/ConcurrencyExtras/AnyHashableSendable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/// A type-erased hashable, sendable value.
///
/// A sendable version of `AnyHashable` that is useful in working around the limitation that an
/// existential `any Hashable` does not conform to `Hashable`.
public struct AnyHashableSendable: Hashable, Sendable {
public let base: any Hashable & Sendable

/// Creates a type-erased hashable, sendable value that wraps the given instance.
public init(_ base: some Hashable & Sendable) {
if let base = base as? AnyHashableSendable {
self = base
} else {
self.base = base
}
}

public static func == (lhs: Self, rhs: Self) -> Bool {
AnyHashable(lhs.base) == AnyHashable(rhs.base)
}

public func hash(into hasher: inout Hasher) {
hasher.combine(base)
}
}

extension AnyHashableSendable: CustomDebugStringConvertible {
public var debugDescription: String {
"AnyHashableSendable(" + String(reflecting: base) + ")"
}
}

extension AnyHashableSendable: CustomReflectable {
public var customMirror: Mirror {
Mirror(self, children: ["value": base])
}
}

extension AnyHashableSendable: CustomStringConvertible {
public var description: String {
String(describing: base)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ need to make weaker assertions due to non-determinism, but can still assert on s
### Data races

- ``LockIsolated``
- ``AnyHashableSendable``

### Serial execution

Expand Down
18 changes: 18 additions & 0 deletions Tests/ConcurrencyExtrasTests/AnyHashableSendableTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import ConcurrencyExtras
import XCTest

final class AnyHashableSendableTests: XCTestCase {
func testBasics() {
XCTAssertEqual(AnyHashableSendable(1), AnyHashableSendable(1))
XCTAssertNotEqual(AnyHashableSendable(1), AnyHashableSendable(2))

func make(_ base: some Hashable & Sendable) -> AnyHashableSendable {
AnyHashableSendable(base)
}

let flat = make(1)
let nested = make(flat)

XCTAssertEqual(flat, nested)
}
}
Loading