Skip to content

Commit 6054df6

Browse files
authored
Add AnyHashableSendable (#36)
* Add `AnyHashableSendable` * conformances * Tests * Update AnyHashableSendable.swift * Update AnyHashableSendableTests.swift * Update AnyHashableSendable.swift
1 parent d5c0298 commit 6054df6

File tree

4 files changed

+67
-0
lines changed

4 files changed

+67
-0
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ This library comes with a number of tools that make working with Swift concurren
3535
testable.
3636

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

48+
### `AnyHashableSendable`
49+
50+
The `AnyHashableSendable` type is a type-erased wrapper like `AnyHashable` that preserves the
51+
sendability of the underlying value.
52+
4753
### Streams
4854

4955
The library comes with numerous helper APIs spread across the two Swift stream types:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/// A type-erased hashable, sendable value.
2+
///
3+
/// A sendable version of `AnyHashable` that is useful in working around the limitation that an
4+
/// existential `any Hashable` does not conform to `Hashable`.
5+
public struct AnyHashableSendable: Hashable, Sendable {
6+
public let base: any Hashable & Sendable
7+
8+
/// Creates a type-erased hashable, sendable value that wraps the given instance.
9+
public init(_ base: some Hashable & Sendable) {
10+
if let base = base as? AnyHashableSendable {
11+
self = base
12+
} else {
13+
self.base = base
14+
}
15+
}
16+
17+
public static func == (lhs: Self, rhs: Self) -> Bool {
18+
AnyHashable(lhs.base) == AnyHashable(rhs.base)
19+
}
20+
21+
public func hash(into hasher: inout Hasher) {
22+
hasher.combine(base)
23+
}
24+
}
25+
26+
extension AnyHashableSendable: CustomDebugStringConvertible {
27+
public var debugDescription: String {
28+
"AnyHashableSendable(" + String(reflecting: base) + ")"
29+
}
30+
}
31+
32+
extension AnyHashableSendable: CustomReflectable {
33+
public var customMirror: Mirror {
34+
Mirror(self, children: ["value": base])
35+
}
36+
}
37+
38+
extension AnyHashableSendable: CustomStringConvertible {
39+
public var description: String {
40+
String(describing: base)
41+
}
42+
}

Sources/ConcurrencyExtras/Documentation.docc/ConcurrencyExtras.md

+1
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,7 @@ need to make weaker assertions due to non-determinism, but can still assert on s
236236
### Data races
237237

238238
- ``LockIsolated``
239+
- ``AnyHashableSendable``
239240

240241
### Serial execution
241242

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import ConcurrencyExtras
2+
import XCTest
3+
4+
final class AnyHashableSendableTests: XCTestCase {
5+
func testBasics() {
6+
XCTAssertEqual(AnyHashableSendable(1), AnyHashableSendable(1))
7+
XCTAssertNotEqual(AnyHashableSendable(1), AnyHashableSendable(2))
8+
9+
func make(_ base: some Hashable & Sendable) -> AnyHashableSendable {
10+
AnyHashableSendable(base)
11+
}
12+
13+
let flat = make(1)
14+
let nested = make(flat)
15+
16+
XCTAssertEqual(flat, nested)
17+
}
18+
}

0 commit comments

Comments
 (0)