Skip to content

Commit

Permalink
Add AnyHashableSendable (#36)
Browse files Browse the repository at this point in the history
* Add `AnyHashableSendable`

* conformances

* Tests

* Update AnyHashableSendable.swift

* Update AnyHashableSendableTests.swift

* Update AnyHashableSendable.swift
  • Loading branch information
stephencelis authored Sep 19, 2024
1 parent d5c0298 commit 6054df6
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 0 deletions.
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)
}
}

0 comments on commit 6054df6

Please sign in to comment.